/* 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   : Toolbars.c                             */
/*                                                 */
/* Purpose: Toolbar-related functions for the      */
/*          browser.                               */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 21-Nov-96: Created.                    */
/***************************************************/

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

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

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

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

#include "Dialler.h"

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

#include "Browser.h"
#include "ChoiceDefs.h"
#include "CSIM.h"
#include "CtrlDefs.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "Handlers.h"
#include "History.h"
#include "Images.h"
#include "Memory.h"
#include "Mouse.h"
#include "Reformat.h"
#include "URLutils.h"
#include "URLveneer.h"

#include "Toolbars.h"

/* Locals */

static int bar_overlap = -1;

/* Local types */

/* Used to record the requirement of a particular browser to */
/* display a certain message in the status bar. For a given  */
/* page which may include frames, an array of these is built */
/* as the frameset is defined and starts to fetch pages.     */
/* The actual status message is derived from examining the   */
/* priorities of all of the messages.                        */

typedef struct status_content
{
  browser_data * b;
  status_type    type;
  unsigned int   start;
  unsigned int   end;
}
status_content;

/* Static function prototypes */

static ObjectId          toolbars_get_upper_backend      (browser_data * b);
static ObjectId          toolbars_get_lower_backend      (browser_data * b);

static void              toolbars_set_size               (ObjectId o, int height, int yscroll, int flags);
static int               toolbars_find_overlap           (ObjectId o);
static int               toolbars_bar_overlap            (ObjectId o);

static _kernel_oserror * toolbars_animation_set_sprite   (browser_data * b);
static _kernel_oserror * toolbars_update_specific_status (browser_data * b, browser_data * ancestor, int entry, status_type type);
static int               toolbars_write_status           (browser_data * ancestor);
static status_type       toolbars_return_inferred        (browser_data * b);
static _kernel_oserror * toolbars_infer_status           (browser_data * b, browser_data * ancestor, int entry);
static _kernel_oserror * toolbars_add_status_item        (browser_data * ancestor);
static status_type       toolbars_return_act_status      (browser_data * b);

static int               toolbars_count_file_saves       (browser_data * b);
static int               toolbars_count_file_saves_r     (browser_data * b);
static int               toolbars_calculate_progress     (browser_data * b);
static int               toolbars_calculate_progress_r   (browser_data * b, int saves);
static int               toolbars_create_progress        (browser_data * b, char * buffer, int buffer_size);

/*************************************************/
/* toolbars_get_upper()                          */
/*                                               */
/* Returns the object ID of the upper toolbar.   */
/* This may well not be the internal top left    */
/* toolbar of the window due to the swap_bars    */
/* flag or merged toolbars.                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the toolbar.          */
/*                                               */
/* Returns:    Object ID of the toolbar, or NULL */
/*             for an error / no upper toolbar.  */
/*************************************************/

ObjectId toolbars_get_upper(browser_data * b)
{
  if (controls.swap_bars) return toolbars_get_lower_backend(b);
  else                    return toolbars_get_upper_backend(b);
}

/*************************************************/
/* toolbars_get_upper_backend()                  */
/*                                               */
/* Returns the object ID of the upper toolbar.   */
/* This may well not be the internal top left    */
/* toolbar of the window due to merged toolbars. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the toolbar.          */
/*                                               */
/* Returns:    Object ID of the toolbar, or NULL */
/*             for an error / no upper toolbar.  */
/*************************************************/

static ObjectId toolbars_get_upper_backend(browser_data * b)
{
  ObjectId t;

  if (b->all_in_bottom)
  {
    if (
         window_get_tool_bars(InternalBottomLeft,
                              b->self_id,
                              &t,
                              NULL,
                              NULL,
                              NULL)
       )
       return NULL;

    else return t;
  }

  if (
       window_get_tool_bars(InternalTopLeft,
                            b->self_id,
                            NULL,
                            &t,
                            NULL,
                            NULL)
     )
     return NULL;

  else return t;
}

/*************************************************/
/* toolbars_get_lower()                          */
/*                                               */
/* Returns the object ID of the lower toolbar.   */
/* This may well not be the internal bottom      */
/* left toolbar of the window due to the         */
/* swap_bars flag or merged toolbars.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the toolbar.          */
/*                                               */
/* Returns:    Object ID of the toolbar, or NULL */
/*             for an error / no lower toolbar.  */
/*************************************************/

ObjectId toolbars_get_lower(browser_data * b)
{
  if (controls.swap_bars) return toolbars_get_upper_backend(b);
  else                    return toolbars_get_lower_backend(b);
}

/*************************************************/
/* toolbars_get_lower_backend()                  */
/*                                               */
/* Returns the object ID of the lower toolbar.   */
/* This may well not be the internal bottom      */
/* left toolbar of the window due to merged      */
/* toolbars.                                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the toolbar.          */
/*                                               */
/* Returns:    Object ID of the toolbar, or NULL */
/*             for an error / no lower toolbar.  */
/*************************************************/

static ObjectId toolbars_get_lower_backend(browser_data * b)
{
  ObjectId t;

  if (b->all_in_top)
  {
    if (
         window_get_tool_bars(InternalTopLeft,
                              b->self_id,
                              NULL,
                              &t,
                              NULL,
                              NULL)
       )
       return NULL;

    else return t;
  }

  if (
       window_get_tool_bars(InternalBottomLeft,
                            b->self_id,
                            &t,
                            NULL,
                            NULL,
                            NULL)
     )
     return NULL;

  else return t;
}

/*************************************************/
/* toolbars_set_size()                           */
/*                                               */
/* Sets the vertical visible height and scroll   */
/* position of a given toolbar.                  */
/*                                               */
/* Parameters: The object ID of the toolbar;     */
/*                                               */
/*             The visible height to set it to;  */
/*                                               */
/*             The y scroll offset to give it;   */
/*                                               */
/*             Flags - alter InternalTopLeft, or */
/*             InternalBottomLeft toolbar.       */
/*************************************************/

