/* 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   : Utils.c                                */
/*                                                 */
/* Purpose: Infrequently altered utilities.        */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 18-Oct-96: Created.                    */
/***************************************************/

#include "setjmp.h"

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

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

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

#include "swis.h"
#include "kernel.h"

#include "toolbox.h"
#include "event.h"
#include "quit.h"
#include "proginfo.h"
#include "window.h"
#include "gadgets.h"

#include "ToolAction.h" /* NOT the proper Toolbox header, as this needed OSLib... */

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

#include "Main.h"
#include "NestWimp.h"
#include "Save.h"
#include "Toolbars.h"

/* Debug build includes */

#ifdef TRACE

  /* Needed to check conversion to millipoints routines don't overflow */

  #include <math.h>

#endif

/* Finally, Utils.h itself */

#include "Utils.h"

/* Local definitions */

#define Acorn_Agent_Start     "Acorn-"
#define Acorn_Agent_Middle    " (RISC OS "
#define Acorn_Agent_End       ")"

#define Netscape_Agent_Start  "Mozilla/4.01 (Compatible; Acorn "
#define Netscape_Agent_Middle "; RISC OS "
#define Netscape_Agent_End    ")"

#define MillipointsPerOSUnit 400

/* Locals */

static int  millipoints_per_os_unit_x = 400; /* See read_os_to_points */
static int  millipoints_per_os_unit_y = 400;
static int  half_mppou_x              = 200;
static int  half_mppou_y              = 200;
static int  overflow_limit_x          = 0x28f5c1; /* = (0x3fffffff / 400) rounded down for caution*/
static int  overflow_limit_y          = 0x28f5c1;

/* Static function prototypes */

static char * lookup_in_given (MessagesFD * control, char * s, int flag, char * arg);

/*************************************************/
/* lookup_token()                                */
/*                                               */
/* Returns a pointer to an expanded message      */
/* token, or '!' if there was an error.          */
/*                                               */
/* NB, due to various limitations of C, the      */
/* lookup is done into a global fixed-size       */
/* buffer. So if you pass multiple calls to this */
/* function in as parameters to something else,  */
/* *it will fail* as each call points to the     */
/* same buffer (which will only contain data     */
/* from the last call).                          */
/*                                               */
/* Parameters: Pointer to a message token;       */
/*                                               */
/*             1 to report an error if the token */
/*             isn't found as well as returning  */
/*             a string of '!', else 0;          */
/*                                               */
/*             An optional pointer to an         */
/*             argument to substitute into the   */
/*             looked up string, or NULL.        */
/*                                               */
/* Returns:    Pointer to the full message text  */
/*             or '!' to signal an error; never  */
/*             a null pointer.                   */
/*                                               */
/* Assumes:    That the pointer to the message   */
/*             token is never NULL.              */
/*************************************************/

char * lookup_token(char * s, int flag, char * arg)
{
  #ifdef TRACE
    if (tl & (1u<<0)) Printf("lookup_token: Called, exitting through lookup_in_given\n");
  #endif

  return lookup_in_given(&meb, s, flag, arg);
}

/*************************************************/
/* lookup_choice()                               */
/*                                               */
/* As lookup_token, but looks up the token in    */
/* the Choices file, rather than the Messages    */
/* file.                                         */
/*                                               */
/* Parameters: As lookup_token.                  */
/*                                               */
/* Returns:    As lookup_token.                  */
/*                                               */
/* Assumes:    As lookup_token.                  */
/*************************************************/

char * lookup_choice(char * s, int flag, char * arg)
{
  #ifdef TRACE
    if (tl & (1u<<0)) Printf("lookup_choice: Called, exitting through lookup_in_given\n");
  #endif

  return lookup_in_given(chb, s, flag, arg);
}

/*************************************************/
/* lookup_control()                              */
/*                                               */
/* As lookup_token, but looks up the token in    */
/* the Controls file, rather than the Messages   */
/* file.                                         */
/*                                               */
/* Parameters: As lookup_token.                  */
/*                                               */
/* Returns:    As lookup_token.                  */
/*                                               */
/* Assumes:    As lookup_token.                  */
/*************************************************/

char * lookup_control(char * s, int flag, char * arg)
{
  #ifdef TRACE
    if (tl & (1u<<0)) Printf("lookup_control: Called, exitting through lookup_in_given\n");
  #endif

  return lookup_in_given(cob, s, flag, arg);
}

/*************************************************/
/* lookup_in_given()                             */
/*                                               */
/* Workhorse back-end to lookup_token,           */
/* lookup_choice and so-on. See lookup_token     */
/* for more information.                         */
/*                                               */
/* Parameters: A MessagesFD pointer, giving the  */
/*             control block of the file to      */
/*             look in;                          */
/*                                               */
/*             Pointer to a message token;       */
/*                                               */
/*             1 to report an error if the token */
/*             isn't found as well as returning  */
/*             a string of '!', else 0;          */
/*                                               */
/*             An optional pointer to an         */
/*             argument to substitute into the   */
/*             looked up string, or NULL.        */
/*                                               */
/* Returns:    Pointer to the full message text  */
/*             or '!' to signal an error; never  */
/*             a null pointer.                   */
/*                                               */
/* Assumes:    That the pointer to the message   */
/*             token is never NULL.              */
/*************************************************/

static char * lookup_in_given(MessagesFD * control, char * s, int flag, char * arg)
{
  _kernel_oserror * e;

  #ifdef TRACE
    if (tl & (1u<<0)) Printf("lookup_in_given: Lookup token '%s'\n",s);
  #endif

  if (strcmp(lasttokn, s))
  {
    #ifdef TRACE
      if (tl & (1u<<0)) Printf("lookup_in_given: Proceeding\n");
    #endif

    StrNCpy0(lasttokn, s);

    e = _swix(MessageTrans_Lookup,
              _INR(0,7),

              control,            /* Pointer to control block               */
              s,                  /* String to look up                      */
              tokens,             /* Global buffer to take looked up string */
              sizeof(tokens) - 1, /* Size of the buffer                     */
              arg,                /* Parameter 0                            */
              0,                  /* Parameter 1                            */
              0,                  /* Parameter 2                            */
              0);                 /* Parameter 3                            */

    tokens[Limits_Message - 1] = 0;

    if (e)
    {
      /* If the lookup fails, put '!' into the lookup buffer and if the */
      /* flag passed into the function is 1, report the error too.      */

      #ifdef TRACE
        if (tl & (1u<<0)) Printf("lookup_in_given: Failed\n");
      #endif

      *lasttokn = 0;
      strcpy(tokens, "!");

      if (flag == 1) show_error_cont(e);
    }
  }

  #ifdef TRACE
    if (tl & (1u<<0)) Printf("lookup_in_given: Returning %s\n",tokens);
  #endif

  return (char *) &tokens;
}

/*************************************************/
/* show_error()                                  */
/*                                               */
/* Reports a (generally serious) error and exits */
/* with EXIT_FAILURE.                            */
/*                                               */
/* Parameters: Pointer to a _kernel_oserror      */
/*             structure.                        */
/*                                               */
/* Assumes:    The pointer may be NULL.          */
/*************************************************/

void show_error(_kernel_oserror *e)
{
  if (e!=NULL)
  {
    _kernel_swi_regs r;
    char             name[Limits_TaskName];
    char             spri[Limits_OS_SpriteName];
    WimpSysInfo      s;

    /* This call checks if errors can be reported in the Desktop, */
    /* or if they need to go into a command window (useful for    */
    /* CLI routines for example)                                  */

    s.r0=0;
    s.r1=0;
    wimp_read_sys_info(3,&s);

    if (s.r0==0) fprintf(stderr,"%s\n",e->errmess);
    else
    {
      StrNCpy0(name,lookup_token("_TaskName:Browse",0,0));  /* Task name for 'Message from...' */
      StrNCpy0(spri,lookup_token("_SpriName:!browse",0,0)); /* Sprite name to put in error box */

      r.r[0] = (int) e;                                       /* Pointer to error block           */
      r.r[1] = (2<<9)+(1<<8);                                 /* Category 2 (warning)             */
      r.r[2] = (int) &name;                                   /* Application name looked up above */
      r.r[3] = (int) &spri;                                   /* Sprite name looked up above      */
      r.r[4] = 1;                                             /* Sprite block pointer (1 = WIMP)  */
      r.r[5] = (int) lookup_token("ErrorBtns:Quit",0,0);      /* Custom button, 'Quit'            */

      _kernel_swi(Wimp_ReportError,&r,&r);
    }

    exit(EXIT_FAILURE); /* Exit after reporting the error */
  }
}

/*************************************************/
/* show_error_cont()                             */
/*                                               */
/* Reports an error but allows execution to then */
/* continue (rather than calling exit()) if the  */
/* user clicks on 'Continue' rather than 'Quit'. */
/* This is accomplished by a longjmp back into   */
/* wherever the setjmp was (e.g. in a poll       */
/* loop).                                        */
/*                                               */
/* Parameters: Pointer to a _kernel_oserror      */
/*             structure.                        */
/*                                               */
/* Assumes:    The pointer may be NULL.          */
/*************************************************/

void show_error_cont(_kernel_oserror *e)
{
  if (e!=NULL)
  {
    _kernel_swi_regs r;
    char             name[Limits_TaskName];
    char             spri[Limits_OS_SpriteName];
    WimpSysInfo      s;

    #ifdef TRACE
      if (e->errnum == Utils_Error_Custom_Fatal) e->errnum = Utils_Error_Custom_Normal;
    #endif

    /* Force 'Quit' only for fatal errors */

    if (e->errnum == Utils_Error_Custom_Fatal) show_error(e);

    /* This all works in much the same way as show_error above. */

    s.r0=0;
    s.r1=0;
    wimp_read_sys_info(3,&s);

    if (s.r0==0) fprintf(stderr,"%s\n",e->errmess);
    else
    {
      StrNCpy0(name, lookup_token("_TaskName:Browse",0,0));
      StrNCpy0(spri, lookup_token("_SpriName:!browse",0,0));

      r.r[0] = (int) e;
      r.r[1] = (2<<9)+(1<<8)+1;
      r.r[2] = (int) &name;
      r.r[3] = (int) &spri;
      r.r[4] = 1;

      /* Have a quit button if not running full screen and the */
      /* error number isn't one defined as having a Continue   */
      /* button only.                                          */

      if (
           e->errnum != Utils_Error_OS_Escape      &&
           e->errnum != Utils_Error_Custom_Message &&
           !choices.full_screen
         )
         r.r[5] = (int) lookup_token("ErrorBtns:Quit",0,0);

      else r.r[5] = 0;

      _kernel_swi(Wimp_ReportError,&r,&r);

      if (r.r[1] == 1) longjmp(env, Main_FromShowErrorCont);

      exit(EXIT_FAILURE); /* Exit if 'Quit' is selected */
    }
  }
}