static void toolbars_set_size(ObjectId o, int height, int yscroll, int flags)
{
  WimpGetWindowStateBlock s;
  ObjectId                p;

  /* Get the toolbar's parent object (the window) */

  #ifdef TRACE
    if (tl & (1u<<1)) Printf("\ntoolbars_set_size: Called with object ID %p\n",(void *) o);
  #endif

  toolbox_get_parent(0, o, &p, NULL);

  #ifdef TRACE
    if (tl & (1u<<1)) Printf("toolbars_set_size: Parent ID is %p\n",(void *) p);
  #endif

  /* This has to be here, or the toolbar can drop out of */
  /* the parent window (?!)...                           */

  ChkError(toolbox_show_object(0, o, 0, 0, p, -1));

  /* Get the toolbar's wimp handle and size through Wimp_GetWindowState */

  ChkError(window_get_wimp_handle(0, o, &s.window_handle));
  ChkError(wimp_get_window_state(&s));

  /* Set the vertical size. */

  if (!controls.swap_bars)
  {
    s.visible_area.ymin = s.visible_area.ymax - height;
    ChkError(toolbox_show_object(0, o, 1, &s.visible_area, p, -1));
  }
  else
  {
    s.visible_area.ymax = s.visible_area.ymin + height;
    ChkError(toolbox_show_object(0, o, 1, &s.visible_area, p, -1));
  }

  /* This call is needed for the toolbox to recognise the new size */

  ChkError(window_set_tool_bars(flags, p, o, o, 0, 0));

  /* Set the scroll position - the set_tool_bars call will */
  /* trounce the scroll position (sigh) if this call is    */
  /* done beforehand, as part of the first show_object     */
  /* call.                                                 */

  s.yscroll = yscroll;
  ChkError(toolbox_show_object(0, o, 1, &s.visible_area, p, -1));

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

/*************************************************/
/* toolbars_find_overlap()                       */
/*                                               */
/* Finds the overlap of the height gadgets 0xd   */
/* and 0xe in the upper toolbar, recording this  */
/* in the local bar_overlap int to make future   */
/* references faster.                            */
/*                                               */
/* Parameters: The object ID of the toolbar.     */
/*************************************************/

static int toolbars_find_overlap(ObjectId o)
{
  BBox url, but;
  int  overlap;

  if (gadget_get_bbox(0, o, URLBarSpacer,    &url)) return 0;
  if (gadget_get_bbox(0, o, ButtonBarSpacer, &but)) return 0;

  overlap = but.ymax - url.ymin;
  if (overlap >= 0) return overlap;

  return 0;
}

/*************************************************/
/* toolbars_bar_overlap()                        */
/*                                               */
/* Returns the overlap of the height gadgets 0xd */
/* and 0xe in the upper toolbar, using a local   */
/* cached value for speed.                       */
/*                                               */
/* Parameters: The object ID of the toolbar.     */
/*************************************************/

static int toolbars_bar_overlap(ObjectId o)
{
  if (bar_overlap < 0) bar_overlap = toolbars_find_overlap(o);

  return bar_overlap;
}

/*************************************************/
/* toolbars_set_presence()                       */
/*                                               */
/* Reads the browser_data structure associated   */
/* with a given browser window object ID, and    */
/* ensures the toolbars associated with that     */
/* window match the flags set inside the data    */
/* structure.                                    */
/*                                               */
/* Parameters: A browser_data struct relevant to */
/*             the window holding the toolbars;  */
/*                                               */
/*             A flags word holding the toolbars */
/*             to alter - InternalTopLeft, or    */
/*             InternalBottomLeft.               */
/*************************************************/

void toolbars_set_presence(browser_data * b, unsigned int flags)
{
  ObjectId o;
  ObjectId t;

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

  /* If not a valid browser_data structure, exit */

  if (!is_known_browser(b)) return;

  /* Toolbars can only exist on ancestor browser windows, not */
  /* in any child frames.                                     */

  b = utils_ancestor(b);
  o = b->self_id;

  /* Deal with merged toolbars separately - they're a simple case */

  if (b->all_in_top || b->all_in_bottom)
  {
    /* The URL bar flag is looked at to see if the toolbar is on or off */
    /* as well as the status bar flag, as the former is used for upper  */
    /* toolbars (as it prompts the vertical shift of page objects), and */
    /* the latter for bottom merged toolbars.                           */

    if (b->all_in_top)
    {
      t = toolbars_get_upper(b);

      if (t)
      {
        #ifdef TRACE
          if (tl & (1u<<1)) Printf("toolbars_set_presence: Top left toolbar ID is %p\n",(void *) t);
        #endif

        if (b->url_bar && b->status_bar) ChkError(toolbox_show_object(0, t, 0, NULL, o, -1));
        else ChkError(toolbox_hide_object(0, t));
      }
    }
    else
    {
      t = toolbars_get_lower(b);

      if (t)
      {
        #ifdef TRACE
          if (tl & (1u<<1)) Printf("toolbars_set_presence: Bottom left toolbar ID is %p\n",(void *) t);
        #endif

        if (b->url_bar && b->status_bar) ChkError(toolbox_show_object(0, t, 0, NULL, o, -1));
        else ChkError(toolbox_hide_object(0, t));
      }
    }

    return;
  }

  /* Proceed with 'normal' toolbars */

  if (flags & InternalTopLeft)
  {
    t = toolbars_get_upper(b);

    if (t)
    {
      #ifdef TRACE
        if (tl & (1u<<1)) Printf("toolbars_set_presence: Top left toolbar ID is %p\n",(void *) t);
      #endif

      /* URL and button bars both present */

      if ((b->url_bar) && (b->button_bar)) toolbars_set_size(t,
                                                             toolbars_button_height(b) + toolbars_url_height(b),
                                                             0,
                                                             controls.swap_bars ? InternalBottomLeft : InternalTopLeft);
      /* URL bar only present */

      else if ((b->url_bar) && !(b->button_bar)) toolbars_set_size(t,
                                                                   toolbars_url_height(b),
                                                                   0,
                                                                   controls.swap_bars ? InternalBottomLeft : InternalTopLeft);
      /* Button bar only present */

      else if (!(b->url_bar) && (b->button_bar))
      {
        /* Find what would be the URL bar height if it was present on its own */

        BBox w;

        gadget_get_bbox(0, t, URLBarSpacer, &w);

        /* Now set the toolbar size and Y scroll offset to show only the buttons */

        toolbars_set_size(t,
                          toolbars_button_height(b),
                          - (w.ymax - w.ymin) + toolbars_bar_overlap(t),
                          controls.swap_bars ? InternalBottomLeft : InternalTopLeft);
      }

      /* URL bar and button bar both absent */

      else toolbox_hide_object(0, t);
    }
  }

  if (flags & InternalBottomLeft)
  {
    t = toolbars_get_lower(b);

    if (t)
    {
      #ifdef TRACE
        if (tl & (1u<<1)) Printf("toolbars_set_presence: Bottom left toolbar ID is %p\n",(void *) t);
      #endif

      /* Status bar present */

      if (b->status_bar) ChkError(toolbox_show_object(0, t, 0, NULL, o, -1));

      /* Status bar absent */

      else ChkError(toolbox_hide_object(0, t));
    }
  }

  /* Make sure that the toolbars have the correct gadget positions */

  if (choices.move_gadgets != Choices_MoveGadgets_Never) toolbars_move_gadgets(b);

//  /* Make sure that the window is completely redrawn */
//
//  {
//    WimpGetWindowStateBlock s;
//    s.window_handle = b->window_handle;
//    ChkError(wimp_get_window_state(&s));
//    coords_box_toworkarea(&s.visible_area, (WimpRedrawWindowBlock *) &s);
//    ChkError(wimp_force_redraw(b->window_handle,
//                               s.visible_area.xmin,
//                               s.visible_area.ymin,
//                               s.visible_area.xmax,
//                               s.visible_area.ymax));
//  }

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

/*************************************************/
/* toolbars_move_gadgets()                       */
/*                                               */
/* Generally called as part of some window       */
/* resize event, this function moves and/or      */
/* resizes various gadgets in various toolbars,  */
/* if the toolbars are present in the window.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the toolbars.         */
/*************************************************/

void toolbars_move_gadgets(browser_data * p)
{
  BBox                      b, g1, g2;
  int                       right, gap, width;
  ObjectId                  t;
  WimpGetWindowStateBlock   s;
  _kernel_oserror         * e;

  #ifdef TRACE
    if (tl & (1u<<1)) Printf("\ntoolbars_move_gadgets: Called\n");
  #endif

  /* Small fetch windows can't move their status line contents */

  if (p->small_fetch) return;

  /* Otherwise, continue as normal */

  s.window_handle = p->window_handle;
  ChkError(wimp_get_window_state(&s));

  b = s.visible_area;
  if ((b.xmax - b.xmin) < controls.minimum_convergence) b.xmax = b.xmin + controls.minimum_convergence;

  /* If the URL bar is present... */

  if (p->url_bar)
  {
    t = toolbars_get_upper(p);

    if (t)
    {
      /* If the pop-up menu gadget for the History list needs to move, move it */
      /* and resize the URL writable to maintain a fixed distance between the  */
      /* two gadgets.                                                          */

      e      = gadget_get_bbox(0, t, URLBarHistoryMenuR, &g1);  /* Get the popup's bounding box    */
      ChkError(gadget_get_bbox(0, t, URLBarWrit,  &g2)); /* Get the writable's bounding box */

      /* Where should the right hand edge of the popup be? */

      right = b.xmax - b.xmin - 4;

      /* If the popup doesn't exist, want to set up a BBox as if a zero */
      /* width popup was present.                                       */

      if (e) g1.xmin = g2.xmax, g1.ymin = 0, g1.xmax = !right, g1.ymax = 0;

      /* Gap is the distance between the right hand edge of the URL writable */
      /* and the left hand edge of the popup.                                */

      gap = g1.xmin - g2.xmax;

      /* Is this the same as it is already? If not, move / resize the gadgets. */

      if (right != g1.xmax)
      {
        if (!e)
        {
          /* Move the popup */

          width = g1.xmax - g1.xmin;

          g1.xmax = right;
          g1.xmin = right - width;

          ChkError(gadget_move_gadget(0, t, URLBarHistoryMenuR, &g1));
        }

        /* Resize the URL writable */

        g2.xmax = g1.xmin - gap;

        ChkError(gadget_move_gadget(0, t, URLBarWrit,  &g2));
      }
    }
  }

  /* If the status bar is present... */

  if (p->status_bar)
  {
    t = toolbars_get_lower(p);

    if (t)
    {
      /* As above, move the byte counter display field if need be, */
      /* and if it does move, resize the status display field.     */

      ChkError(gadget_get_bbox(0, t, StatusBarProgress, &g1));
      ChkError(gadget_get_bbox(0, t, StatusBarStatus, &g2));

      gap = g1.xmin - g2.xmax;

      right = b.xmax - b.xmin - 20;

      if (right != g1.xmax)
      {
        width = g1.xmax - g1.xmin;

        g1.xmax = right;
        g1.xmin = right - width;

        ChkError(gadget_move_gadget(0, t, StatusBarProgress, &g1));

        g2.xmax = g1.xmin - gap;

        ChkError(gadget_move_gadget(0, t, StatusBarStatus, &g2));
      }
    }
  }

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

/*************************************************/
/* toolbars_animation_set_sprite()               */
/*                                               */
/* Sets the sprite in the button gadget used for */
/* the status bar animation.                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the animation.        */
/*************************************************/

static _kernel_oserror * toolbars_animation_set_sprite(browser_data * b)
{
  ObjectId t;
  char     v[32];
  int      time_now;

  /* Update the current frame time */

  if (
       _swix(OS_ReadMonotonicTime,
             _OUT(0),

             &time_now)
     )
     b->current_time = 0;

  else b->current_time = time_now;

  /* Find the status bar object Id */

  t = toolbars_get_lower(b);
  if (!t) return 0;

  /* Advance the frame counter and put a validation string  */
  /* that would give a button gadget the relevant animation */
  /* sprite into v.                                         */

  b->current_frame ++;
  if (b->current_frame >= animation_frames) b->current_frame = 0;

  sprintf(v, "sa%d\0", b->current_frame);

  /* Set the validation string on the status bar animation */
  /* button icon to v, so that the new sprite is shown.    */

  return button_set_validation(0, t, StatusBarAnimAnim, v);
}

/*************************************************/
/* toolbars_animation()                          */
/*                                               */
/* Advances the browser animation in the status  */
/* bar by one frame.                             */
/*                                               */
/* Parameters are as for a standard Wimp event   */
/* handler (this is called on null events).      */
/*************************************************/

int toolbars_animation(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int time_now;

  /* What is the time now? */

  if (
       _swix(OS_ReadMonotonicTime,
             _OUT(0),

             &time_now)
     )
     return 0;

  /* If we have animation frames, a displayed status bar, and */
  /* enough time has passed, advance the animation.           */

  if (
       handle->status_bar   &&
       animation_frames > 0 &&
       time_now         > (handle->current_time + controls.anim_delay)
     )
     ChkError(toolbars_animation_set_sprite(handle));

  return 0;
}

/*************************************************/
/* toolbars_animation_drift()                    */
/*                                               */
/* Advances the browser animation in the status  */
/* bar by one frame, until it reaches the first  */
/* frame, when it deregisters itself.            */
/*                                               */
/* Parameters are as for a standard Wimp event   */
/* handler (this is called on null events).      */
/*************************************************/

int toolbars_animation_drift(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int time_now;

  /* What is the time now? */

  if (
       _swix(OS_ReadMonotonicTime,
             _OUT(0),

             &time_now)
     )
     return 0;

  if (
       handle->status_bar   &&
       animation_frames > 0 &&
       time_now         > (handle->current_time + controls.anim_delay)
     )
  {
    ChkError(toolbars_animation_set_sprite(handle));

    /* Deregister the handler if at frame 0. */

    if (handle->current_frame == 0)
    {
      deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) toolbars_animation_drift, handle);
      handle->anim_drift = 0;
    }
  }

  return 0;
}

/*************************************************/
/* toolbars_animate_slow()                       */
/*                                               */
/* Advances an animation in component            */
/* StatusBarAnimAnim (see Toolbars.h) in an      */
/* object of ID given in void * handle (must be  */
/* cast to this to fit as a Wimp event handler)  */
/* by one frame for each complete cycle of the   */
/* main status animation (whether that is        */
/* actually animating or not).                   */
/*                                               */
/* So that more than one dialogue could be up    */
/* and animating at once, the object's client    */
/* handle is used to store the current animation */
/* frame. Consequently this routine is of no use */
/* to dialogues that need the client handle for  */
/* other reasons.                                */
/*                                               */
/* Parameters are as for a standard Wimp event   */
/* handler (this is called on null events).      */
/*************************************************/

int toolbars_animate_slow(int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle)
{
  ObjectId o = (ObjectId) handle;
  char     v[32];
  int      slow_animation_frame;

  /* Because client handles can only store one thing, in this case */
  /* the animation frame, we have no easy way of storing the time  */
  /* that the animation was last advanced by a frame. So, make the */
  /* assumption of a 1cs poll time, and use a strategy of integer  */
  /* division of frame count to get the actual frame number. The   */
  /* timing is thus not at all independent of machine load, but    */
  /* the dialogue box animations are a very low priority issue.    */

  if (toolbox_get_client_handle(0, o, (void **) &slow_animation_frame)) return 0;

  /* Advance the frame counter and put a validation string  */
  /* that would give a button gadget the relevant animation */
  /* sprite into v.                                         */

  slow_animation_frame ++;
  if (slow_animation_frame >= animation_frames * animation_frames * controls.anim_delay) slow_animation_frame = 0;

  sprintf(v, "sa%d\0", slow_animation_frame / (animation_frames * controls.anim_delay));

  /* Set the validation string on the status bar animation */
  /* button icon to v, so that the new sprite is shown.    */

  button_set_validation(0, o, StatusBarAnimAnim, v);
  toolbox_set_client_handle(0, o, (void *) slow_animation_frame);

  return 0;
}

/*************************************************/
/* toolbars_hide_cgi()                           */
/*                                               */
/* This routine will look through a URL for a    */
/* ? and turn it into a terminator. This can be  */
/* used to hide CGI information from the user.   */
/*                                               */
/* Parameters: Pointer to the URL, which must be */
/*             writable in memory, as the first  */
/*             '?' it contains (if any) will be  */
/*             turned into a zero byte.          */
/*************************************************/

void toolbars_hide_cgi(char * url)
{
  char * search = url;

  while (*search)
  {
    if (*search == '?')
    {
      *search = '\0';
      break;
    }

    search++;
  }
}

/*************************************************/
/* toolbars_hide_internal()                      */
/*                                               */
/* When passed a pointer to an internal URL,     */
/* this routine finds the separator between the  */
/* internal specifier plus message token and the */
/* extra information in the URL, and copies this */
/* extra data down to the start of the URL.      */
/*                                               */
/* Parameters: Pointer to the URL, which may be  */
/*             altered quite significantly; so   */
/*             this should be a local copy held  */
/*             by the caller to avoid corrupting */
/*             any important full version of the */
/*             URL.                              */
/*************************************************/

void toolbars_hide_internal(char * iurl)
{
  char * extra;
  int    exoff;

  exoff = urlutils_internal_extra(iurl);
  if (!exoff) return;

  extra = iurl + exoff;

  memmove(iurl, extra, strlen(extra) + 1);
}

/*************************************************/
/* toolbars_update_status()                      */
/*                                               */
/* The standard external way of updating a       */
/* browser window's status bar. The given        */
/* requests that a given message type (see the   */
/* definition of status_type in Toolbars.h)      */
/* be used for the status bar.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which wants the message putting   */
/*             in its (or its ancestor's)        */
/*             status bar;                       */
/*                                               */
/*             Type of message to display.       */
/*************************************************/

_kernel_oserror * toolbars_update_status(browser_data * b, status_type type)
{
  _kernel_oserror * e;
  browser_data    * ancestor = utils_ancestor(b);
  status_content  * contents;
  int               i, found;

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

  /* Refer to the array through 'contents' - slightly more   */
  /* convenient than 'ancestor->status_contents' every time! */

  contents = ancestor->status_contents;

  /* See if the given browser has already got a message */
  /* registered with the base browser...                */

  if (contents)
  {
    #ifdef TRACE
      if (tl & (1u<<1))
      {
        Printf("\ntoolbars_update_status:");
        Printf("\nUpdating for browser %p; old contents:\n",b);

        Printf("\nEntry | b        | Message    | Decay");
        Printf("\n------+----------+------------+------\n");

        for (i = 0; i < ancestor->nstatus; i++)
        {
          switch (contents[i].type)
          {
            default: Printf("%d     | %08x | %02d UNKNOWN | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;

            case 0:  Printf("%d     | %08x | %02d Ready   | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;
            case 1:  Printf("%d     | %08x | %02d Viewing | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;

            case 2:  Printf("%d     | %08x | %02d Format  | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;
            case 3:  Printf("%d     | %08x | %02d Process | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;
            case 4:  Printf("%d     | %08x | %02d GetPics | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;
            case 5:  Printf("%d     | %08x | %02d Fetch   | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;

            case 6:  Printf("%d     | %08x | %02d LinkTo  | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;
            case 7:  Printf("%d     | %08x | %02d Help    | %d\n",i, contents[i].b, contents[i].type, contents[i].end - contents[i].start); break;
          }
        }

        Printf("\n");
      }
    #endif

    /* Loop round all the status entries, looking for */
    /* one which matches the given browser_data.      */
    /* If found, set 'found' to the array index of    */
    /* that entry.                                    */

    found = -1;

    for (i = 0; i < ancestor->nstatus; i++)
    {
      if (contents[i].b == b)
      {
        found = i;
        break;
      }
    }
  }
  else
  {
    found = -1;

    #ifdef TRACE
      if (tl & (1u<<1))
      {
        Printf("\ntoolbars_update_status:");
        Printf("\nUpdating for browser %p; this has no contents array.\n\n",b);
      }
    #endif
  }

  /* If not found, add an entry for this browser */

  if (found < 0)
  {
    #ifdef TRACE
      if (tl & (1u<<1)) Printf("toolbars_update_status: Adding item\n");
    #endif

    e = toolbars_add_status_item(ancestor);
    if (e) return e;

    found    = ancestor->nstatus - 1;
    contents = ancestor->status_contents;

    if (contents && found >= 0)
    {
      contents[found].b     = b;
      contents[found].start = 0;
      contents[found].end   = 0;
      contents[found].type  = Toolbars_Status_NoType;
    }
  }

  /* Proceed if there's definitely an entry to proceed with... */

  if (contents && found >= 0)
  {
    #ifdef TRACE
      if (tl & (1u<<1)) Printf("toolbars_update_status: Exitting through toolbars_update_specific_status\n");
    #endif

    return toolbars_update_specific_status(b, ancestor, found, type);
  }
  else
  {
    #ifdef TRACE
      if (tl & (1u<<1)) Printf("toolbars_update_status: Failed, exitting quietly\n");
    #endif

    return NULL;
  }
}

/*************************************************/
/* toolbars_update_specific_status()             */
/*                                               */
/* Updates the given browser's given entry in    */
/* the given ancestor's status_content array the */
/* given message type... Messages will only be   */
/* allowed to rise in priority, with the         */
/* timeouts on all messages dealing with letting */
/* things fall back again.                       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which owns the entry to be        */
/*             updated;                          */
/*                                               */
/*             Pointer to the ancestor           */
/*             browser_data struct holding the   */
/*             status_content array;             */
/*                                               */
/*             Index of the entry to update;     */
/*                                               */
/*             Message type to update it with.   */
/*************************************************/

static _kernel_oserror * toolbars_update_specific_status(browser_data * b, browser_data * ancestor, int entry, status_type type)
{
  _kernel_oserror * e;
  status_content  * contents = ancestor->status_contents;
  int               timeout  = 0;
  int               highest;

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

  /* This should never happen, but be defensive anyway... Fail */
  /* if the ancestor seems to have no status_content array.    */

  if (!contents || !ancestor->nstatus)
  {
    #ifdef TRACE
      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "No status_content array in toolbars_update_specific_status")

      show_error_ret(&erb);
    #endif

    return NULL;
  }

  /* If the message type requested is lower priority */
  /* than that already in use by this browser, don't */
  /* do anything - let the timeouts handle it.       */

  if (type < contents[entry].type) return NULL;

  /* Otherwise, update the message type */

  contents[entry].type = type;

  /* Reset the timeout counter */

  e = _swix(OS_ReadMonotonicTime,
            _OUT(0),

            &contents[entry].start);

  switch (contents[entry].type)
  {
    case Toolbars_Status_NoType:
    case Toolbars_Status_Ready:
    case Toolbars_Status_Viewing:
    {
      /* For the idle cases, set all timers to zero */

      contents[entry].start = timeout = 0;
    }
    break;

    default:
    case Toolbars_Status_Formatting:
    case Toolbars_Status_Processing:
    case Toolbars_Status_GetPics:
    case Toolbars_Status_Fetching:
    case Toolbars_Status_Connected:
    case Toolbars_Status_SentReq:
    case Toolbars_Status_Responded:
    case Toolbars_Status_Redirected:
    case Toolbars_Status_Connecting:
    {
      /* For some messages, a general timeout */

      timeout = atoi(lookup_control("ShowMiscFor:50",0,0));
    }
    break;

    case Toolbars_Status_PlugIn:
    {
      /* For Plug-In messages, use the Plug-in status timeout */

      timeout = atoi(lookup_control("ShowPlugInFor:150",0,0));
    }
    break;

    case Toolbars_Status_LinkTo:
    {
      /* Specific timeout for LinkTo messages */

      timeout = atoi(lookup_control("ShowLinksFor:200",0,0));
    }
    break;

    case Toolbars_Status_Help:
    {
      /* Specific timeout for Help messages */

      timeout = atoi(lookup_control("ShowHelpFor:600",0,0));
    }
    break;
  }

  contents[entry].end = contents[entry].start + timeout;

  /* Now reflect the highest priority message of the array */

  highest = toolbars_write_status(ancestor);

  /* Work out if the timeout handler needs to be registered */
  /* or deregistered.                                       */

  switch (highest)
  {
    case Toolbars_Status_NoType:
    case Toolbars_Status_Ready:
    case Toolbars_Status_Viewing:
    {
      /* If the highest priority is an idle case and the handler is */
      /* registered, deregister it.                                 */

      if (ancestor->status_handler)
      {
        ancestor->status_handler = 0;

        deregister_null_claimant(Wimp_ENull,
                                 (WimpEventHandler *) toolbars_timeout_status,
                                 ancestor);
      }
    }
    break;

    default:
    {
      /* Otherwise, for non-idle cases, register the handler if it */
      /* isn't already present.                                    */

      if (!ancestor->status_handler)
      {
        ancestor->status_handler = 1;

        register_null_claimant(Wimp_ENull,
                               (WimpEventHandler *) toolbars_timeout_status,
                               ancestor);
      }

    }
    break;
  }

  /* Finished */

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

  return NULL;
}

/*************************************************/
/* toolbars_write_status()                       */
/*                                               */
/* Reflects the highest priority message in a    */
/* status_content array in the status bar of     */
/* the array's owner.                            */
/*                                               */
/* Called by toolbars_update_status as part of   */
/* its normal operation.                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is an ancestor holding a    */
/*             status_content array.             */
/*                                               */
/* Returns:    The message type that was chosen  */
/*             as the highest priority.          */
/*                                               */
/* Assumes:    That the ancestor browser_data    */
/*             pointer is not NULL.              */
/*************************************************/

static int toolbars_write_status(browser_data * ancestor)
{
  char             new_status [Limits_StatusBarStatus];
  char             url        [Limits_StatusBarStatus];
  char           * use_status = new_status;
  int              dontappend = 0;
  int              found      = -1;
  int              i, count;
  status_type      highest;
  ObjectId         t;
  browser_data   * priority;
  status_content * contents = ancestor->status_contents;

  /* Fail if there's no status_content array */

  if (!contents || !ancestor->nstatus)
  {
    #ifdef TRACE
      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "No status_content array in toolbars_write_status")

      show_error_ret(&erb);
    #endif

    return 0;
  }

  /* Get the ID of the toolbar in which the status bar resides */

  t = toolbars_get_lower(ancestor);
  if (!t) return 0;

  /* Examine all entries for the highest priority message */

  highest = Toolbars_Status_NoType;
  count   = 0;

  for (i = 0; i < ancestor->nstatus; i++)
  {
    /* If the message for this entry is higher than the highest recorded */
    /* so far, then record this message as the highest. Reset the count  */
    /* of the number of times this message has been encountered to zero  */
    /* and remember the index into the array in 'found'.                 */

    if (contents[i].type > highest) highest = contents[i].type, count = 1, found = i;

    /* If the message for this entry is the same as the highest recorded */
    /* so far, then increment the count of the number of times the       */
    /* message has been encountered.                                     */

    else if (contents[i].type == highest) count++;
  }

  /* Now have the highest priority message in 'highest', the number of  */
  /* times it has been requested in 'count', and the index of the first */
  /* occurrence of this message in 'found'.                             */

  priority = contents[found].b;

  /* According to the highest priority message, write the new status into 'new_status' */

  switch (highest)
  {
    default:                     /* (Drop through */
    case Toolbars_Status_Ready:  /* to Viewing)   */
    case Toolbars_Status_Viewing:
    {
      char title[Limits_Title];

      *title = 0;

      /* Try to get the window's title. If this generates an error or   */
      /* is equal to the 'blank page' case, use 'Ready' for the status. */

      if (
           window_get_title(0, ancestor->self_id, title, sizeof(title), NULL) ||
           !strcmp(title, lookup_token("BlankPage:Blank page",0,0))
         )
         StrNCpy0(new_status, lookup_token("Ready:Ready",0,0))

      else
      {
        char format[Limits_StatusFormat];

        title[sizeof(title) - 1] = 0;

        StrNCpy0(format, lookup_token("Page:Viewing '%s'",0,0))
        StrNCpy0(url, title)

        url[sizeof(new_status) - strlen(format) - 1] = 0;
        sprintf(new_status, format, url);
      }

      /* Append an 'active' suffix if needed */

      if (priority->anim_handler || priority->plugin_active)
      {
        char * active = lookup_token("Actv: (active)",0,0);

        if (strlen(active) + strlen(new_status) + 1 < sizeof(new_status)) strcat(new_status, active);
      }

      /* If preceeded by a '-' don't ever append a progress counter */

      if (new_status[0] == '-') use_status++, dontappend = 1;
    }
    break;

    case Toolbars_Status_GetPics:
    {
      int images = image_count_pending(ancestor);

      if (images == 1) StrNCpy0(new_status, lookup_token("GetPic:Fetching 1 image...", 0, 0))
      else if (images)  sprintf(new_status, lookup_token("GetPics:Fetching %d images...", 0, 0), images);
      else             StrNCpy0(new_status, lookup_token("GetPic0:All current images fetched...", 0, 0));

      if (new_status[0] == '-') use_status++, dontappend = 1;
    }
    break;

    case Toolbars_Status_Fetching:   /* (Drop through  */
    case Toolbars_Status_Processing: /* to Formatting) */
    case Toolbars_Status_Connected:
    case Toolbars_Status_SentReq:
    case Toolbars_Status_Responded:
    case Toolbars_Status_Redirected:
    case Toolbars_Status_Connecting:
    case Toolbars_Status_Formatting:
    {
      char format[Limits_StatusFormat];

      /* For many occurrences, report an appropriate 'cumulative' */
      /* message; otherwise, report the one specific case.        */
      /*                                                          */
      /* If the browser has a parent - i.e. this is a frames      */
      /* document - then the 'many' equivalents are always used;  */
      /* this is to stop flickering between the 'single' and      */
      /* 'many' states during a fetch/reformat/etc.               */

      if (count > 1 || priority->real_parent)
      {
        if (highest == Toolbars_Status_Fetching)
        {
          /* This one is a little special, as we could be saving an object to disc */
          /* and the user may not have dealt with the save dialogue yet.           */

          if (priority->fetch_status == BS_DATAFETCH && !priority->save_file)
          {
            StrNCpy0(new_status, lookup_token("FetchWait:Waiting for a Save dialogue to be dealt with...", 0, 0))
          }
          else
          {
            StrNCpy0(new_status, lookup_token("FetchMany:Fetching frame contents...", 0, 0))
          }
        }

        /* Simpler cases */

        else if (highest == Toolbars_Status_Processing) StrNCpy0(new_status, lookup_token("ProcessMany:Processing frames contents...",                      0, 0))
        else if (highest == Toolbars_Status_Connected)  StrNCpy0(new_status, lookup_token("ConnectedMany:Connected to servers to fetch frames contents...", 0, 0))
        else if (highest == Toolbars_Status_SentReq)    StrNCpy0(new_status, lookup_token("SentReqMany:Sent requests to fetch frames contents...",          0, 0))
        else if (highest == Toolbars_Status_Responded)  StrNCpy0(new_status, lookup_token("RespondedMany:Responses received for frames contents...",        0, 0))
        else if (highest == Toolbars_Status_Connecting) StrNCpy0(new_status, lookup_token("ConnectingMany:Trying to connect to fetch frames contents...",   0, 0))
        else if (highest == Toolbars_Status_Redirected) StrNCpy0(new_status, lookup_token("RedirectedMany:Frame fetches are being redirected...",           0, 0))
        else                                            StrNCpy0(new_status, lookup_token("FormatMany:Formatting frames contents...",                       0, 0))
      }
      else
      {
        /* Find the URL to display, if any */

        if      (browser_fetch_url  (priority)) StrNCpy0(url, browser_fetch_url  (priority))
        else if (browser_current_url(priority)) StrNCpy0(url, browser_current_url(priority))
        else *url = 0;

        if (*url)
        {
          /* Get the Messages file token as a format string for sprintf, into */
          /* which the URL can be substituted.                                */

          if (highest == Toolbars_Status_Fetching)
          {
            if (priority->fetch_status == BS_DATAFETCH && !priority->save_file)
            {
              StrNCpy0(format, lookup_token("FetchWait:Waiting for a Save dialogue to be dealt with...", 0, 0))
            }
            else
            {
              StrNCpy0(format, lookup_token("Fetch:Fetching '%s'...", 0, 0))
            }
          }
          else if (highest == Toolbars_Status_Processing) StrNCpy0(format, lookup_token("Process:Processing '%s'...",                        0, 0))
          else if (highest == Toolbars_Status_Connected)  StrNCpy0(format, lookup_token("Connected:Connected to server to fetch '%s'...",    0, 0))
          else if (highest == Toolbars_Status_SentReq)    StrNCpy0(format, lookup_token("SentReq:Sent request to fetch '%s'...",             0, 0))
          else if (highest == Toolbars_Status_Responded)  StrNCpy0(format, lookup_token("RespondedMany:Response received for '%s'...",       0, 0))
          else if (highest == Toolbars_Status_Connecting) StrNCpy0(format, lookup_token("ConnectingMany:Trying to connect to fetch '%s'...", 0, 0))
          else if (highest == Toolbars_Status_Redirected) StrNCpy0(format, lookup_token("RedirectedMany:Redirecting to '%s'...",             0, 0))
          else                                            StrNCpy0(format, lookup_token("Format:Formatting '%s'...",                         0, 0))

          /* Possibly hide CGI information in the URL */

          #ifdef HIDE_CGI
            toolbars_hide_cgi(url);
          #endif

          /* Ensure the URL isn't too long to fit int he new_status */
          /* buffer along with the format string defined above. If  */
          /* the format string comes from a messages file we can't  */
          /* make assumptions about how many of its characters will */
          /* actually appear in the final output string after the   */
          /* sprintf call, so we must treat it as if they all will  */
          /* appear - hence the ' - 1'.                             */

          url[sizeof(new_status) - strlen(format) - 1] = 0;

          toolbars_hide_internal(url);

          /* Write the combined message into new_status */

          sprintf(new_status, format, url);
        }
        else
        {
          /* The URL is not known, so can do a direct lookup into new_status */

          if (highest == Toolbars_Status_Fetching)
          {
            if (priority->fetch_status == BS_DATAFETCH && !priority->save_file)
            {
              StrNCpy0(new_status, lookup_token("FetchWait:Waiting for a Save dialogue to be dealt with...", 0, 0))
            }
            else
            {
              StrNCpy0(new_status, lookup_token("FetchUK:Fetching web page...", 0, 0))
            }
          }
          else if (highest == Toolbars_Status_Processing) StrNCpy0(new_status, lookup_token("ProcessUK:Processing web page...",            0, 0))
          else if (highest == Toolbars_Status_Connected)  StrNCpy0(new_status, lookup_token("ConnectedUK:Connected to server...",          0, 0))
          else if (highest == Toolbars_Status_SentReq)    StrNCpy0(new_status, lookup_token("SentReqUK:Sent request for web page...",      0, 0))
          else if (highest == Toolbars_Status_Responded)  StrNCpy0(new_status, lookup_token("RespondedUK:Server response received...",     0, 0))
          else if (highest == Toolbars_Status_Connecting) StrNCpy0(new_status, lookup_token("ConnectingUK:Trying to connect to server...", 0, 0))
          else                                            StrNCpy0(new_status, lookup_token("FormatUK:Formatting web page...",             0, 0))
        }
      }

      if (new_status[0] == '-') use_status++, dontappend = 1;
    }
    break;

    case Toolbars_Status_Help:
    {
      StrNCpy0(new_status, priority->status_help);

      dontappend = 1;
    }
    break;

    case Toolbars_Status_LinkTo:
    {
      HStream * over;
      char      format[Limits_StatusFormat];
      int       allowed;
      int       dealt_with = 0;

      /* For comments on the use of the 'format' buffer, see the */
      /* code above.                                             */

      over = priority->pointer_over;

      /* If the token that the pointer is over isn't suitable for a LinkTo */
      /* message, use the keyboard selected item instead; so the pointer   */
      /* always takes priority over the keyboard.                          */

      if (
           !over ||
           (
             (
               !over->anchor  ||
               !*over->anchor
             )
             && !(over->type & (TYPE_ISMAP | TYPE_ISCLIENTMAP))
           )
         )
         over = ancestor->selected;

      /* So, do we now have a suitable token, be it from the token the */
      /* pointer was over or whatever is keyboard selected?            */

      if (
           over &&
           (
             (
               over->anchor &&
               *over->anchor
             )
             || (over->type & (TYPE_ISMAP | TYPE_ISCLIENTMAP))
           )
         )
      {
        /* Yes, so create an appropriate status message. */

        StrNCpy0(format, lookup_token("LinkTo:Link to '%s'...",0,0));

        /* If a client-side image map, must work out the URL */

        if (over->type & TYPE_ISCLIENTMAP)
        {
          char * csim_url;

          csim_return_info(priority,
                           over,
                           priority->map_x,
                           priority->map_y,
                           &csim_url,
                           NULL,
                           NULL);

          if (csim_url && *csim_url)
          {
            dealt_with = 1;

            StrNCpy0(url, csim_url)
          }
        }

        /* If a server side map and either not client side as well, */
        /* or the client side map gave us no URL, use the anchor    */
        /* instead, and append coordinate information.              */
        /*                                                          */
        /* If not server side, just use the anchor information in   */
        /* the token on its own.                                    */

        if (!dealt_with && over->anchor && *over->anchor)
        {
          StrNCpy0(url, over->anchor);

          #ifdef HIDE_CGI
            toolbars_hide_cgi(url);
          #endif

          allowed          = sizeof(new_status) - strlen(format);
          url[allowed - 1] = 0;

          toolbars_hide_internal(url);

          /* If a server-side map, want to append coordinate info. */

          if (over->type & TYPE_ISMAP)
          {
            int required = utils_number_length(priority->map_x) +
                           utils_number_length(priority->map_y) +
                           5; /* 5 = length of " (, )" */

            if (strlen(url) + required + 1 <= allowed)
            {
              char append[128];

              sprintf(append, " (%d, %d)", priority->map_x, priority->map_y);

              strcat(url, append);
            }
          }

          dealt_with = 1;
        }
      }

      /* Did we end up with something we can use in a LinkTo message? */

      if (dealt_with)
      {
        /* Yes, so compile the final message together now */

        sprintf(new_status, format, url);
        if (new_status[0] == '-') use_status++, dontappend = 1;
      }
      else
      {
        /* No suitable token, so the message is out of date; that */
        /* is, no object is selected (say) but the message is     */
        /* still on a timer. In that case, cancel it.             */

        toolbars_cancel_status(priority, highest);

        return Toolbars_Status_Ready;
      }
    }
    break;

    case Toolbars_Status_PlugIn:
    {
      StrNCpy0(new_status, priority->plugin_status ? priority->plugin_status : "");

      dontappend = 1;
    }
    break;
  }

  /* Right, finally have a message in new_status. Now */
  /* append the fetch progress, if required.          */
  /*                                                  */
  /* Any messages starting with '-' will never have a */
  /* counter appended, as dontappend will have been   */
  /* set to 1 by things checking this above.          */

  if (controls.append_status && !dontappend)
  {
    char progress[Limits_FetchProgress];
    int  add;

    toolbars_create_progress(contents[found].b, progress, sizeof(progress));

    add = 1 + 1 + (controls.use_brackets ? 2 : 0); /* (Terminating byte, plus separating space, possibly plus 2 brackets) */

    /* Only proceed if there's room for the extra text */

    if (strlen(new_status) + strlen(progress) + add <= sizeof(new_status))
    {
      /* Concatenate the extra text */

      strcat(new_status, " ");
      if (controls.use_brackets) strcat(new_status, "(");
      strcat(new_status, progress);
      if (controls.use_brackets) strcat(new_status, ")");
    }
  }

  /* Check that the string isn't the same as already */
  /* present, and if not, update the status bar. The */
  /* 'url' char array used temporarily is now free,  */
  /* with the new status line in new_status, so we   */
  /* can reuse that here.                            */

  ChkError(displayfield_get_value(0, t, StatusBarStatus, url, sizeof(url),NULL));

  if (
       strcmp(use_status, url)
     )
  {
    /* If the string has changed, display it */

    ChkError(displayfield_set_value(0,
                                    t,
                                    StatusBarStatus,
                                    use_status));

    /* Ensure the toolbar buttons are up to date in light of the new status */

    ChkError(toolbars_set_button_states(priority));
  }

  #ifdef TRACE
    if (tl & (1u<<1)) Printf("toolbars_write_status: Successful, returning with message type %d\n",priority);
  #endif

  return highest;
}

/*************************************************/
/* toolbars_cancel_status()                      */
/*                                               */
/* toolbars_remove_status_item is used to        */
/* remove a browser from its ancestor's array of */
/* status_content structures because the browser */
/* is about to become invalid (e.g. be deleted   */
/* because its associated window was closed).    */
/*                                               */
/* This function is used if an existing browser  */
/* wants to cancel a message it registered       */
/* earlier, before it times out. The message     */
/* type that is being cancelled must be stated;  */
/* if the browser's entry in the status_content  */
/* array does not record this type, no action    */
/* will be taken. Otherwise the message will be  */
/* cancelled as asked.                           */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which wants the message           */
/*             cancelling;                       */
/*                                               */
/*             Message type that it wants to     */
/*             remove.                           */
/*************************************************/

_kernel_oserror * toolbars_cancel_status(browser_data * b, status_type type)
{
  browser_data    * ancestor = utils_ancestor(b);
  status_content  * contents;
  int               i, found;

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

  contents = ancestor->status_contents;
  if (!contents || !ancestor->nstatus)
  {
    #ifdef TRACE
      if (tl & (1u<<1)) Printf("toolbars_cancel_status: Exitting - ancestor has no array\n");
    #endif

    return NULL;
  }

  /* Find the entry */

  found = -1;

  for (i = 0; i < ancestor->nstatus; i++)
  {
    if (contents[i].b == b)
    {
      found = i;
      break;
    }
  }

  /* If not found, exit */

  if (found < 0)
  {
    #ifdef TRACE
      if (tl & (1u<<1)) Printf("toolbars_cancel_status: Exitting - can't find entry\n");
    #endif

    return NULL;
  }

  /* If not the given message type, exit */

  if (contents[found].type != type)
  {
    #ifdef TRACE
      if (tl & (1u<<1)) Printf("toolbars_cancel_status: Exitting - entry message type %d doesn't match given type %d\n", contents[found].type, type);
    #endif

    return NULL;
  }

  /* Otherwise, clear the message and restore something */
  /* more meaningful with the inference routine. To do  */
  /* this, we'll need to free any Plug-In message, if   */
  /* there was one.                                     */

  if (type == Toolbars_Status_PlugIn)
  {
    free(b->plugin_status);
    b->plugin_status = 0;
  }

  contents[found].start = 0;
  contents[found].end   = 0;
  contents[found].type  = Toolbars_Status_NoType;

  #ifdef TRACE
    if (tl & (1u<<1)) Printf("toolbars_cancel_status: Exitting through toolbars_infer_status\n");
  #endif

  return toolbars_infer_status(b, ancestor, found);
}

/*************************************************/
/* toolbars_cancel_all()                         */
/*                                               */
/* If a browser wants to cancel all messages it  */
/* registered earlier, except for LinkTo or Help */
/* messages, perhaps to ensure that some message */
/* is displayed now rather than after another    */
/* times out, it should call this function. The  */
/* original idea was for browsers that have just */
/* completed a full fetch, which want to get rid */
/* of any timing out formatting or fetching      */
/* messages.                                     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which wants the message           */
/*             cancelling.                       */
/*************************************************/

_kernel_oserror * toolbars_cancel_all(browser_data * b)
{
  #ifdef TRACE
    if (tl & (1u<<1)) Printf("toolbars_cancel_all: Called\n");
  #endif

  RetError(toolbars_cancel_status(b, Toolbars_Status_PlugIn));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Connecting));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Redirected));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Responded));
  RetError(toolbars_cancel_status(b, Toolbars_Status_SentReq));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Connected));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Fetching));
  RetError(toolbars_cancel_status(b, Toolbars_Status_GetPics));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Processing));
  RetError(toolbars_cancel_status(b, Toolbars_Status_Formatting));

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

  return NULL;
}