/*************************************************/
/* show_error_ret()                              */
/*                                               */
/* Reports an error but allows execution to then */
/* continue (rather than calling exit()) if the  */
/* user clicks on 'Continue' rather than 'Quit'. */
/* This is accomplished by simply returning.     */
/*                                               */
/* Parameters: Pointer to a _kernel_oserror      */
/*             structure.                        */
/*                                               */
/* Assumes:    The pointer may be NULL.          */
/*************************************************/

void show_error_ret(_kernel_oserror *e)
{
  if (e!=NULL)
  {
    _kernel_swi_regs r;
    char             name[Limits_TaskName];
    char             spri[Limits_OS_SpriteName];
    WimpSysInfo      s;

    #ifdef TRACE
      if (e->errnum == Utils_Error_Custom_Fatal) e->errnum = Utils_Error_Custom_Normal;
    #endif

    /* Force 'Quit' only for fatal errors */

    if (e->errnum == Utils_Error_Custom_Fatal) show_error(e);

    /* This all works in much the same way as show_error above. */

    s.r0=0;
    s.r1=0;
    wimp_read_sys_info(3,&s);

    if (s.r0==0) fprintf(stderr,"%s\n",e->errmess);
    else
    {
      StrNCpy0(name,lookup_token("_TaskName:Browse",0,0));
      StrNCpy0(spri,lookup_token("_SpriName:!browse",0,0));

      r.r[0] = (int) e;
      r.r[1] = (2<<9)+(1<<8)+1;
      r.r[2] = (int) &name;
      r.r[3] = (int) &spri;
      r.r[4] = 1;

      /* Have a quit button if not running full screen and the */
      /* error number isn't one defined as having a Continue   */
      /* button only.                                          */

      if (
           e->errnum != Utils_Error_OS_Escape      &&
           e->errnum != Utils_Error_Custom_Message &&
           !choices.full_screen
         )
         r.r[5] = (int) lookup_token("ErrorBtns:Quit",0,0);

      else r.r[5] = 0;

      _kernel_swi(Wimp_ReportError,&r,&r);

      if (r.r[1] != 1) exit(EXIT_FAILURE); /* Exit if 'Quit' is selected, else return normally */
    }
  }
}

/*************************************************/
/* report_toolbox_error()                        */
/*                                               */
/* If the toolbox generates an error this funct- */
/* ion will be called to report it. Parameters   */
/* are as standard for a Toolbox event handler.  */
/*************************************************/

int report_toolbox_error(int eventcode,ToolboxEvent *event,IdBlock *idb,void *handle)
{
  ChkError((_kernel_oserror *) &event->data);

  return 1;
}

/*************************************************/
/* make_no_fetch_memory_error()                  */
/*                                               */
/* Typically called from Fetch.c, if a memory    */
/* claim fails early in a fetch. Stores an       */
/* appropriate error in the global error         */
/* block 'erb'.                                  */
/*                                               */
/* Parameters: A numerical value to include in   */
/*             the message to help the           */
/*             programmer know where the error   */
/*             came from.                        */
/*************************************************/

_kernel_oserror * make_no_fetch_memory_error(int stage)
{
  char num[20];

  sprintf(num, "%d", stage);

  erb.errnum =  0;

  StrNCpy0(erb.errmess,
           lookup_token("NoMemFet:There is not enough free memory to perform this fetch (%0).",
                        0,
                        num));

  return &erb;
}

/*************************************************/
/* make_no_cont_memory_error()                   */
/*                                               */
/* Called if a memory claim fails during a fetch */
/* - stores an appropriate error in the global   */
/* error block 'erb'.                            */
/*                                               */
/* Parameters: A numerical value to include in   */
/*             the message to help the           */
/*             programmer know where the error   */
/*             came from.                        */
/*************************************************/

_kernel_oserror * make_no_cont_memory_error(int stage)
{
  char num[20];

  sprintf(num, "%d", stage);

  erb.errnum =  0;

  StrNCpy0(erb.errmess,
           lookup_token("NoMemRea:There is not enough free memory to continue the page fetch (%0).",
                        0,
                        num));

  return &erb;
}

/*************************************************/
/* make_no_table_memory_error()                  */
/*                                               */
/* Typically called from Tables.c, if a memory   */
/* claim fails during table parsing routines.    */
/* Stores an appropriate error in the global     */
/* error block 'erb'.                            */
/*                                               */
/* Parameters: A numerical value to include in   */
/*             the message to help the           */
/*             programmer know where the error   */
/*             came from.                        */
/*************************************************/

_kernel_oserror * make_no_table_memory_error(int stage)
{
  char num[20];

  sprintf(num, "%d", stage);

  erb.errnum =  0;

  StrNCpy0(erb.errmess,
           lookup_token("NoMemTab:There is not enough free memory to display this table (%0).",
                        0,
                        num));

  return &erb;
}

/*************************************************/
/* make_no_memory_error()                        */
/*                                               */
/* A general error generation routine for failed */
/* memory claims. Stores the error in the global */
/* error block 'erb'.                            */
/*                                               */
/* Parameters: A numerical value to include in   */
/*             the message to help the           */
/*             programmer know where the error   */
/*             came from.                        */
/*************************************************/

_kernel_oserror * make_no_memory_error(int stage)
{
  char num[20];

  sprintf(num, "%d", stage);

  erb.errnum =  0;

  StrNCpy0(erb.errmess,
           lookup_token("NoMemGen:There is not enough free memory to continue this operation (%0).",
                        0,
                        num));

  return &erb;
}

/*************************************************/
/* show_centred()                                */
/*                                               */
/* Shows a Toolbox object centred to the screen, */
/* opened persistently where possible            */
/*                                               */
/* Parameters: An Object ID of any Toolbox       */
/*             object that will return its Wimp  */
/*             window handle when                */
/*             Toolbox_ObjectMiscOp is called    */
/*             for it with a reason code of 0 -  */
/*             e.g. Window, DCS, or ColourDBox.  */
/*                                               */
/* Assumes:    That the ID is a valid one.       */
/*************************************************/

void show_centred(ObjectId o)
{
  WimpGetWindowStateBlock w;
  ObjectId                p;
  BBox                    b;
  _kernel_oserror        *e;

  /* Get the Wimp window handle of the Toolbox object */
  /* and get its size using Wimp_GetWindowState. This */
  /* sounds simple, but there's a different function  */
  /* call to get the window handle for each object    */
  /* class. The best (but still poor) approach is to  */
  /* call the Toolbox_MiscOp SWI with a reason code   */
  /* of 0, which in most cases will mean 'return Wimp */
  /* window handle'. If this is not the case for an   */
  /* object type, the Wimp call will then fault and   */
  /* this error condition can be used to default down */
  /* to some coordinate value that seems appropriate. */

  w.window_handle = 0;

  _swix(Toolbox_ObjectMiscOp,
        _INR(0,2) | _OUT(0),

        0,
        o,
        0,

        &w.window_handle);

  e = wimp_get_window_state(&w);

  if (e != NULL)
  {
    w.visible_area.xmin = 480;
    w.visible_area.ymin = 320;
  }
  else
  {
    w.visible_area.xmin = w.visible_area.xmax - w.visible_area.xmin;
    w.visible_area.ymin = w.visible_area.ymax - w.visible_area.ymin;
  }

  /* Find the screen x and y size in pixels and scale them  */
  /* to OS units using OS_ReadModeVariable calls; also work */
  /* out the top left coordinates at the same time          */

  _swix(OS_ReadModeVariable,_INR(0,1) | _OUT(2),-1,11,&w.xscroll);
  _swix(OS_ReadModeVariable,_INR(0,1) | _OUT(2),-1,4,&w.yscroll);
  b.xmin = (((w.xscroll + 1) << w.yscroll) - w.visible_area.xmin) / 2;

  _swix(OS_ReadModeVariable,_INR(0,1) | _OUT(2),-1,12,&w.xscroll);
  _swix(OS_ReadModeVariable,_INR(0,1) | _OUT(2),-1,5,&w.yscroll);
  b.ymin = (((w.xscroll + 1) << w.yscroll) + w.visible_area.ymin) / 2;

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

  ChkError(toolbox_show_object(0,       /* Bit 0 set - Wimp_CreateMenu semantics;          */
                                        /* Bit 1 set - Wimp_CreateSubMenu semantics        */
                               o,       /* Object ID given to function                     */
                               2,       /* 0 - 'default position'; 1 - specify position in */
                                        /* full; 2 - use top left corner coordinate pair   */
                               &b.xmin, /* Top left corner coordinate pair                 */
                               p,       /* Parent object ID                                */
                               -1));    /* Parent component ID (not interested in that)    */
}

/*************************************************/
/* set_corrected_extent()                        */
/*                                               */
/* Sets the extent of a window, making sure that */
/* xmin = 0 and ymax = 0 (so ymin is negative,   */
/* etc. etc.) - this means that topx = topy = 0. */
/*                                               */
/* Parameters: Flags to pass to the Toolbox in   */
/*             the SetExtent call;               */
/*                                               */
/*             The object ID of the browser      */
/*             window to be altered;             */
/*                                               */
/*             Pointer to a BBox holding the     */
/*             extent coordinates.               */
/*************************************************/

_kernel_oserror * set_corrected_extent(unsigned int f, ObjectId o, BBox * w)
{
  BBox t;

  t.xmin = 0;
  t.ymin = w->ymin - w->ymax;
  t.xmax = w->xmax - w->xmin;
  t.ymax = 0;

  return window_set_extent(f,o,&t);
}

/*************************************************/
/* find_behind()                                 */
/*                                               */
/* Returns the window handle of the first non-   */
/* pane window in front of a given window.       */
/*                                               */
/* Parameters: The window handle in question.    */
/*                                               */
/* Returns:    Handle of the first non-pane      */
/*             window in front of the given one, */
/*             or -1 if it is at the top of the  */
/*             stack.                            */
/*************************************************/

int find_behind(int w)
{
  WimpGetWindowStateBlock s;

  s.window_handle = w;

  ChkError(wimp_get_window_state(&s));

  if (s.behind != -1)
  {
    do
    {
      s.window_handle = s.behind;
      ChkError(wimp_get_window_state(&s));
    }
    while(((s.flags & WimpWindow_Pane) != 0) && (s.behind != -1));

    s.behind = s.window_handle;
  }

  return s.behind;
}

/*************************************************/
/* find_tool_sizes()                             */
/*                                               */
/* Returns the title bar and scroll bar widths   */
/* in OS units, including their outlines.        */
/*                                               */
/* Parameters: Pointer to an int, in which the   */
/*             title bar height is placed;       */
/*                                               */
/*             Pointer to an int, in which the   */
/*             horizontal scroll bar bar height  */
/*             is placed;                        */
/*                                               */
/*             Pointer to an int, in which the   */
/*             vertical scroll bar width is      */
/*             placed.                           */
/*                                               */
/* Assumes:    Any of the pointers may be NULL.  */
/*************************************************/

_kernel_oserror * find_tool_sizes(int * theight, int * hheight, int * vwidth)
{
  _kernel_oserror           * e;
  WimpGetWindowOutlineBlock   outline;
  WimpGetWindowStateBlock     s;
  ObjectId                    o;
  int                         th, hh, vw;

  /* Create an object with a title bar and both scroll bars */

  e = toolbox_create_object(0, "ToolSizes", &o);
  if (e) return e;

  /* Open it behind the Pinboard */

  s.visible_area.xmin = 256;
  s.visible_area.ymin = 256;
  s.visible_area.xmax = 512;
  s.visible_area.ymax = 512;
  s.xscroll           = 0;
  s.yscroll           = 0;
  s.behind            = -3;

  e = toolbox_show_object(0, o, Toolbox_ShowObject_FullSpec, &s.visible_area, 0, -1);
  if (e) return e;

  /* Get the window state (for current visible area) and outline */

  e = window_get_wimp_handle(0, o, &s.window_handle);
  if (e) return e;

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

  outline.window_handle = s.window_handle;
  e = wimp_get_window_outline(&outline);
  if (e) return e;

  /* Work out the various sizes */

  th = outline.outline.ymax - s.visible_area.ymax;
  hh = s.visible_area.ymin - outline.outline.ymin;
  vw = outline.outline.xmax - s.visible_area.xmax;

  if (theight) *theight = th;
  if (hheight) *hheight = hh;
  if (vwidth)  *vwidth  = vw;

  /* Return via. deleting the temporary window */

  return toolbox_delete_object(0, o);
}

/*************************************************/
/* register_null_claimant()                      */
/*                                               */
/* Call if you want to claim null polls.         */
/*                                               */
/* Parameters: As for a Wimp event handler, but  */
/*             without the object ID.            */
/*************************************************/

void register_null_claimant(int eventcode,WimpEventHandler * handler,browser_data * handle)
{
  null_counter++;
  ChkError(event_register_wimp_handler(-1,eventcode,handler,handle));

  #ifdef TRACE
    if (tl & (1u<<2))
    {
      int   shut_up_compiler = (int)   handler;
      int * function_name    = (int *) shut_up_compiler;

      Printf("register_null_claimant:   Registered a claimant for browser %p\n", handle);

      /* If the word before the function address has ff in the high */
      /* byte, the function name starts as many bytes before the    */
      /* function address as specified in the low 3 bytes.          */

      function_name -= 1;

      if (((*function_name) & 0xff000000) == 0xff000000)
      {
        Printf("                          Handler is '\0213%s\0217'\n", (char *) (((char *) function_name) - ((*function_name) & 0x00ffffff)));
      }
      else
      {
        Printf("                          Cannot find handler's name; address is \02130x%08x\0217\n", (int) handler);
      }
    }
  #endif

  if (null_counter == 1)
  {
    unsigned int mask;

    ChkError(event_get_mask(&mask));
    mask = (mask & (~Wimp_Poll_NullMask));
    ChkError(event_set_mask(mask));

    #ifdef TRACE
      if (tl & (1u<<2)) Printf("register_null_claimant:   \0211Nulls claimed\0217\n");
    #endif
  }
}

/*************************************************/
/* deregister_null_claimant()                    */
/*                                               */
/* Call if you want to release null polls.       */
/*                                               */
/* Parameters: As for a Wimp event handler, but  */
/*             without the object ID.            */
/*************************************************/

void deregister_null_claimant(int eventcode,WimpEventHandler * handler,browser_data * handle)
{
  null_counter--;
  ChkError(event_deregister_wimp_handler(-1,eventcode,handler,handle));

  #ifdef TRACE
    if (tl & (1u<<2))
    {
      int   shut_up_compiler = (int)   handler;
      int * function_name    = (int *) shut_up_compiler;

      Printf("deregister_null_claimant: Deregistered a claimant for browser %p\n", handle);

      /* If the word before the function address has ff in the high */
      /* byte, the function name starts as many bytes before the    */
      /* function address as specified in the low 3 bytes.          */

      function_name -= 1;

      if (((*function_name) & 0xff000000) == 0xff000000)
      {
        Printf("                          Handler is '\0216%s\0217'\n", (char *) (((char *) function_name) - ((*function_name) & 0x00ffffff)));
      }
      else
      {
        Printf("                          Cannot find handler's name; address is \2160x%08x\0217\n", (int) handler);
      }
    }
  #endif

  if (null_counter < 0) null_counter = 0;

  if (!null_counter)
  {
    unsigned int mask;

    ChkError(event_get_mask(&mask));
    mask = (mask | Wimp_Poll_NullMask);
    ChkError(event_set_mask(mask));

    #ifdef TRACE
      if (tl & (1u<<2)) Printf("deregister_null_claimant: \0212Nulls released\0217\n");
    #endif
  }
}

/*************************************************/
/* intersection()                                */
/*                                               */
/* Takes two BBoxes and returns a pointer to a   */
/* third which is the the intersection between   */
/* the first two, or NULL, if they don't         */
/* intersect.                                    */
/*                                               */
/* Parameters: Pointer to a BBox;                */
/*                                               */
/*             Pointer to another BBox.          */
/*                                               */
/* Returns:    Pointer to a BBox which is the    */
/*             intersection of the given two,    */
/*             or NULL, if they don't intersect. */
/*************************************************/

BBox * intersection(BBox * a, BBox * b)
{
  static BBox intersect;

  #define max(a,b) ((a) > (b) ? (a) : (b))
  #define min(a,b) ((a) < (b) ? (a) : (b))

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

  if ((a->xmin >= b->xmax) || (a->xmax <= b->xmin) || (a->ymin >= b->ymax) || (a->ymax <= b->ymin)) return NULL;

  intersect.xmin = max(a->xmin,b->xmin);
  intersect.xmax = min(a->xmax,b->xmax);
  intersect.ymin = max(a->ymin,b->ymin);
  intersect.ymax = min(a->ymax,b->ymax);

  return &intersect;
}

/*************************************************/
/* set_graphics_intersection()                   */
/*                                               */
/* Intended for redraw loop routines, this sets  */
/* up a given graphics rectangle, but takes      */
/* account of the intersection between this and  */
/* the current (given) graphics rectangle for    */
/* the redraw. The rectangle *must* be restored  */
/* with restore_graphics_intersection() as soon  */
/* as the rectangle set here is finished with;   */
/* the caller must thus remember this rectangle  */
/* for later.                                    */
/*                                               */
/* Parameters: Pointer to a BBox describing the  */
/*             rectangle to set, where xmax and  */
/*             ymax are inclusive;               */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             current graphics rectangle, where */
/*             xmax and ymax are exclusive (e.g. */
/*             as in a WimpRedrawWindowBlock's   */
/*             redraw_area BBox).                */
/*                                               */
/* Returns:    Pointer to a BBox describing the  */
/*             actual rectangle that was set. If */
/*             this is NULL, the two do not      */
/*             intersect at all and the redraw   */
/*             subsequent graphics window        */
/*             restoration can and should be     */
/*             skipped.                          */
/*************************************************/

BBox * set_graphics_intersection(BBox * rbox, BBox * cbox)
{
  BBox * ibox;
  BBox   ogrect = *cbox;

  ogrect.xmax -= 1;
  ogrect.ymax -= 1;

  ibox = intersection(rbox, &ogrect);

  if (!ibox) return NULL;

  bbc_gwindow(ibox->xmin, ibox->ymin, ibox->xmax, ibox->ymax);

  return ibox;
}

/*************************************************/
/* restore_graphics_intersection()               */
/*                                               */
/* Restores the Wimp's redraw graphics rectangle */
/* which was changed by a call to                */
/* set_graphics_intersection (which *must* have  */
/* been called before this restoring function).  */
/*                                               */
/* Parameters: Pointer to a BBox holding the     */
/*             graphics rectangle as it was      */
/*             before set_graphics_intersection  */
/*             was called, where xmax and ymax   */
/*             are exclusive (e.g. as in a       */
/*             WimpRedrawWindowBlock's           */
/*             redraw_area BBox).                */
/*************************************************/

void restore_graphics_intersection(BBox * cbox)
{
  BBox ogrect = *cbox;

  ogrect.xmax -= 1;
  ogrect.ymax -= 1;

  bbc_gwindow(ogrect.xmin, ogrect.ymin, ogrect.xmax, ogrect.ymax);
}

/*************************************************/
/* read_os_to_points()                           */
/*                                               */
/* To avoid having to use a SWI every time       */
/* a conversion is made between OS units and     */
/* points or vice versa, this initialises        */
/* some internal variables which are used        */
/* subsequently. It may be called on a mode      */
/* change, for example, to ensure things are up  */
/* to date.                                      */
/*                                               */
/* If printing, values of MillipointsPerOSUnit   */
/* as defined at the top of this file are used,  */
/* since you can't read it; it seems that during */
/* a print job, this call may *not* be used,     */
/* contrary to the information on PRM 3-573.     */
/* This bug caused *severe* grief during the     */
/* development of the print routines...          */
/*************************************************/