/*************************************************/
/* toolbars_timeout_status()                     */
/*                                               */
/* A null event handler registered and deregis-  */
/* tered by toolbars_update_specific_status,     */
/* which handles checking the 'start' and 'end'  */
/* fields of the given ancestor browser_data     */
/* struct to see if any messages have expired.   */
/*                                               */
/* toolbars_update_specific_status will only     */
/* allow browsers to register progressively      */
/* higher priority messages than previously      */
/* registered - this routine allows those        */
/* previous priorities to drop down.             */
/*                                               */
/* Parameters are as standard for a Wimp null    */
/* event handler (handle = pointer to the        */
/* ancestor browser_data structure).             */
/*************************************************/

int toolbars_timeout_status(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  browser_data   * ancestor = handle; /* Just to make things clearer */
  status_content * contents = ancestor->status_contents;
  int              i, timenow;

  /* Fail if there's no status_content array */

  if (!contents || !ancestor->nstatus)
  {
    #ifdef TRACE
      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "No status_content array in toolbars_timeout_status")

      show_error_ret(&erb);
    #endif

    return 0;
  }

  /* Go through all entries in the array */

  for (i = 0; i < ancestor->nstatus; i++)
  {
    if (contents[i].start > 0)
    {
      /* An active message that is timing out */

      _swix(OS_ReadMonotonicTime,
            _OUT(0),

            &timenow);

      if (contents[i].start != contents[i].end && timenow > contents[i].end)
      {
        /* Message has expired. Was it a Plug-In status message? */

        if (contents[i].type == Toolbars_Status_PlugIn)
        {
          free(ancestor->plugin_status);
          ancestor->plugin_status = NULL;
        }

        /* OK, set the status to 'nothing', then infer the current state */

        contents[i].start = 0;
        contents[i].end   = 0;
        contents[i].type  = Toolbars_Status_NoType;

        ChkError(toolbars_infer_status(contents[i].b, ancestor, i));
      }
    }
    else
    {
      /* Message has <= 0 in 'start' field; if non-idle, this is an expiry indicator */

      switch (contents[i].type)
      {
        case Toolbars_Status_NoType:
        case Toolbars_Status_Ready:
        case Toolbars_Status_Viewing:
        {
          /* Idle, so do nothing */

          i = i; /* Ensure compiler handles this OK... */
        }

        default:
        {
          /* Non-idle message; it has expired. Was it a Plug-In status message? */

          if (contents[i].type == Toolbars_Status_PlugIn)
          {
            free(ancestor->plugin_status);
            ancestor->plugin_status = NULL;
          }

          /* OK, set the status to 'nothing' */

          contents[i].start = 0;
          contents[i].end   = 0;
          contents[i].type  = Toolbars_Status_NoType;
        }
        break;
      }
    }
  }

  return 0;
}