void read_os_to_points(void)
{
  int x = 1, y = 1;

  if (!printing)
  {
    if (
          _swix(Font_Converttopoints,
                _INR(1,2) | _OUTR(1,2),

                x,
                y,

                &x,
                &y)
       )
    {
      millipoints_per_os_unit_x = MillipointsPerOSUnit;
      millipoints_per_os_unit_y = MillipointsPerOSUnit;
    }
    else
    {
      millipoints_per_os_unit_x = x;
      millipoints_per_os_unit_y = y;
    }
  }
  else
  {
    millipoints_per_os_unit_x = MillipointsPerOSUnit;
    millipoints_per_os_unit_y = MillipointsPerOSUnit;
  }

  overflow_limit_x = (0x3fffffff / millipoints_per_os_unit_x) - 1;
  overflow_limit_y = (0x3fffffff / millipoints_per_os_unit_y) - 1;

  half_mppou_x = millipoints_per_os_unit_x / 2;
  half_mppou_y = millipoints_per_os_unit_y / 2;
}

/*************************************************/
/* convert_pair_to_os()                          */
/*                                               */
/* Converts from millipoints to OS units. The    */
/* scale factor is determined by a previous call */
/* to read_os_to_points.                         */
/*                                               */
/* Parameters: A coordinate in millipoints;      */
/*                                               */
/*             Another coord in millipoints;     */
/*                                               */
/*             Pointer to an int into which the  */
/*             first coordinate, converted to OS */
/*             units, is placed;                 */
/*                                               */
/*             Similarly a pointer to an int for */
/*             the second coordinate.            */
/*                                               */
/* Assumes:    The pointers may NOT be NULL. The */
/*             input and output variables may be */
/*             the same (so passing in x, y, &x, */
/*             &y would work correctly).         */
/*************************************************/

void convert_pair_to_os(int x, int y, int * osx, int * osy)
{
  *osx = ((x + half_mppou_x) / millipoints_per_os_unit_x) & ~(wimpt_dx() - 1);
  *osy = ((y + half_mppou_y) / millipoints_per_os_unit_y) & ~(wimpt_dy() - 1);
}

/*************************************************/
/* convert_pair_to_points()                      */
/*                                               */
/* Converts from OS units to millipoints. The    */
/* scale factor is determined by a previous call */
/* to read_os_to_points.                         */
/*                                               */
/* Parameters: A coordinate in OS units;         */
/*                                               */
/*             Another coordinate in OS units;   */
/*                                               */
/*             Pointer to an int into which the  */
/*             first coordinate, converted to    */
/*             millipoints, is placed;           */
/*                                               */
/*             Similarly a pointer to an int for */
/*             the second coordinate.            */
/*                                               */
/* Assumes:    The pointers may not be NULL. The */
/*             input and output variables may be */
/*             the same (so passing in x, y, &x, */
/*             &y would work correctly).         */
/*************************************************/

void convert_pair_to_points(int x, int y, int * mpx, int * mpy)
{
  #ifdef TRACE

    if (abs(x) > overflow_limit_x || abs(y) > overflow_limit_y)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,
              "convert_pair_to_points: Can't convert (%d, %d) to millipoints without overflow.",
              x,y);

      show_error_ret(&erb);

      *mpx = *mpy = 0;

      return;
    }

  #endif

  *mpx = x * millipoints_per_os_unit_x;
  *mpy = y * millipoints_per_os_unit_y;
}

/*************************************************/
/* convert_to_os()                               */
/*                                               */
/* As convert_pair_to_os, but only converts one  */
/* coordinate at a time.                         */
/*                                               */
/* Parameters: An x coordinate in millipoints;   */
/*                                               */
/*             Pointer to an int into which the  */
/*             coordinate, converted to OS       */
/*             units, is placed.                 */
/*                                               */
/* Assumes:    That the pointer is not NULL. The */
/*             input and output variable may be  */
/*             the same (so passing in x, &x     */
/*             would work correctly);            */
/*                                               */
/*             If x and y scalings differ, this  */
/*             will only ever use the x scaling. */
/*************************************************/

void convert_to_os(int x, int * osx)
{
  *osx = ((x + half_mppou_x) / millipoints_per_os_unit_x) & ~(wimpt_dx() - 1);
}

/*************************************************/
/* convert_to_points()                           */
/*                                               */
/* As convert_pair_to_points, but only converts  */
/* one coordinate at a time.                     */
/*                                               */
/* Parameters: An x coordinate in OS units;      */
/*                                               */
/*             Pointer to an int into which the  */
/*             coordinate, converted to milli-   */
/*             points, is placed.                */
/*                                               */
/* Assumes:    That the pointer is not NULL. The */
/*             input and output variable may be  */
/*             the same (so passing in x, &x     */
/*             would work correctly);            */
/*                                               */
/*             If x and y scalings differ, this  */
/*             will only ever use the x scaling. */
/*************************************************/

void convert_to_points(int x, int * mpx)
{
  #ifdef TRACE

    if (abs(x) > overflow_limit_x)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,
              "convert_to_points: Can't convert '%d' to millipoints without overflow.",
              x);

      show_error_ret(&erb);

      *mpx = 0;

      return;
    }

  #endif

  *mpx = x * millipoints_per_os_unit_x;
}

/*************************************************/
/* convert_box_to_os()                           */
/*                                               */
/* As convert_pair_to_os, but converts the four  */
/* coordinates inside a BBox in one go.          */
/*                                               */
/* Parameters: Pointer to a BBox containing      */
/*             coords in millipoints;            */
/*                                               */
/*             Pointer to a BBox into which the  */
/*             first box's coords, converted to  */
/*             OS units, are placed.             */
/*                                               */
/* Assumes:    That neither pointer is NULL. The */
/*             two pointers may be the same (so  */
/*             passing in &box, &box would work  */
/*             correctly).                       */
/*************************************************/

void convert_box_to_os(const BBox * mp, BBox * os)
{
  os->xmin = ((mp->xmin + half_mppou_x) / millipoints_per_os_unit_x) & ~(wimpt_dx() - 1);
  os->ymin = ((mp->ymin + half_mppou_y) / millipoints_per_os_unit_y) & ~(wimpt_dy() - 1);
  os->xmax = ((mp->xmax + half_mppou_x) / millipoints_per_os_unit_x) & ~(wimpt_dx() - 1);
  os->ymax = ((mp->ymax + half_mppou_y) / millipoints_per_os_unit_y) & ~(wimpt_dy() - 1);
}

/*************************************************/
/* convert_box_to_points()                       */
/*                                               */
/* As convert_pair_to_points, but converts the   */
/* four coordinates inside a BBox in one go.     */
/*                                               */
/* Parameters: Pointer to a BBox containing      */
/*             coords in OS units;               */
/*                                               */
/*             Pointer to a BBox into which the  */
/*             first box's coords, converted to  */
/*             millipoints, are placed.          */
/*                                               */
/* Assumes:    That neither pointer is NULL. The */
/*             two pointers may be the same (so  */
/*             passing in &box, &box would work  */
/*             correctly).                       */
/*************************************************/

void convert_box_to_points(const BBox * os, BBox * mp)
{
  #ifdef TRACE

    if (
         abs(os->xmin) > overflow_limit_x ||
         abs(os->ymin) > overflow_limit_y ||
         abs(os->xmax) > overflow_limit_x ||
         abs(os->ymax) > overflow_limit_y
       )
    {
      erb.errnum = Utils_Error_Custom_Normal;
      sprintf(erb.errmess,
              "convert_box_to_points: Can't convert (%d, %d, %d, %d) to millipoints without overflow.",
              os->xmin,
              os->ymin,
              os->xmax,
              os->ymax);

      show_error_ret(&erb);

      mp->xmin = mp->ymin = 0;
      mp->xmax = mp->ymax = 0;

      return;
    }

  #endif

  mp->xmin = os->xmin * millipoints_per_os_unit_x;
  mp->ymin = os->ymin * millipoints_per_os_unit_y;
  mp->xmax = os->xmax * millipoints_per_os_unit_x;
  mp->ymax = os->ymax * millipoints_per_os_unit_y;
}

/*************************************************/
/* read_sprite_size()                            */
/*                                               */
/* Finds out the size of a given sprite in the   */
/* application's sprite pool in OS units.        */
/*                                               */
/* Parameters: Pointer to the sprite name;       */
/*                                               */
/*             Pointer to int into which the     */
/*             sprite's width is returned;       */
/*                                               */
/*             Pointer to int into which the     */
/*             sprite's height is returned.      */
/*                                               */
/* Assumes:    The name pointer is not NULL, but */
/*             either of the two int pointers    */
/*             may be.                           */
/*************************************************/

_kernel_oserror * read_sprite_size(char * name, int * width, int * height)
{
  int               w, h, m;
  _kernel_oserror * e;

  e = _swix(OS_SpriteOp,
            _INR(0,2) | _OUTR(3,4) | _OUT(6),

            0x128,
            sprite_block,
            name,

            &w,
            &h,
            &m);

  if (e) return e;

  w = w << bbc_modevar(m, BBC_XEigFactor);
  h = h << bbc_modevar(m, BBC_YEigFactor);

  if (width)  *width  = w;
  if (height) *height = h;

  return NULL;
}

/*************************************************/
/* utils_text_width()                            */
/*                                               */
/* Returns the width of a given piece of text,   */
/* in OS units, if it were to be plotted in the  */
/* Desktop. Wimp_TextOp is used if available,    */
/* else the width and spacing of the bitmap font */
/* is read and the width is calculated from      */
/* this instead.                                 */
/*                                               */
/* Parameters: Pointer to the text;              */
/*                                               */
/*             Pointer to an int, into which the */
/*             width is written;                 */
/*                                               */
/*             0 to work out the whole string    */
/*             width, or the number of chars to  */
/*             read.                             */
/*                                               */
/* Assumes:    Either pointer may be NULL;       */
/*                                               */
/*             If the number of chars to read is */
/*             greater than the string length,   */
/*             the value given is ignored and    */
/*             the string length used instead.   */
/*************************************************/