/*************************************************/
/* toolbars_return_inferred()                    */
/*                                               */
/* Looks at a given browser_data struct to work  */
/* out what state it is in, returning this       */
/* information as a status_type value (see       */
/* Toolbars.h).                                  */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct in question.               */
/*                                               */
/* Returns:    Its status, as a status_type      */
/*             value.                            */
/*************************************************/

static status_type toolbars_return_inferred(browser_data * b)
{
  status_type type = Toolbars_Status_Ready;

  /* Work out what message should be displayed. */
  /* Since this is most often called when a     */
  /* status message expires, any messages which */
  /* should appear persistently in the status   */
  /* bar - such as Fetching or Viewing - should */
  /* be returned by this function. Otherwise,   */
  /* the message will be replaced by another    */
  /* that this function chooses.                */

  /* Is there a Plug-In status message? */

  if (b->plugin_status && *b->plugin_status)
  {
    type = Toolbars_Status_PlugIn;
  }

  /* Otherwise, is a page fetch in progress? */

  else
  {
    if (fetch_fetching(b))
    {
      /* If fetching, may in fact just be passing tokens through the fetcher */
      /* with no new actual data to get from the server. In that case want   */
      /* to give a 'processing' message; otherwise, definitely 'fetching'.   */

      if (b->fetch_status == BS_PROCESS) type = Toolbars_Status_Processing;
      else
      {
        /* Need to find out the genuine fetch status, or all of the */
        /* varied fetch information that we can get out of the      */
        /* fetchers gets collapsed to some less meaningful generic  */
        /* message by this routine.                                 */

        int s;

        if (!url_status(0, b->fetch_handle, &s, NULL, NULL))
        {
          if      (s & URL_Status_Transfer)  type = Toolbars_Status_Fetching;
          else if (s & URL_Status_Responded) type = Toolbars_Status_Responded;
          else if (s & URL_Status_SentReq)   type = Toolbars_Status_SentReq;
          else if (s & URL_Status_SentData)  type = Toolbars_Status_SentReq;
          else if (s & URL_Status_Connected) type = Toolbars_Status_Connected;
          else if (!s)                       type = Toolbars_Status_Connecting;
        }
        else type = Toolbars_Status_Fetching;
      }
    }
    else
    {
      int specimg;

      /* If not fetching pages, are we fetching images? If so, display an */
      /* appropriate message.                                             */

      specimg = image_count_specific_pending(b);

      if (specimg && image_fetching(b)) type = Toolbars_Status_GetPics;
      else
      {
        /* If not fetching images either, then may be formatting or idle. */

        if (reformat_formatting(b)) type = Toolbars_Status_Formatting;
        else                        type = Toolbars_Status_Viewing;
      }
    }
  }

  return type;
}