_kernel_oserror * utils_text_width(char * text, int * width, int scan)
{
  int cwidth, cspacing;
  int len;

  /* Return if there's no text or 'width' is NULL */

  if (!width) return NULL;

  if (!text || !*text)
  {
    *width = 0;
    return NULL;
  }

  /* Otherwise, set 'len' either to the string length, */
  /* if 'scan' is zero, or to the value of 'scan'.     */

  len = strlen(text);
  if (scan && scan < len) len = scan;

  /* Rather than try mucking about guessing what version number of */
  /* Wimp supports Wimp_TextOp, simply use the alternative method  */
  /* if the SWI raises an error.                                   */

  if (
       _swix(Wimp_TextOp,
             _INR(0,2) | _OUT(0),

             1,
             text,
             len,

             width)
     )
  {
    /* Find out the spacing (start of one char to start of next) */
    /* and width of the text the Wimp is using, assuming that if */
    /* there is no nested wimp, Wimp_TextOp is unavailable.      */

    int vars[3] = {
                    BBC_GCharSizeX,
                    BBC_GCharSpaceX,
                    -1
                  };

    RetError(bbc_vduvars(vars, vars));

    cwidth   = vars[0];
    cspacing = vars[1];

    /* cspacing gives how much to increment x by after plotting a   */
    /* character, and therefore includes cwidth; so to find the     */
    /* width, we'd use (len * cspacing) - (cspacing - cwidth),      */
    /* which simplifies to the below (plus conversion to OS units). */

    *width = ((len - 1) * cspacing + cwidth) * wimpt_dx();
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* set_gadget_state()                            */
/*                                               */
/* Greys or ungreys a gadget, only changing its  */
/* state to avoid flicker.                       */
/*                                               */
/* Parameters: Object ID the gadget resides in;  */
/*                                               */
/*             Component ID of the gadget;       */
/*                                               */
/*             1 to grey, 0 to ungrey.           */
/*************************************************/

_kernel_oserror * set_gadget_state(ObjectId o, ComponentId c, int grey_state)
{
  _kernel_oserror * e;
  unsigned int      flags;

  e = gadget_get_flags(0, o, c, &flags);
  if (e) return e;

  /* Only change state, to avoid flicker. */

  if (!!grey_state != !!(flags & Gadget_Faded))
  {
    if (grey_state) flags |=  Gadget_Faded;
    else            flags &= ~Gadget_Faded;

    return gadget_set_flags(0, o, c, flags);
  }

  return NULL;
}

/*************************************************/
/* anti_twitter()                                */
/*                                               */
/* Calls the anti-twitter code over a given      */
/* redraw area.                                  */
/*                                               */
/* Parameters: Pointer to a WimpRedrawWindow     */
/*             block with the redraw_area BBox   */
/*             holding the area over which the   */
/*             anti-twitter code should be       */
/*             called.                           */
/*************************************************/

void anti_twitter(WimpRedrawWindowBlock * r)
{
  char         nhantitwitter[256];
  int          mode, ok = 1;
  unsigned int modeflags;

  #define AntiTwitter1 50
  #define AntiTwitter2 55

  #ifndef TRACE

    /* Older interlace modules only support modes 50 and 55 */
    /* directly, though can still appear to work in others. */

    _swix(OS_Byte,
          _IN(0) | _OUT(2),

          135,

          &mode);

    if (mode == AntiTwitter1 || mode == AntiTwitter2) ok = 1;
    else
    {
      /* If the current mode is not mode 50 or 55, this mode module  */
      /* may be new enough to support setting bit 8 of the modeflags */
      /* to indicate interlace.                                      */

      _swix(OS_ReadModeVariable,
            _INR(0,1) | _OUT(2),

            -1,
            0,

            &modeflags);

      ok = !!(modeflags & (1<<8));
    }

  #else

    /* For trace builds, always try to anti-twitter (allows testing */
    /* in certain non-interlaced Desktop screen modes)              */

    ok = 1;

    /* Hmph - stop compiler complaining about things not being used... */

    mode      = 0;
    modeflags = 0;

  #endif

  if (ok)
  {
    BBox   gwind;
    BBox * area;

    gwind.xmin = (bbc_vduvar(BBC_GWLCol)) * wimpt_dx();
    gwind.ymin = (bbc_vduvar(BBC_GWBRow)) * wimpt_dy();
    gwind.xmax = (bbc_vduvar(BBC_GWRCol) + 1) * wimpt_dx();
    gwind.ymax = (bbc_vduvar(BBC_GWTRow) + 1) * wimpt_dy();

    area = intersection(&gwind, &r->redraw_area);

    if (area)
    {
      sprintf(nhantitwitter,
              "%%NHAntiTwitter %d %d %d %d\n",

              area->xmin,
              area->ymin,
              area->xmax - area->xmin,
              area->ymax - area->ymin);

      _swix(OS_CLI,
            _IN(0),

            nhantitwitter);
    }
  }
}

/*************************************************/
/* adjust()                                      */
/*                                               */
/* Returns 1 if Wimp_GetPointerInfo says that    */
/* Adjust is being pressed, else 0.              */
/*************************************************/

int adjust(void)
{
  WimpGetPointerInfoBlock info;

  wimp_get_pointer_info(&info);

  return !!(info.button_state & Wimp_MouseButtonAdjust);
}

/*************************************************/
/* hide_gadget()                                 */
/*                                               */
/* Hides a given gadget by moving it out of the  */
/* visible area of the window it is in.          */
/*                                               */
/* Parameters: Object ID the gadget lies in;     */
/*             Component ID of the gadget.       */
/*                                               */
/* Returns:    1 if the gadget was moved out,    */
/*             else 0.                           */
/*************************************************/

int hide_gadget(ObjectId o, ComponentId c)
{
  BBox g;

  if (gadget_get_bbox(0, o, c, &g)) return 0;

  /* If the gadget has a large negative X coordinate, */
  /* assume it's been moved out already.              */

  if (g.xmin < -4096) return 0;

  /* Otherwise, move it */

  g.xmin -= 8192;
  g.xmax -= 8192;

  if (gadget_move_gadget(0, o, c, &g)) return 0;

  return 1;
}

/*************************************************/
/* show_gadget()                                 */
/*                                               */
/* Shows a given gadget hidden by hide_gadget.   */
/*                                               */
/* Parameters: Object ID the gadget lies in;     */
/*             Component ID of the gadget.       */
/*                                               */
/* Returns:    1 if the gadget was moved in,     */
/*             else 0.                           */
/*************************************************/

int show_gadget(ObjectId o, ComponentId c)
{
  BBox g;

  if (gadget_get_bbox(0, o, c, &g)) return 0;

  /* If the gadget hasn't got a large negative X coordinate, */
  /* assume it's not been moved out.                         */

  if (g.xmin > -4096) return 0;

  /* Otherwise, move it */

  g.xmin += 8192;
  g.xmax += 8192;

  if (gadget_move_gadget(0, o, c, &g)) return 0;

  return 1;
}

/*************************************************/
/* gadget_hidden()                               */
/*                                               */
/* Call to find out if a gadget has been moved   */
/* out with hide_gadget or is still visible.     */
/*                                               */
/* Parameters: Object ID the gadget lies in;     */
/*             Component ID of the gadget.       */
/*                                               */
/* Returns:    1 if the gadget is hidden else 0. */
/*************************************************/

int gadget_hidden(ObjectId o, ComponentId c)
{
  BBox g;

  /* If there's an error getting the gadget's bounding box, then the gadget */
  /* is missing altogether or something else has gone wrong; in any case,   */
  /* safest action is to say it has been hidden.                            */

  if (gadget_get_bbox(0, o, c, &g)) return 1;

  /* Simple assumption that this much of a negative X value = gadget hidden */

  if (g.xmin < -4096) return 1;

  return 0;
}

/*************************************************/
/* slab_gadget()                                 */
/*                                               */
/* Slabs a gadget in briefly, by setting its     */
/* Selected bit. Gadget must be made of one icon */
/* only.                                         */
/*                                               */
/* Parameters: Object ID the gadget lies in;     */
/*             Component ID of the gadget.       */
/*************************************************/

void slab_gadget(ObjectId o, ComponentId c)
{
  WimpSetIconStateBlock set;
  WimpGetIconStateBlock get;
  int                   icon[2];

  /* Get the icon number and window handle of the gadget */

  if (gadget_get_icon_list(0, o, c, icon, sizeof(icon), NULL)) return;
  if (window_get_wimp_handle(0, o, &get.window_handle)) return;
  get.icon_handle = icon[0];

  /* Get the icon state */

  if (wimp_get_icon_state(&get)) return;

  /* Set the flags as selected */

  set.window_handle = get.window_handle;
  set.icon_handle   = get.icon_handle;
  set.EOR_word      = get.icon.flags | WimpIcon_Selected;
  set.clear_word    = 0xffffffff;

  if (wimp_set_icon_state(&set)) return;

  /* Wait a while */

  {
    int time_now, time_start;

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

    do
    {
      _swix(OS_ReadMonotonicTime, _OUT(0), &time_now);
    }
    while (time_now - time_start < 15);
  }

  /* Restore the old flags */

  set.EOR_word = get.icon.flags;

  wimp_set_icon_state(&set);
}

/*************************************************/
/* slab_gadget_in()                              */
/*                                               */
/* Slabs a gadget in, by setting its Selected    */
/* bit. Gadget must be made of one icon only.    */
/*                                               */
/* Parameters: Object ID the gadget lies in;     */
/*             Component ID of the gadget.       */
/*************************************************/

void slab_gadget_in(ObjectId o, ComponentId c)
{
  WimpSetIconStateBlock set;
  WimpGetIconStateBlock get;
  int                   icon[2];

  /* Get the icon number and window handle of the gadget */

  if (gadget_get_icon_list(0, o, c, icon, sizeof(icon), NULL)) return;
  if (window_get_wimp_handle(0, o, &get.window_handle)) return;
  get.icon_handle = icon[0];

  /* Get the icon state */

  if (wimp_get_icon_state(&get)) return;

  /* Set the flags as selected */

  set.window_handle = get.window_handle;
  set.icon_handle   = get.icon_handle;
  set.EOR_word      = get.icon.flags | WimpIcon_Selected;
  set.clear_word    = 0xffffffff;

  wimp_set_icon_state(&set);
}

/*************************************************/
/* slab_gadget_out()                             */
/*                                               */
/* Slabs a gadget out, by clearing its Selected  */
/* bit. Gadget must be made of one icon only.    */
/*                                               */
/* Parameters: Object ID the gadget lies in;     */
/*             Component ID of the gadget.       */
/*************************************************/

void slab_gadget_out(ObjectId o, ComponentId c)
{
  WimpSetIconStateBlock set;
  WimpGetIconStateBlock get;
  int                   icon[2];

  /* Get the icon number and window handle of the gadget */

  if (gadget_get_icon_list(0, o, c, icon, sizeof(icon), NULL)) return;
  if (window_get_wimp_handle(0, o, &get.window_handle)) return;
  get.icon_handle = icon[0];

  /* Get the icon state */

  if (wimp_get_icon_state(&get)) return;

  /* Set the flags as unselected */

  set.window_handle = get.window_handle;
  set.icon_handle   = get.icon_handle;
  set.EOR_word      = get.icon.flags &~ WimpIcon_Selected;
  set.clear_word    = 0xffffffff;

  wimp_set_icon_state(&set);
}

/*************************************************/
/* utils_check_caret_restoration()               */
/*                                               */
/* Checks to see if the given dialogue has the   */
/* caret, and if it has a parent. If so, it'll   */
/* return the Object ID of that parent, else     */
/* NULL_ObjectId is written.                     */
/*                                               */
/* Parameters: The Object ID of the dialogue     */
/*             to check.                         */
/*************************************************/

ObjectId utils_check_caret_restoration(ObjectId window_id)
{
  WimpGetCaretPositionBlock   caret_b;
  int                         caret_w;
  ObjectId                    parent = NULL_ObjectId;
  _kernel_oserror           * e      = NULL;

  /* Do we have the input focus? */

  e = wimp_get_caret_position(&caret_b);

  if (!e)
  {
    e = window_get_wimp_handle(0,
                               window_id,
                               &caret_w);

    if (!e)
    {
      if (caret_w == caret_b.window_handle)
      {
        /* Yes, we have the caret. So move it back to the Print */
        /* dialogue - well, this object's parent, anyway.       */

        e = toolbox_get_parent(0,
                               window_id,
                               &parent,
                               NULL);

        if (e) parent = NULL_ObjectId;

        else if (parent == NULL_ObjectId)
        {
          /* Maybe there's an ancestor? (E.g. PrintDBox using an  */
          /* alternate window -> doesn't pass Parent info through */
          /* so we need the ancestor instead... sigh).            */

          e = toolbox_get_ancestor(0,
                                   window_id,
                                   &parent,
                                   NULL);

          if (e) parent = NULL_ObjectId;
        }
      }
    }
  }

  /* Finished */

  return parent;
}

/*************************************************/
/* utils_restore_caret()                         */
/*                                               */
/* If the given dialogue has the caret, put the  */
/* caret into the parent of this object, in the  */
/* default input focus position.                 */
/*                                               */
/* Parameters: The Object ID of the dialogue     */
/*             whos parent is to gain the caret. */
/*                                               */
/* Assumes:    The parent has a default caret    */
/*             position set up.                  */
/*************************************************/

_kernel_oserror * utils_restore_caret(ObjectId window_id)
{
  _kernel_oserror           * e = NULL;
  ObjectId                    parent;
  ComponentId                 focus_c;

  /* Do we have the input focus and a parent? */

  parent = utils_check_caret_restoration(window_id);

  if (parent != NULL_ObjectId)
  {
    /* Find the default caret position of the parent */

    e = window_get_default_focus(0,
                                 parent,
                                 &focus_c);

    /* Set the focus there */

    if (!e)
    {

      if (focus_c != NULL_ComponentId) gadget_set_focus(0,
                                                        parent,
                                                        focus_c);
    }
  }

  /* Finished */

  return e;
}

/*************************************************/
/* copy_toolaction_info()                        */
/*                                               */
/* Copies the internal details of a given        */
/* ToolAction gadget to another.                 */
/*                                               */
/* Ident off, ident on, ident faded, select      */
/* action, adjust action and click-show details  */
/* are copied with ToolAction SWIs; the help     */
/* text is copied with gadget library calls.     */
/*                                               */
/* This does *not* copy state info, such as      */
/* on/off or greyed.                             */
/*                                               */
/* Parameters: Object ID the source gadget is    */
/*             in;                               */
/*                                               */
/*             Component ID of the source;       */
/*                                               */
/*             Object ID the destination gadget  */
/*             is in;                            */
/*                                               */
/*             Component ID of the destination.  */
/*************************************************/

_kernel_oserror * copy_toolaction_info(ObjectId src_o, ComponentId src_c, ObjectId dst_o, ComponentId dst_c)
{
  _kernel_oserror * e;
  char              ident [Limits_ToolActionIdent];
  char              help  [Limits_Help];
  unsigned int      flags;
  int               adjust_act, select_act;
  int               adjust_cs,  select_cs;

  /* Off state ident string */

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,5),

            toolaction_SET_IDENT_OFF,
            src_o,
            ToolAction_GetIdent,
            src_c,
            ident,
            sizeof(ident));

  if (e) return e;

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,4),

            toolaction_SET_IDENT_OFF,
            dst_o,
            ToolAction_SetIdent,
            dst_c,
            ident);

  if (e) return e;

  /* On state ident string */

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,5),

            toolaction_SET_IDENT_ON,
            src_o,
            ToolAction_GetIdent,
            src_c,
            ident,
            sizeof(ident));

  if (e) return e;

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,4),

            toolaction_SET_IDENT_ON,
            dst_o,
            ToolAction_SetIdent,
            dst_c,
            ident);

  if (e) return e;

  /* Faded state ident string */

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,5),

            toolaction_SET_IDENT_FADE,
            src_o,
            ToolAction_GetIdent,
            src_c,
            ident,
            sizeof(ident));

  if (e) return e;

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,4),

            toolaction_SET_IDENT_FADE,
            dst_o,
            ToolAction_SetIdent,
            dst_c,
            ident);

  if (e) return e;

  /* Adjust and select actions */

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,3) | _OUTR(0,1),

            0,
            src_o,
            ToolAction_GetAction,
            src_c,

            &select_act,
            &adjust_act);

  if (e) return e;

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,5),

            0,
            dst_o,
            ToolAction_SetAction,
            dst_c,
            select_act,
            adjust_act);

  if (e) return e;

  /* Adjust and select click-shows */

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,3) | _OUTR(0,1),

            0,
            src_o,
            ToolAction_GetClickShow,
            src_c,

            &select_cs,
            &adjust_cs);

  if (e) return e;

  e = _swix(Toolbox_ObjectMiscOp,
            _INR(0,5),

            0,
            dst_o,
            ToolAction_SetClickShow,
            dst_c,
            select_cs,
            adjust_cs);

  if (e) return e;

  /* The gadget flags */

  e = gadget_get_flags(0, src_o, src_c, &flags);
  if (e) return e;

  e = gadget_set_flags(flags, dst_o, dst_c, flags);
  if (e) return e;

  /* Finally, the help text */

  e = gadget_get_help_message(0,
                              src_o,
                              src_c,
                              help,
                              sizeof(help),
                              NULL);

  if (e) return e;

  return gadget_set_help_message(0,
                                 dst_o,
                                 dst_c,
                                 help);
}

/*************************************************/
/* set_window_flags()                            */
/*                                               */
/* Sets the flags of a given window, assuming    */
/* the nested Wimp is available...               */
/*                                               */
/* Parameters: Window handle;                    */
/*             EOR word;                         */
/*             Clear word.                       */
/*                                               */
/* Assumes:    That a window manager that        */
/*             supports extended Wimp_OpenWindow */
/*             calls (R2 = 'TASK') is present.   */
/*                                               */
/* The flags are set according to                */
/*                                               */
/* new = (old BIC clear word) EOR EOR word       */
/*                                               */
/* i.e.:                                         */
/*                                               */
/* C  E  Effect                                  */
/* ------------                                  */
/* 0  0  Preserve bit                            */
/* 0  1  Toggle bit                              */
/* 1  0  Clear bit                               */
/* 1  1  Set bit                                 */
/*************************************************/

_kernel_oserror * set_window_flags(int window_handle, unsigned int clear_word, unsigned int eor_word)
{
  /* Block required for the extended Wimp_OpenWindow */

  typedef struct
  {
    WimpOpenWindowBlock open;
    unsigned int        flags;
  }
  ExtendedOpenBlock;

  _kernel_oserror         * e;
  WimpGetWindowStateBlock   s;
  unsigned int              parent, align;
  unsigned int              new_flags;
  ExtendedOpenBlock         ext_o;

  /* Get the current window details */

  s.window_handle = window_handle;

  e = _swix(Wimp_GetWindowState,
            _INR(1, 2) | _OUTR(3, 4),

            &s,
            Magic_Word_TASK, /* See MiscDefs.h */

            &parent,
            &align);

  if (e) return e;

  /* Obtain the new flags word */

  new_flags = (s.flags & ~clear_word) ^ eor_word;

  /* Fill in the new open block and reopen the window with it */

  ext_o.open.window_handle = s.window_handle;
  ext_o.open.visible_area  = s.visible_area;
  ext_o.open.xscroll       = s.xscroll;
  ext_o.open.yscroll       = s.yscroll;
  ext_o.open.behind        = s.behind;
  ext_o.flags              = new_flags;

  return _swix(Wimp_OpenWindow,
               _INR(1,4),

               &ext_o,
               Magic_Word_TASK,
               parent,
               align | Alignment_NewFlagsGiven);
}

/*************************************************/
/* debounce_keypress()                           */
/*                                               */
/* For some key presses (e.g. function keys), it */
/* is not desirable to let the key autorepeat.   */
/* This function sits in a tight loop waiting    */
/* for all keys to be released before exitting.  */
/*                                               */
/* Returns: 1 if a key was being pressed and the */
/*          function waited for its release,     */
/*          else 0.                              */
/*************************************************/

int debounce_keypress(void)
{
  int               key, waited = 0;
  _kernel_oserror * e;

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

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

              &key);

    if (key != 255) waited = 1;
  }
  while (!e && key != 255);

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

  return waited;
}

/*************************************************/
/* task_from_window()                            */
/*                                               */
/* Returns the task handle of the owner of a     */
/* given window.                                 */
/*                                               */
/* Parameters: A window handle.                  */
/*                                               */
/* Returns:    Task handle of the window owner.  */
/*************************************************/

int task_from_window (int window_handle)
{
  WimpMessage  m;
  int          handle;

  m.hdr.size        = 20;
  m.hdr.your_ref    = 0;
  m.hdr.action_code = 0;

  if (
       wimp_send_message(Wimp_EUserMessageAcknowledge,
                         &m,
                         window_handle,
                         0,
                         &handle)
     )
     return 0;

  return handle;
}

/*************************************************/
/* utils_browser_from_window()                   */
/*                                               */
/* Given a window handle, returns an associated  */
/* browser_data struct, if one may be found.     */
/*                                               */
/* Parameters: The window handle;                */
/*                                               */
/*             Pointer to a pointer to a         */
/*             browser_data struct, in which the */
/*             associated structure's address is */
/*             written, or NULL for none found / */
/*             there is an error.                */
/*                                               */
/* Assumes:    The last parameter may not be     */
/*             NULL (it is pointless to allow    */
/*             this).                            */
/*************************************************/