/*************************************************/
/* toolbars_infer_status()                       */
/*                                               */
/* When a toolbar message in the status_content  */
/* array of an ancestor browser has expired,     */
/* this routine is called to work out what the   */
/* current status for the owner browser should   */
/* be. The toolbar is appropriately updated.     */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which owns the message;           */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             which is the above's ancestor;    */
/*                                               */
/*             Index into the ancestor's         */
/*             status_content array for the      */
/*             message owner.                    */
/*                                               */
/* Assumes:    That the ancestor pointer is not  */
/*             NULL.                             */
/*************************************************/

static _kernel_oserror * toolbars_infer_status(browser_data * b, browser_data * ancestor, int entry)
{
  status_content * contents = ancestor->status_contents;
  status_type      type;

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

  /* Fail if there's no status_content array */

  if (!contents || !ancestor->nstatus)
  {
    #ifdef TRACE
      erb.errnum = Utils_Error_Custom_Normal;

      StrNCpy0(erb.errmess,
               "No status_content array in toolbars_infer_status")

      show_error_ret(&erb);
    #endif

    return NULL;
  }

  /* Find the message type */

  type = toolbars_return_inferred(b);

  /* Exit through the toolbar update routine */

  #ifdef TRACE
    if (tl & (1u<<1)) Printf("toolbars_infer_status: Exitting through toolbars_update_specific_status with type %d\n",type);
  #endif

  return toolbars_update_specific_status(b, ancestor, entry, type);
}

/*************************************************/
/* toolbars_add_status_item()                    */
/*                                               */
/* Adds an item to a given ancestor's array of   */
/* status_content structures. The contents are   */
/* not initialised - this is left to the caller. */
/*                                               */
/* Parameters: Pointer to browser_data struct in */
/*             which the addition should be made */
/*             (i.e. the ancestor).              */
/*                                               */
/* Assumes:    That the ancestor browser_data    */
/*             pointer is not NULL.              */
/*************************************************/

static _kernel_oserror * toolbars_add_status_item(browser_data * ancestor)
{
  /* Increment the status counter */

  ancestor->nstatus ++;

  /* Allocate the required memory */

  return memory_set_chunk_size(ancestor, NULL, CK_STAT, ancestor->nstatus * sizeof(status_content));
}

/*************************************************/
/* toolbars_remove_status_item()                 */
/*                                               */
/* Removes a browser_data structure from its     */
/* ancestor's array of status_content structs.   */
/*                                               */
/* Typically called by windows_close_browser, so */
/* issues of having children within the window   */
/* relating to the given browser are dealt with  */
/* automatically (children would be being closed */
/* through a function in Frames.c, with the      */
/* Windows.c function called as part of this).   */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct to remove from the array;  */
/*                                               */
/*             Pointer to the ancestor           */
/*             browser_data structure.           */
/*                                               */
/* Assumes:    That the ancestor pointer is not  */
/*             NULL.                             */
/*************************************************/

_kernel_oserror * toolbars_remove_status_item(browser_data * b, browser_data * ancestor)
{
  status_content * contents = ancestor->status_contents;
  int              i, found;

  /* Can't proceed if the ancestor has no contents array */
  /* (this is fine, so TRACE builds don't give an error  */
  /* either).                                            */

  if (!contents || !ancestor->nstatus) return NULL;

  /* Find the browser's entry in the status contents array */

  found = -1;

  for (i = 0; i < ancestor->nstatus; i++)
  {
    if (contents[i].b == b)
    {
      found = i;
      break;
    }
  }

  /* If not found, exit */

  if (found < 0) return NULL;

  /* Otherwise, remove the entry */

  if (found < ancestor->nstatus - 1)
  {
    memmove(&contents[found],
            &contents[found + 1],
            (ancestor->nstatus - found - 1) * sizeof(status_content));
  }

  ancestor->nstatus--;

  return memory_set_chunk_size(ancestor, NULL, CK_STAT, ancestor->nstatus * sizeof(status_content));
}

/*************************************************/
/* toolbars_return_act_status()                  */
/*                                               */
/* Returns the highest status for the ancestor   */
/* of a given browser window that does not       */
/* include non-active transient items            */
/* (specifically, LinkTo and Help messages).     */
/*                                               */
/* This allows the caller to determine whether a */
/* browser and any of its frames are fetching,   */
/* for example.                                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the ancestor in       */
/*             question.                         */
/*                                               */
/* Returns:    A status_type (see Toolbars.h)    */
/*             describing the ancestor's current */
/*             status.                           */
/*************************************************/

static status_type toolbars_return_act_status(browser_data * b)
{
  browser_data   * ancestor = utils_ancestor(b);
  status_type      highest  = Toolbars_Status_NoType;
  status_content * contents;
  int              i;

  if (!b) return Toolbars_Status_NoType;

  /* Find the highest item that isn't a LinkTo or Help message */

  contents = ancestor->status_contents;

  for (i = 0; i < ancestor->nstatus; i++)
  {
    if (
         contents[i].type == Toolbars_Status_LinkTo ||
         contents[i].type == Toolbars_Status_Help
       )
    {
      status_type type;

      /* Non-active status entry, so need to work out what */
      /* it would say if this message had timed out        */

      type = toolbars_return_inferred(b);

      if (type > highest) highest = type;
    }
    else
    {
      /* Otherwise, remember this status if its the highest */

      if (contents[i].type > highest) highest = contents[i].type;
    }
  }

  return highest;
}

/*************************************************/
/* toolbars_update_progress()                    */
/*                                               */
/* Reflects a fetch's progress in the browser    */
/* window's status bar.                          */
/*                                               */
/* Parameters: A pointer to the browser_data     */
/*             structure associated with the     */
/*             fetch.                            */
/*************************************************/

void toolbars_update_progress(browser_data * b)
{
  char              progress [Limits_StatusBarProgress];
  char              old      [Limits_StatusBarProgress];
  ObjectId          t;
  _kernel_oserror * e;
  browser_data    * ancestor = utils_ancestor(b);
  int               saves;

  /* May want to only update at specific intervals of time */

  if (
       controls.progress_update_delay   &&
       !
       (
         b->fetch_status == BS_IDLE    ||
         b->fetch_status == BS_PROCESS
       )
     )
  {
    int time_now;

    if (
         !_swix(OS_ReadMonotonicTime,
                _OUT(0),

                &time_now)
       )
    {
      if (time_now - ancestor->progress_updated < controls.progress_update_delay) return;

      ancestor->progress_updated = time_now;
    }
  }

  /* Find out how many file saves, if any, are in progress */

  saves = toolbars_create_progress(b, progress, sizeof(progress));

  /* Find the toolbar and try to get the old progress string */

  t = toolbars_get_lower(ancestor);
  if (!t) return;

  e = button_get_value(0, t, StatusBarProgress, old, sizeof(old), NULL);

  /* Only update the display field if the contents have */
  /* changed, to avoid flicker.                         */

  if (!e && strcmp(progress, old))
  {
    int flags;
    int colour_old, colour_new;

    /* Set the colour if required */

    if (controls.colour_progress != Controls_ColourProgress_NotAColour)
    {
      if (!button_get_flags(0, t, StatusBarProgress, &flags))
      {
        browser_data * ancestor = b->ancestor;

        if (!ancestor) ancestor = b;

        /* Remember the old colour, and set the new according  */
        /* to whether or not file saves are in progress. If    */
        /* there, use the Messages file defined colour; else   */
        /* use the colour recorded in the browser_data struct. */

        colour_old = (flags & 0x0f000000) >> 24;
        colour_new = saves ? controls.colour_progress : ancestor->progress_colour;

        if (colour_new != colour_old)
        {
          button_set_flags(0,
                           t,
                           StatusBarProgress,
                           0x0f000000,
                           colour_new << 24);
        }
      }
    }

    button_set_value(0, t, StatusBarProgress, progress);
  }
  else if (e && controls.append_status) toolbars_update_status(b, Toolbars_Status_Ready);
}

/*************************************************/
/* toolbars_count_file_saves()                   */
/*                                               */
/* Checks to see if any frames in a given        */
/* frameset are saving out files.                */
/*                                               */
/* Parameters: Pointer to any browser_data       */
/*             structure in the frameset.        */
/*                                               */
/* Returns:    The number of file saves going on */
/*             in the parent and any children it */
/*             might have.                       */
/*************************************************/

static int toolbars_count_file_saves(browser_data * b)
{
  browser_data * ancestor = b->ancestor;

  if (!ancestor) ancestor = b;

  return toolbars_count_file_saves_r(ancestor);
}

/*************************************************/
/* toolbars_count_file_saves_r()                 */
/*                                               */
/* Recursive back-end to                         */
/* toolbars_count_file_saves.                    */
/*                                               */
/* Parameters: Pointer to the ancestor           */
/*             browser_data struct in the        */
/*             frameset.                         */
/*                                               */
/* Returns:    As toolbars_count_file_saves.     */
/*************************************************/

static int toolbars_count_file_saves_r(browser_data * b)
{
  int i, count = 0;

  if (b->nchildren)
  {
    for (i = 0; i < b->nchildren; i++)
    {
      count += toolbars_count_file_saves_r(b->children[i]);
    }
  }

  if (b->save_file) count ++;

  return count;
}

/*************************************************/
/* toolbars_calculate_progress()                 */
/*                                               */
/* Works out how much data has been fetched for  */
/* a frameset.                                   */
/*                                               */
/* If a file save is in progress, the amount of  */
/* data fetched for that file save and nothing   */
/* else is returned as a negative number (to     */
/* flag that this is happening). Callers must    */
/* remember to check for this, even if only to   */
/* reverse the sign on the returned value.       */
/*                                               */
/* Parameters: Pointer to any browser_data       */
/*             struct in the frameset in         */
/*             question.                         */
/*                                               */
/* Returns:    The amount collectively fetched,  */
/*             in bytes, or the amount fetched   */
/*             for a file save with its sign     */
/*             reversed (i.e. a negative number) */
/*             again in bytes.                   */
/*************************************************/

static int toolbars_calculate_progress(browser_data * b)
{
  browser_data * ancestor = b->ancestor;
  int            saves    = toolbars_count_file_saves(b);

  if (!ancestor) ancestor = b;

  return toolbars_calculate_progress_r(ancestor, saves);
}

/*************************************************/
/* toolbars_calculate_progress_r()               */
/*                                               */
/* Recursive back-end to                         */
/* toolbars_calculate_progress.                  */
/*                                               */
/* Parameters: Pointer to the ancestor           */
/*             browser_data struct in the        */
/*             frameset;                         */
/*                                               */
/*             0 if no browser in the frameset   */
/*             is saving data to a file, else    */
/*             non-zero.                         */
/*                                               */
/* Returns:    As toolbars_calculate_progress.   */
/*************************************************/

static int toolbars_calculate_progress_r(browser_data * b, int saves)
{
  int i, fetched = 0;

  if (b->nchildren)
  {
    int localfetched;

    /* For children, if the frameset has some file saving going on */
    /* use only negative numbers returned, i.e. report cumulative  */
    /* file sizes. Otherwise, use only positive numbers, i.e.      */
    /* report image / page source fetched data.                    */

    for (i = 0; i < b->nchildren; i++)
    {
      localfetched = toolbars_calculate_progress_r(b->children[i], saves);

      if (saves)
      {
        if (localfetched < 0) fetched += localfetched;
      }
      else
      {
        if (localfetched > 0) fetched += localfetched;
      }
    }
  }

  if (b->save_file)
  {
    /* If saving, don't want to confuse the issue with any image */
    /* fetch information or whatever. So just use the current    */
    /* output file size.                                         */

    fetched = (int) (-(ftell(b->save_file)));
  }
  else
  {
    /* Count the image data obtained so far */

    fetched += image_total_bytes_fetched(b);

    /* Add the current source store size */

    if (b->save_oldstore) fetched += b->save_oldstore;
    else if (b->source)   fetched += flex_size((flex_ptr) &b->source);
  }

  return fetched;
}

/*************************************************/
/* toolbars_create_progress()                    */
/*                                               */
/* Builds a string indicating fetch progress in  */
/* a given buffer.                               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the fetch;            */
/*                                               */
/*             Pointer to the buffer;            */
/*                                               */
/*             Size of that buffer.              */
/*                                               */
/* Returns:    The number of file saves going on */
/*             inside the frameset, from         */
/*             toolbars_count_file_saves.        */
/*************************************************/

static int toolbars_create_progress(browser_data * b, char * buffer, int buffer_size)
{
  browser_data * ancestor   = utils_ancestor(b);
  int            saves      = 0;
  int            len        = 1; /* Start with the terminating zero byte accounted for */
  int            percentage = 0;
  int            fetched;

  fetched = toolbars_calculate_progress(ancestor);
  if (fetched < 0) fetched = -fetched, saves = toolbars_count_file_saves(b); /* If a negative number, the absolute value is the amount of data being saved to a file */

  /* Is the buffer big enough? */

  if      (fetched < 10240)    len = utils_number_length(fetched);
  else if (fetched < 10485760) len = utils_number_length((fetched + 512)    / 1024)    + 1; /* For 'K' */
  else                         len = utils_number_length((fetched + 524288) / 1048576) + 1; /* For the 'M' */

  if (saves) len += utils_number_length(saves) + 2;

  if (len > buffer_size)
  {
    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;

      sprintf(erb.errmess,
              "Buffer size of %d isn't as large as required (%d) in toolbars_create_progress",
              buffer_size,
              len);

      show_error_ret(&erb);

    #endif

    return saves;
  }

  /* For single file saves, we may be able to use a percentage counter */

  if (saves == 1 && b->data_size > 0 && b->save_link)
  {
    percentage = (fetched * 100) / b->data_size;

    /* If the data size was misjudged, we can exceed 100% fetched. In */
    /* that case, drop back to a byte counter, rather than sticking   */
    /* at 100%.                                                       */

    if (percentage > 100) percentage = 0;
  }

  /* Write the amount into the buffer; '0' for <=0 bytes, a byte  */
  /* amount for less than 10K, a K amount for less than 10M, else */
  /* an amount in megabytes.                                      */

  if (saves < 2)
  {
    if (percentage)
    {
      /* Special case - if we know the data size, and we're saving an object, */
      /* then display a percentage amount fetched instead.                    */

      sprintf(buffer, "%d%%", percentage);
    }
    else
    {
      if      (fetched < 10240)    sprintf(buffer, "%d",  fetched);
      else if (fetched < 10485760) sprintf(buffer, "%dK", (fetched + 512)    / 1024);
      else                         sprintf(buffer, "%dM", (fetched + 524288) / 1048576);
    }
  }
  else
  {
    if      (fetched < 10240)    sprintf(buffer, "%d: %d",  saves, fetched);
    else if (fetched < 10485760) sprintf(buffer, "%d: %dK", saves, (fetched + 512)    / 1024);
    else                         sprintf(buffer, "%d: %dM", saves, (fetched + 524288) / 1048576);
  }

  return saves;
}