_kernel_oserror * utils_browser_from_window(int window_handle, browser_data ** browser)
{
  ObjectId o = NULL, a = NULL;

  *browser = NULL;

  /* Get the associated Object ID */

  RetError(window_wimp_to_toolbox(0,
                                  window_handle,
                                  -1,
                                  &o,
                                  NULL));

  /* If this has an ancestor, check the ID against the ancestor's */
  /* toolbar IDs. This is because we could be a frame - in which  */
  /* case there is an ancestor - or a toolbar, in which case we   */
  /* want to return the browser_data struct for that ancestor.    */

  RetError(toolbox_get_ancestor(0, o, &a, NULL));

  if (a)
  {
    browser_data * ancestor = NULL;
    ObjectId       upper, lower;

    /* Get the ancestor's client handle */

    RetError(toolbox_get_client_handle(0, a, (void *) &ancestor));

    /* If a known browser, get the toolbars */

    if (is_known_browser(ancestor))
    {
      upper = toolbars_get_upper(ancestor);
      lower = toolbars_get_lower(ancestor);

      /* If either ID matches that which the message */
      /* came from, the return the ancestor ID.      */

      if (upper == o || lower == o)
      {
        *browser = ancestor;

        return NULL;
      }
    }
  }

  /* Get its client handle */

  RetError(toolbox_get_client_handle(0, o, (void *) browser));

  /* If this is not a known browser, don't use it */

  if (!is_known_browser(*browser)) *browser = NULL;

  /* Finished */

  return NULL;
}

/*************************************************/
/* is_known_browser()                            */
/*                                               */
/* Finds out if the given browser_data struct is */
/* in the list of known structures. The struct   */
/* may be NULL (0 would be returned), so this    */
/* doesn't need to be checked for externally.    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             to check.                         */
/*                                               */
/* Returns:    1 if the structure is in the list */
/*             or 0 if it isn't.                 */
/*************************************************/

int is_known_browser(browser_data * b)
{
  browser_data * check = last_browser;
  int            found = 0;

  /* If b is NULL, can't be a valid browser_data structure! */

  if (!b) return 0;

  /* Search the list */

  while (check && !found)
  {
    if (check == b) found = 1;
    else check = check->previous;
  }

  /* Return the search results */

  return found;
}

/*************************************************/
/* utils_parent()                                */
/*                                               */
/* Works out a given browser_data structure's    */
/* parent. If there is only an ancestor or       */
/* neither an ancestor or parent, it returns     */
/* NULL.                                         */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that you want the parent of.      */
/*                                               */
/* Returns:    Pointer to the structure's        */
/*             parent, or NULL if there is only  */
/*             an ancestor or no parents at all. */
/*                                               */
/* Assumes:    If it gets NULL, it returns NULL. */
/*************************************************/

browser_data * utils_parent(browser_data * b)
{
  if (!b) return NULL;

  if      (b->parent      && b->parent      != b->ancestor) return b->parent;
  else if (b->real_parent && b->real_parent != b->ancestor) return b->real_parent;

  return NULL;
}

/*************************************************/
/* utils_ancestor()                              */
/*                                               */
/* Works out a given browser_data structure's    */
/* ancestor (which may be the given structure!). */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             that you want the ancestor of.    */
/*                                               */
/* Returns:    Pointer to the structure's        */
/*             ancestor.                         */
/*                                               */
/* Assumes:    If it gets NULL, it returns NULL. */
/*************************************************/

browser_data * utils_ancestor(browser_data * b)
{
  if (!b) return NULL;

  if (b->ancestor) return b->ancestor;
  return b;
}

/*************************************************/
/* encode_base64()                               */
/*                                               */
/* Passed a pointer to data and its length, this */
/* will fill the output buffer with Base64-      */
/* encoded data, returning the length of the     */
/* output data (this is not terminated).         */
/*                                               */
/* The length of the output will be -            */
/*                                               */
/* (length of input, rounded up to next multiple */
/* of 3) times 4                                 */
/*                                               */
/* - so make sure you have a big enough output   */
/* buffer.                                       */
/*                                               */
/* Parameters: Pointer to the data to encode;    */
/*                                               */
/*             Length of the data to encode;     */
/*                                               */
/*             Pointer to the output buffer.     */
/*                                               */
/* Assumes:    That the output buffer is big     */
/*             enough (see above).               */
/*************************************************/

int encode_base64(const char * in, int len, char * out)
{
  const static char * base64_table = "ABCDEFGHIJKLMNOP"
                                     "QRSTUVWXYZabcdef"
                                     "ghijklmnopqrstuv"
                                     "wxyz0123456789+/";
  unsigned long       chunk;
  char              * out_ptr      = out;
  int                 p            = 0;
  int                 i;

  while (p < len)
  {
    chunk = 0;

    for (i = 0; i < 3; i++, p++)
    {
      if (p < len) chunk = (chunk << 8) | in[p];
      else         chunk = (chunk << 8);
    }

    *out++ = base64_table[chunk >> 18];
    *out++ = base64_table[(chunk >> 12) & 0x3f];

    if (p <= len + 1)
    {
      *out++ = base64_table[(chunk >> 6) & 0x3f];

      if (p <= len) *out++ = base64_table[chunk & 0x3f];
      else          *out++ = '=';
    }
    else
    {
      *out++ = '=';
      *out++ = '=';
    }
  }

  return out - out_ptr;
}

/*************************************************/
/* utils_strcasecmp()                            */
/*                                               */
/* Function to compare two strings case          */
/* insensitively.                                */
/*                                               */
/* Originally by sbrodie.                        */
/*                                               */
/* The conversions to unsigned int stop the      */
/* compiler messing around with shifts all over  */
/* the place whilst trying to promote the chars  */
/* to int whilst retaining the sign.             */
/*                                               */
/* Problems: Choice of return value when strings */
/* do not match is based upon character number   */
/* rather than any alphabetic sorting.           */
/*                                               */
/* Parameters: As strcmp.                        */
/*                                               */
/* Returns:    As strcmp.                        */
/*************************************************/

int utils_strcasecmp(const char *a, const char *b)
{
  for (;;)
  {
    unsigned int f1 = *a++;
    unsigned int s1 = *b++;

    if (f1 == 0) return -s1;

    if (f1 != s1)
    {
      unsigned int f2     = (unsigned int) tolower(f1);
      unsigned int s2     = (unsigned int) tolower(s1);
      signed int   result = f2 - s2;

      if (result != 0) return result;
    }
  }
}

/*************************************************/
/* utils_strncasecmp()                           */
/*                                               */
/* Function to compare two strings case          */
/* insensitively up to a maximum char count.     */
/*                                               */
/* Originally by sbrodie.                        */
/*                                               */
/* The conversions to unsigned int stop the      */
/* compiler messing around with shifts all over  */
/* the place whilst trying to promote the chars  */
/* to int whilst retaining the sign.             */
/*                                               */
/* Problems: Choice of return value when strings */
/* do not match is based upon character number   */
/* rather than any alphabetic sorting.           */
/*                                               */
/* Parameters: As strncmp.                       */
/*                                               */
/* Returns:    As strncmp.                       */
/*************************************************/

int utils_strncasecmp(const char * a, const char * b, unsigned int n)
{
  for (; n; --n)
  {
    unsigned int f1 = *a++;
    unsigned int s1 = *b++;

    if (f1 == 0) return -s1;

    if (f1 != s1)
    {
      unsigned int f2     = (unsigned int) tolower(f1);
      unsigned int s2     = (unsigned int) tolower(s1);
      signed int   result = f2 - s2;

      if (result != 0) return result;
    }
  }

  return 0;
}

/*************************************************/
/* utils_get_task_handle()                       */
/*                                               */
/* Returns the task handle of the given task     */
/* (name comparison is case insensitive).        */
/*                                               */
/* Parameters: Pointer to a null-terminated task */
/*             name;                             */
/*                                               */
/*             Pointer to an unsigned int, in    */
/*             which the task handle is written, */
/*             or 0 if the task is not found.    */
/*                                               */
/* Assumes:    Neither pointer may be NULL.      */
/*************************************************/

_kernel_oserror * utils_get_task_handle(const char * task_to_get, unsigned int * found_handle)
{
  _kernel_oserror * e;
  char            * c;
  int             * p;
  int               buffer  [32];
  char              taskname[Limits_TaskName];
  int             * notused;
  int               t;
  int               len    = strlen(task_to_get);
  int               next   = 0;
  unsigned int      handle = 0;

  do
  {
    e = _swix(TaskManager_EnumerateTasks,
              _INR(0,2) | _OUTR(0,1),

              next,
              buffer,
              sizeof(buffer),

              &next,
              &notused);

    if (e) return e;

    /* Go through as much of the buffer as the call said it used */

    for (p = buffer; p < notused && handle == 0; p += 4)
    {
      c = (char *) p[1];
      t = 0;

      memset(taskname, 0, sizeof(taskname));
      while (*c > 31 && t < sizeof(taskname) - 1) taskname[t++] = *c++;

      if (!utils_strncasecmp(taskname, task_to_get, len)) handle = p[0];
    }
  }
  while (next >= 0 && handle == 0);

  /* Return the handle */

  *found_handle = handle;

  return NULL;
}

/*************************************************/
/* utils_stop_proxy()                            */
/*                                               */
/* Stops the [WebServe] application by sending   */
/* an AppControl message.                        */
/*************************************************/

_kernel_oserror * utils_stop_proxy(void)
{
  unsigned int handle = 0;
  WimpMessage  msg;

  /* First, get WebServe's task handle */

  RetError(utils_get_task_handle(lookup_token("ProxyName:Acorn WebServe",0,0), &handle));

  #ifdef TRACE

    if (!handle)
    {
      /* Didn't find WebServe, so complain and exit */

      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess, "WebServe is not present");

      show_error_ret(&erb);

      return NULL;
    }

  #else

    if (!handle)
    {
      /* Didn't find WebServe, so exit */

      return NULL;
    }

  #endif

  /* If WebServe is present, send the message */

  msg.hdr.size        = 32;
  msg.hdr.your_ref    = 0;
  msg.hdr.action_code = Wimp_MAppControl;

  msg.data.app_control.version = 1;
  msg.data.app_control.flags   = Wimp_MAppControl_ImmediateAction;
  msg.data.app_control.reason  = Wimp_MAppControl_Stop;

  return wimp_send_message(Wimp_EUserMessage, &msg, handle, -1, NULL);
}