/*************************************************/
/* toolbars_update_url()                         */
/*                                               */
/* Reflects the currently fetching URL in the    */
/* URL bar of a browser window.                  */
/*                                               */
/* Parameters: A pointer to the browser_data     */
/*             structure relevant to the window. */
/*************************************************/

void toolbars_update_url(browser_data * b)
{
  char     url[Limits_URLBarWrit];
  char     cmp[sizeof(url)];
  ObjectId t;

  t = toolbars_get_upper(b);
  if (!t) return;

  if (browser_fetch_url(b))
  {
    StrNCpy0(url, browser_fetch_url(b));
  }
  else if (browser_current_url(b))
  {
    StrNCpy0(url, browser_current_url(b));
  }
  else *url = 0;

  /* If we can use URL aliases, then compare the text to put in */
  /* the bar against the Hotlist URL string. If the same, use   */
  /* an alias for the hotlist instead of the given URL.         */

  #ifdef ALIAS_URLS
  {
    char compare[sizeof(url)];

    urlutils_create_hotlist_url(compare, sizeof(compare));

    if (!strcmp(url, compare)) StrNCpy0(url, lookup_token("AtHotlist:Hotlist",0,0));
  }
  #endif

  #ifdef HIDE_CGI
    toolbars_hide_cgi(url);
  #endif

  /* For internal URLs, strip off the internal bit at the start */

  toolbars_hide_internal(url);

  /* Write the URL in only if it's different from the existing contents */

  writablefield_get_value(0, t, URLBarWrit, cmp, sizeof(cmp), NULL);
  if (strcmp(cmp, url)) writablefield_set_value(0, t, URLBarWrit, url);
}

/*************************************************/
/* toolbars_update_dialler_time()                */
/*                                               */
/* Updates the dialler status display in the     */
/* upper toolbar. Should only be called if there */
/* is a Display Field component, ID 0xF, which   */
/* can take the required display values.         */
/*                                               */
/* This shows online time. It is expected that   */
/* the function will only be called from a null  */
/* event handler to update the time regularly.   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the display.          */
/*************************************************/

_kernel_oserror * toolbars_update_dialler_time(browser_data * b)
{
  _kernel_oserror * e;
  ObjectId          t;
  int               status, start_time, time_now;
  char              display[Limits_URLBarDiallerStatus];
  char              compare[Limits_URLBarDiallerStatus];

  t = toolbars_get_upper(b);
  if (!t) return NULL;

  e = _swix(Dialler_Status,
            _IN(0) | _OUTR(0,1),

            Dialler_Status_ConnectTime,

            &status,
            &start_time);

  /* If the dialler isn't present, don't want to end up */
  /* with a recursive error report, so fail silently.   */

  if (e) return NULL;

  _swix(OS_ReadMonotonicTime,
        _OUT(0),

        &time_now);

  /* Only update as often as specified in the Messages file */

  if (time_now - b->dialler_last > controls.show_dstat_for)
  {
    /* If connected, must display an online time, else show offline. */

    if (status & Dialler_Connected)
    {
      int hours, minutes, seconds;

      /* Remember when this was done */

      b->dialler_last = time_now;

      /* Work out how long we've been online */

      time_now -= start_time;

      if (time_now < 0) time_now = 0;

      hours     = time_now / 360000;
      time_now -= (hours * 360000);
      minutes   = time_now / 6000;
      time_now -= (minutes * 6000);
      seconds   = (time_now / (controls.quantise * 100)) * controls.quantise;

      if (hours > 99) hours = 99;

      if (sizeof(display) >= 9) sprintf(display, "%02u:%02u:%02u\0", hours, minutes, seconds);
      else *display = 0;

      /* Get the current string into 'compare' */

      e = displayfield_get_value(0, t, URLBarDiallerStatus, compare, sizeof(compare), NULL);
      if (e) return e;
      compare[sizeof(compare) - 1] = 0;

      /* Update the display field if necessary, else exit */

      if (strcmp(display, compare))
      {
        e = displayfield_set_value(0, t, URLBarDiallerStatus, display);
        if (e) return e;
      }
    }
  }

  return NULL;
}

/*************************************************/
/* toolbars_update_dialler_status()              */
/*                                               */
/* Updates the dialler status display in the     */
/* upper toolbar. Should only be called if there */
/* is a Display Field component, ID 0xF, which   */
/* can take the required display values.         */
/*                                               */
/* If the status is 'online', a null event       */
/* handler will be installed to call             */
/* toolbars_update_dialler_time to show the      */
/* online time. If the status is not 'online',   */
/* that handler will be deregistered if present. */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the display.          */
/*************************************************/

_kernel_oserror * toolbars_update_dialler_status(browser_data * b)
{
  _kernel_oserror * e;
  ObjectId          t;
  int               status;
  int               showing_time = 0;
  char              display[Limits_URLBarDiallerStatus];
  char              statstr[Limits_URLBarDiallerStatus];
  char              compare[sizeof(display)];

  t = toolbars_get_upper(b);
  if (!t) return NULL;

  memset(statstr, 0, sizeof(statstr));

  e = _swix(Dialler_Status,
            _INR(0,2) | _OUT(0),

            Dialler_Status_StatusString,
            statstr,
            sizeof(statstr),

            &status);

  /* If the dialler isn't present, don't want to end up */
  /* with a recursive error report, so fail silently.   */

  if (e) return NULL;

  statstr[sizeof(statstr) - 1] = 0;

  if (status & Dialler_StatusChanged)
  {
    /* Use the status string. Put the text in the 'tokens' messages buffer */
    /* as this is used lower down for the actual dialler status display    */
    /* string's source. To ensure that any future lookup_token calls don't */
    /* look at the 'lasttokn' buffer and think they don't need to do any   */
    /* work, must mark the contents of 'tokens' as invalid by setting      */
    /* the first char of 'lasttokn' to 0.                                  */

    *lasttokn = 0;
    StrNCpy0(tokens, statstr);
  }
  else
  {
    int info = status & Dialler_GeneralInfoMask;

    /* Get the relevant status text in the 'tokens' messages buffer. */
    /* If an item is missing from the Messages file, the lookup will */
    /* fail silently leaving '!' in the tokens buffer. This is taken */
    /* to mean 'don't show this status', and the routine will exit.  */

    switch (info)
    {
      case Dialler_ExecutingScript_Dial:     lookup_token("Dialling",  0, 0); break; /* Executing dialling script */
      case Dialler_ExecutingScript_Hangup:   lookup_token("Hangup",    0, 0); break; /* Executing hangup script   */
      case Dialler_ExecutingScript_Answer:   lookup_token("Answering", 0, 0); break; /* Answering                 */
      case Dialler_AbortedScript_Syntax:     lookup_token("SError",    0, 0); break; /* Script syntax error       */
      case Dialler_AbortedScript_Timeout:    lookup_token("Timeout",   0, 0); break; /* Timed out                 */
      case Dialler_AbortedScript_NoCarrier:  lookup_token("Carrier",   0, 0); break; /* No carrier                */
      case Dialler_AbortedScript_Error:      lookup_token("MError",    0, 0); break; /* 'ERROR' from modem        */
      case Dialler_AbortedScript_NoDialtone: lookup_token("Dialtone",  0, 0); break; /* No dialtone               */
      case Dialler_AbortedScript_Busy:       lookup_token("MBusy",     0, 0); break; /* 'BUSY' from modem         */
      case Dialler_AbortedScript_NoAnswer:   lookup_token("Answer",    0, 0); break; /* No answer                 */

      /* If there's no specific special status, are we online? */

      default:
      {
        if (status & Dialler_Connected) showing_time = 1;
        else lookup_token("Offline", 0, 0);
      }
      break;
    }
  }

  if (showing_time)
  {
    /* If we want to show online time, install the handler to do so */
    /* (if it isn't already installed).                             */

    if (!b->dialler_last)
    {
      /* dialler_last holds a timer, and if non-zero is used by window close */
      /* routines to show that the null claimant needs deregistering. It is  */
      /* unlikely, but nonetheless possible, that the window could be closed */
      /* after the handler is registered but before it is called, so the     */
      /* dialler_last field would still be zero and no deregistration would  */
      /* take place. Hence to ensure that this doesn't happen, set the field */
      /* to a (small) non-zero value for now.                                */

      b->dialler_last = 1;
      register_null_claimant(Wimp_ENull, (WimpEventHandler *) handle_dialler_display, b);
    }
  }
  else
  {
    /* Otherwise, remove the handler if it is installed */

    if (b->dialler_last) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) handle_dialler_display, b);
    b->dialler_last = 0;

    /* Copy the token contents to the display buffer and update the field */
    /* if the value has changed.                                          */

    StrNCpy0(display, tokens);

    e = displayfield_get_value(0, t, URLBarDiallerStatus, compare, sizeof(compare), NULL);
    if (e) return e;

    /* Update the display field if necessary, else exit */

    if (strcmp(display, compare))
    {
      e = displayfield_set_value(0, t, URLBarDiallerStatus, display);
      if (e) return e;
    }
  }

  /* May need to alter the label on this display, if there is one */

  {
    char label[Limits_URLBarDiallerStatusLabel];

    if (button_get_value(0, t, URLBarDiallerStatusLabel, label, sizeof(label), NULL)) return NULL;
    label[sizeof(label) - 1] = 0;

    if (!showing_time) lookup_token("DiaStatusDial:Dialler",0,0);
    else               lookup_token("DiaStatusTime:Time"   ,0,0);

    if (strcmp(label, tokens))
    {
      StrNCpy0(label, tokens);

      return button_set_value(0, t, URLBarDiallerStatusLabel, label);
    }
  }

  return NULL;
}

/*************************************************/
/* toolbars_merged_to_status()                   */
/*                                               */
/* If the URL writable and status lines are      */
/* merged, this ensures that the combined field  */
/* is put into 'status' display.                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the field;            */
/*                                               */
/*             Object ID of the toolbar the      */
/*             gadgets are in.                   */
/*                                               */
/* Assumes:    That the fields are indeed merged */
/*             though a null object ID can be    */
/*             given.                            */
/*************************************************/

void toolbars_merged_to_status(browser_data * b, ObjectId t)
{
  if (t)
  {
    char label[Limits_StatusBarStatusLabel];

    show_gadget(t, StatusBarStatus);
    hide_gadget(t, URLBarWrit);

    /* Update the label, if present */

    if (!button_get_value(0, t, StatusBarStatusLabel, label, sizeof(label), NULL))
    {
      label[sizeof(label) - 1] = 0;

      lookup_token("DisplayStats:Status",0,0);

      if (strcmp(label, tokens))
      {
        StrNCpy0(label, tokens);
        button_set_value(0, t, StatusBarStatusLabel, label);
      }
    }
  }
}

/*************************************************/
/* toolbars_merged_to_url()                      */
/*                                               */
/* If the URL writable and status lines are      */
/* merged, this ensures that the combined field  */
/* is put into 'URL entry' display.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the field;            */
/*                                               */
/*             Object ID of the toolbar the      */
/*             gadgets are in.                   */
/*                                               */
/* Assumes:    That the fields are indeed merged */
/*             though a null object ID can be    */
/*             given.                            */
/*************************************************/