/*************************************************/
/* utils_len_printf()                            */
/*                                               */
/* Returns the length of a given printf string.  */
/* Can be useful for buffer length determination */
/* when wishing to use sprintf, for example.     */
/*                                               */
/* This function runs *very* slowly. If it is    */
/* difficult / impossible to determine the       */
/* length (or upper limit of length) any other   */
/* way, use it; else find a different approach   */
/* (e.g. see utils_number_length).               */
/*                                               */
/* Parameters: As for printf.                    */
/*                                               */
/* Returns:    Length of the output string (as   */
/*             returned by vfprintf) or -1 if    */
/*             some error occurred.              */
/*************************************************/

int utils_len_printf(const char * format, ...)
{
  int       len;
  va_list   ptr;
  FILE    * fp;

  fp = fopen("Null:", "wb");
  if (!fp) return -1;

  va_start(ptr, format);
  len = vfprintf(fp, format, ptr);
  va_end(ptr);

  fclose(fp);

  return len;
}

/*************************************************/
/* utils_number_length()                         */
/*                                               */
/* Returns the number of bytes the given number  */
/* would occupy if converted to a string by      */
/* sprintf with a '%d' specifier used in the     */
/* format string.                                */
/*                                               */
/* Parameters: The number to check.              */
/*                                               */
/* Returns:    The number of characters it would */
/*             occupy as a string.               */
/*                                               */
/* Assumes:    An 'int' is 32-bit signed.        */
/*************************************************/

int utils_number_length(int number)
{
  int len = 0;

  if (number < 0) len += 1, number = -number;

  if      (number < 10)         len += 1;
  else if (number < 100)        len += 2;
  else if (number < 1000)       len += 3;
  else if (number < 10000)      len += 4;
  else if (number < 100000)     len += 5;
  else if (number < 1000000)    len += 6;
  else if (number < 10000000)   len += 7;
  else if (number < 100000000)  len += 8;
  else if (number < 1000000000) len += 9;
  else                          len += 10;

  return len;
}

/*************************************************/
/* utils_return_hash()                           */
/*                                               */
/* Returns a very crude hash number for a given  */
/* string.                                       */
/*                                               */
/* Parameters: Pointer to the string.            */
/*                                               */
/* Returns:    A hash number.                    */
/*************************************************/

unsigned int utils_return_hash(const char * s)
{
  unsigned int h = 0;

  if (!s) return 0;

  while (*s) h += *s++;

  return h;
}

/*************************************************/
/* utils_build_user_agent_string                 */
/*                                               */
/* Works out an appropriate HTTP response User   */
/* Agent string.                                 */
/*                                               */
/* Parameters: Flag to indicate fake Netscape    */
/*             header required.                  */
/*                                               */
/*             Pointer to a buffer to place the  */
/*             string in;                        */
/*                                               */
/*             Size of the buffer - note that a  */
/*             string longer than                */
/*             Limits_UserAgent will never be    */
/*             generated.                        */
/*************************************************/

void utils_build_user_agent_string(int netscape, char * buffer, int buffer_size)
{
  _kernel_oserror * e;
  char              agent[Limits_UserAgent];
  char              ver  [32];
  int               modnum, bcdver = 0;
  const char      * agent_middle, * agent_end;

  if (!buffer || !buffer_size) return;

  buffer[0] = '\0';

  /* The AGENT_... definitions are at the top of ths file.     */
  /*                                                           */
  /* First, the string start, e.g. 'Mozilla/2.0 (compatible; ' */

  StrNCpy0(agent, netscape ? Netscape_Agent_Start : Acorn_Agent_Start);

  /* The browser name */

  *lasttokn = 0; /* Being very (maybe over?) cautions here */
  *tokens   = 0;
  lookup_token("_TaskName",1,0);

  /* Can't have spaces outside the comment field */

  if (!strchr(agent, '('))
  {
    while (strchr(tokens, ' ')) *strchr(tokens, ' ') = '-';
  }

  if (strlen(tokens) + strlen(agent) + 2 < sizeof(agent))
  {
    strcat(agent, tokens);

    /* Separate version by a / outside the comment field (see HTTP 1.1 spec section 3.8) */

    if (strchr(agent, '(')) strcat(agent, " ");
    else                    strcat(agent, "/");
  }

  /* The browser version */

  *lasttokn = 0;
  *tokens   = 0;
  lookup_token("Version",1,0);

  /* Just do a simple version outside of the comment field */

  if (!strchr(agent, '('))
  {
    if (strchr(tokens, ' ')) *strchr(tokens, ' ') = '\0';
  }

  /* Can't have nested ()s in the User Agent string */

  if (strchr(tokens, '(')) *strchr(tokens, '(') = '[';
  if (strchr(tokens, ')')) *strchr(tokens, ')') = ']';

  /* Append the version, if it'll fit */

  if (strlen(tokens) + strlen(agent) + 1 < sizeof(agent)) strcat(agent, tokens);

  /* Intermediate string */

  agent_middle = netscape ? Netscape_Agent_Middle : Acorn_Agent_Middle;
  if (strlen(agent) + strlen(agent_middle) + 1 < sizeof(agent)) strcat(agent, agent_middle);

  /* Extract the Utility Module version number */

  e = _swix(OS_Module,
            _INR(0,1) | _OUT(1),

            18,
            "UtilityModule",

            &modnum);

  if (e) return;

  e = _swix(OS_Module,
            _INR(0,2) | _OUT(6),

            20,
            modnum,
            -1,

            &bcdver);

  if (e) return;

  /* Write the version number and put it into the agent string */

  sprintf(ver, "%d.%02x", bcdver >> 16, (bcdver & 0xffff) >> 8);

  if (strlen(agent) + strlen(ver) + 1 < sizeof(agent)) strcat(agent, ver);

  /* Finish things off */

  agent_end = netscape ? Netscape_Agent_End : Acorn_Agent_End;
  if (strlen(agent) + strlen(agent_end) + 1 < sizeof(agent)) strcat(agent, agent_end);

  /* Copy into the given buffer */

  strncpy(buffer, agent, buffer_size - 1);
  buffer[buffer_size - 1] = 0;

  /* Finished. */

  return;
}

/*************************************************/
/* utils_check_scrap()                           */
/*                                               */
/* If Save_ScrapFile (which should represent a   */
/* system variable name, see Save.h) does not    */
/* exist, report an appropriate error and        */
/* return 1. Else return 0.                      */
/*                                               */
/* Returns: See above.                           */
/*************************************************/

int utils_check_scrap(void)
{
  int              len;
  _kernel_swi_regs r;

  r.r[0] = (int) Save_ScrapVar;
  r.r[1] = (int) NULL;
  r.r[2] = -1;
  r.r[3] = 0;
  r.r[4] = 4;

  /* _swix will not work correctly for this particular SWI if */
  /* requiring the returned R2 value. Something to do with    */
  /* the call relying on generating an error, but _swix spots */
  /* it and pulls out earlier than the call expects. Or some  */
  /* such thing...                                            */

  _kernel_swi(OS_ReadVarVal, &r, &r);

  len = r.r[2];

  if (!len)
  {
    erb.errnum = Utils_Error_Custom_Message;
    sprintf(erb.errmess,
            "%s%s",
            Save_ScrapFile,
            lookup_token("NoScrapDef: not defined.",0,0));

    show_error_ret(&erb);

    return 1;
  }

  return 0;
}

/*************************************************/
/* utils_canonicalise_path()                     */
/*                                               */
/* Take some pathname (which may include         */
/* a path or other general system variable) and  */
/* expand (or canonicalise) it.                  */
/*                                               */
/* Caller is responsible for calling free() on   */
/* the returned block.                           */
/*                                               */
/* Parameters: Pointer to the path to            */
/*             canonicalise;                     */
/*                                               */
/*             Pointer to a char *, which will   */
/*             be filled in with the address of  */
/*             a malloced block - the caller is  */
/*             responsible for freeing it.       */
/*                                               */
/* Returns:    If there is an error, it returns  */
/*             it, but it may return NULL and    */
/*             also return NULL as the pointer   */
/*             to the malloced block if some     */
/*             other internal failure occurred.  */
/*************************************************/

_kernel_oserror * utils_canonicalise_path(const char * in, char ** out)
{
  int required;

  if (!in || !*in || !out) return NULL;

  RetError(_swix(OS_FSControl,
                 _INR(0, 5) | _OUT(5),

                 37,   in,
                 NULL, NULL,
                 NULL, 0,

                 &required)); /* Path length not including terminator returned as MINUS r5 */

  *out = malloc(1 - required); /* (Yes, '1 - required' - see above!) */

  if (!*out) return make_no_memory_error(30);

  RetError(_swix(OS_FSControl,
                 _INR(0, 5) | _OUT(5),

                 37,   in,
                 *out, NULL,
                 NULL, 1 - required,

                 &required));

  /* Er, 'something' went wrong... PRMs say to check, but not what to */
  /* do if you don't get 1 back here and haven't had an error from    */
  /* the SWI call!                                                    */

  if (required != 1)
  {
    free (*out);
    *out = NULL;
  }

  return NULL;
}

/*************************************************/
/* utils_build_tree()                            */
/*                                               */
/* Takes a fully canonicalised pathname and      */
/* ensures that all the directories in the path  */
/* exist. This is useful if you are going to     */
/* save something to a temporary directory in    */
/* Scrap or somewhere in <Choices$Write>, say,   */
/* and need to ensure that the directory         */
/* structure you're addressing is present.       */
/*                                               */
/* Parameters: Pointer to the path to ensure is  */
/*             present.                          */
/*************************************************/

_kernel_oserror * utils_build_tree(const char * path)
{
  char * temp;
  char * p;
  int    level, len;

  /* Sanity check, and take a local copy of the path */

  if (!path || !*path) return NULL;

  len = strlen(path);

  temp = malloc(len + 1);
  if (!temp) return make_no_memory_error(31);

retry:

  level = 0;
  strcpy(temp, path);

  /* Create the directories */

  do
  {
    p = strrchr(temp, '.');

    if (p)
    {
      *p = '\0';

      if (!_swix(OS_File,
                 _INR(0,1) | _IN(4),

                 8,
                 temp,
                 0))
      {
        if (level) goto retry;
        else       break;
      }
    }

    level++;
  }
  while (p);

  /* Finished */

  free(temp);

  return NULL;
}