void toolbars_merged_to_url(browser_data * b, ObjectId t)
{
  if (t)
  {
    char label[Limits_StatusBarStatusLabel];

    show_gadget(t, URLBarWrit);
    hide_gadget(t, StatusBarStatus);

    /* Update the label, if present */

    if (!button_get_value(0, t, StatusBarStatusLabel, label, sizeof(label), NULL))
    {
      label[sizeof(label) - 1] = 0;

      lookup_token("DisplayURL:URL",0,0);

      if (strcmp(label, tokens))
      {
        StrNCpy0(label, tokens);
        button_set_value(0, t, StatusBarStatusLabel, label);
      }
    }
  }
}

/*************************************************/
/* toolbars_set_bistate_state()                  */
/*                                               */
/* Sets a bistate button to a given state, which */
/* depends on the type of button in use.         */
/*                                               */
/* Naming conventions on the button types should */
/* dictate which state is which, with the word   */
/* order of the name relating to the states (so  */
/* for example, BiState_Cancel_Back would be at  */
/* Cancel for state 0, and Back for state 1).    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the button;           */
/*                                               */
/*             Object ID of the toolbar the      */
/*             button is in;                     */
/*                                               */
/*             State (0 or 1) to set it to.      */
/*************************************************/

void toolbars_set_bistate_state(browser_data * b, ObjectId t, int state)
{
  switch (b->bistate)
  {
    case BiState_Cancel_Back:
    {
      ObjectId source;

      /* State 0 = Cancel, state 1 = Back */

      source = state ? ButtonBarBack : ButtonBarCancel;

      /* Copy the characteristics of the gadget over. */

      ChkError(copy_toolaction_info(t, source, t, ButtonBarBistate));

      b->bistate_state = state;
    }
    break;
  }
}

/*************************************************/
/* toolbars_set_tristate_state()                 */
/*                                               */
/* Sets a tristate button to a given state,      */
/* which depends on the type of button in use.   */
/*                                               */
/* Naming conventions on the button types should */
/* dictate which state is which, with the word   */
/* order of the name relating to the states (so  */
/* for example, TriState_Go_GoTo_Stop would be   */
/* at Go for state 0, GoTo for state 1, and Stop */
/* for state 2).                                 */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the button;           */
/*                                               */
/*             Object ID of the toolbar the      */
/*             button is in;                     */
/*                                               */
/*             State (0, 1 or 2) to set it to.   */
/*************************************************/

void toolbars_set_tristate_state(browser_data * b, ObjectId t, int state)
{
  ObjectId source;

  switch (b->tristate)
  {
    case TriState_Go_GoTo_Stop:
    {
      /* State 0 = Go, 1 = GoTo, 2 = Stop */

      if (!state)          source = ButtonBarGo;
      else if (state == 1) source = ButtonBarGoTo;
      else                 source = ButtonBarStop;

      /* Copy the characteristics of the gadget over. */

      ChkError(copy_toolaction_info(t, source, t, ButtonBarTristate));

      b->tristate_state = state;
    }
    break;
  }
}

/*************************************************/
/* toolbars_button_height()                      */
/*                                               */
/* Returns the button bar height for a window,   */
/* or 0 if there is no bar present. If there is  */
/* both a button bar and URL bar present the     */
/* combined height is less than the height of    */
/* both individual bars summed, due to an extra  */
/* gap at the edges for aesthetics. This         */
/* function does not attempt to compensate for   */
/* this (see toolbars_url_height, which does).   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which holds the toolbars of       */
/*             interest.                         */
/*************************************************/

int toolbars_button_height(browser_data * b)
{
  if (b->button_bar)
  {
    ObjectId t;
    BBox     w;

    if (b->all_in_bottom) return 0;

    t = toolbars_get_upper(b);
    if (!t) return 0;

    if (!gadget_get_bbox(0, t, ButtonBarSpacer, &w)) return (w.ymax - w.ymin);
  }

  return 0;
}

/*************************************************/
/* toolbars_url_height()                         */
/*                                               */
/* Returns the URL bar height for a window, or 0 */
/* if there is no URL bar present. If there is   */
/* both a button bar and a URL bar present the   */
/* returned value will be reduced to account for */
/* the effective overlap of the two bars, as     */
/* when each is present individually there is a  */
/* gap at the edges for aesthetics.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which holds the toolbars of       */
/*             interest.                         */
/*************************************************/

int toolbars_url_height(browser_data * b)
{
  if (b->url_bar)
  {
    ObjectId t;
    BBox     w;

    if (b->all_in_bottom) return 0;

    t = toolbars_get_upper(b);
    if (!t) return 0;

    if (!gadget_get_bbox(0, t, URLBarSpacer, &w))
    {
      if (b->button_bar) return (w.ymax - w.ymin - toolbars_bar_overlap(t));
      else return (w.ymax - w.ymin);
    }
  }

  return 0;
}

/*************************************************/
/* toolbars_status_height()                      */
/*                                               */
/* Returns the status bar height for a window,   */
/* or 0 if there is no status bar present.       */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which holds the toolbars of       */
/*             interest.                         */
/*************************************************/

int toolbars_status_height(browser_data * b)
{
  if (b->status_bar)
  {
    ObjectId t;
    BBox     w;

    if (b->all_in_top) return 0;

    t = toolbars_get_lower(b);
    if (!t) return 0;

    if (!gadget_get_bbox(0, t, StatusBarSpacer, &w)) return (w.ymax - w.ymin);
  }

  return 0;
}

/*************************************************/
/* toolbars_set_button_states()                  */
/*                                               */
/* Sets the button states in the button bar and  */
/* URL bar of a given browser window, greying or */
/* ungreying them according to the browser's     */
/* current state.                                */
/*                                               */
/* This call will not return errors derived from */
/* attempts to access non-existant gadgets; they */
/* will fail silently, to allow more varied UI   */
/* designs to work correctly.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the browser window.   */
/*************************************************/

_kernel_oserror * toolbars_set_button_states(browser_data * b)
{
  ObjectId t;

  t = toolbars_get_upper(b);

  if (!t) return NULL; /* (E.g., parents (but not ancestors) in framesets won't have toolbars) */

  if (controls.dont_grey == Controls_DontGrey_GreyAll)
  {
    /* Can only stop if there's activity of some sort */

    if (
         (b->anim_handler    && !b->anim_drift)      ||
         (b->meta_refresh_at && b->meta_refresh_url)
       )
       set_gadget_state(t, ButtonBarStop, 0);

    else set_gadget_state(t, ButtonBarStop, 1);

    /* Can only go back if we're not at the start of the history */
    /* and there's a history to go into.                         */

    if (!history_can_go_backwards(b)) set_gadget_state(t, ButtonBarBack,    1);
    else                              set_gadget_state(t, ButtonBarBack,    0);

    /* Can only go forward if we're in the history somewhere */

    if (!history_can_go_forwards(b))  set_gadget_state(t, ButtonBarForward, 1);
    else                              set_gadget_state(t, ButtonBarForward, 0);

    /* Can only view or save the document source if there is some... */

    if (!b->source)
    {
      set_gadget_state(t, ButtonBarViewSource, 1);
      set_gadget_state(t, ButtonBarSaveSource, 1);
    }
    else
    {
      set_gadget_state(t, ButtonBarViewSource, 0);
      set_gadget_state(t, ButtonBarSaveSource, 0);
    }

    /* Can only save the page as text or a Draw file, or print it, if */
    /* we have a visible line list.                                   */

    if (b->nchildren || !b->cell || !b->cell->nlines)
    {
      set_gadget_state(t, ButtonBarPrint,      1);
      set_gadget_state(t, ButtonBarSaveAsText, 1);
      set_gadget_state(t, ButtonBarSaveAsDraw, 1);
    }
    else
    {
      set_gadget_state(t, ButtonBarPrint,      0);
      set_gadget_state(t, ButtonBarSaveAsText, 0);
      set_gadget_state(t, ButtonBarSaveAsDraw, 0);
    }

    /* Only need to load images if delayed image loading is set  */
    /* in the local browser flags, or plain backgrounds are set. */

    if (
         (
           !b->show_foreground &&
           b->displayed != Display_External_Image
         )
         ||
         !b->show_background
       )
       set_gadget_state(t, ButtonBarLoadImages, 0);

    else set_gadget_state(t, ButtonBarLoadImages, 1);
  }

  if (controls.dont_grey != Controls_DontGrey_GreyNone)
  {
    /* Can only get a history menu if there's a history present. */

    if (history_empty(b)) set_gadget_state(t, URLBarHistoryMenuR, 1);
    else                  set_gadget_state(t, URLBarHistoryMenuR, 0);
  }

  /* Handle bistate buttons */

  if (b->bistate)
  {
    switch (b->bistate)
    {
      case BiState_Cancel_Back:
      {
        /* If the URL writable and status display are merged, */
        /* and the URL display is visible, then the field is  */
        /* in writable mode and the bistate should be at      */
        /* 'cancel'. Otherwise, it should be at 'back'.       */

        if (b->merged_url && !gadget_hidden(t, URLBarWrit)) toolbars_set_bistate_state(b, t, 0);
        else                                                toolbars_set_bistate_state(b, t, 1);
      }
      break;
    }
  }

  /* Handle tristate buttons */

  if (b->tristate)
  {
    switch (b->tristate)
    {
      case TriState_Go_GoTo_Stop:
      {
        /* If the URL writable and status display are merged, */
        /* and the status display is hidden, then the field   */
        /* is in writable mode and the tristate should be at  */
        /* 'go'.                                              */

        if (b->merged_url && !gadget_hidden(t, URLBarWrit)) toolbars_set_tristate_state(b, t, 0);
        else
        {
          /* If we have an animation handler, should be showing 'Stop', */
          /* else we should be at 'go to'. However, odd states can      */
          /* occur so it's wise to check with this tristate, where the  */
          /* state is very important (you can't go anywhere if it's     */
          /* stuck on Stop, for example), what the status bar says.     */

          if (
               (b->anim_handler    && !b->anim_drift)      ||
               (b->meta_refresh_at && b->meta_refresh_url)
             )
          {
            toolbars_set_tristate_state(b, t, 2); /* Stop */
          }
          else
          {
            status_type type = toolbars_return_act_status(b);

            switch (type)
            {
              default:
              case Toolbars_Status_NoType:
              case Toolbars_Status_Ready:
              case Toolbars_Status_Viewing:
              {
                toolbars_set_tristate_state(b, t, 1); /* Go To */
              }
              break;

              case Toolbars_Status_Formatting:
              case Toolbars_Status_Processing:
              case Toolbars_Status_GetPics:
              case Toolbars_Status_Connected:
              case Toolbars_Status_SentReq:
              case Toolbars_Status_Responded:
              case Toolbars_Status_Redirected:
              case Toolbars_Status_Connecting:
              case Toolbars_Status_Fetching:
              {
                toolbars_set_tristate_state(b, t, 2); /* Stop */
              }
              break;
            }
          }
        }
      }
      break;
    }
  }

  return NULL;
}

/*************************************************/
/* toolbars_set_all_button_states()              */
/*                                               */
/* Sets all the button states in the toolbars of */
/* all the browser windows currently open; that  */
/* is, it greys and ungreys them as appropriate  */
/* for that browser's current state.             */
/*************************************************/

_kernel_oserror * toolbars_set_all_button_states(void)
{
  _kernel_oserror * e;
  browser_data    * b;

  b = last_browser;

  while (b)
  {
    e = toolbars_set_button_states(b);
    if (e) return e;

    b = b->previous;
  }

  return NULL;
}