/* 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 "NestWimp.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"

/* Constant definitions */

#define MillipointsPerOSUnit 400

/* Locals */

const static char * base64_table="ABCDEFGHIJKLMNOP"
                                 "QRSTUVWXYZabcdef"
                                 "ghijklmnopqrstuv"
                                 "wxyz0123456789+/";

static int  millipoints_per_os_unit_x = 400;
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;

/*************************************************/
/* 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)
{
  _kernel_oserror * e;

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

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

    StrNCpy0(lasttokn, s);

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

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

    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_token: Failed\n");
      #endif

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

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

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

  return (char *) &tokens;
}

/*************************************************/
/* lookup_choice()                               */
/*                                               */
/* As lookup_token() above, but looks up the     */
/* token in the Choices file, rather than the    */
/* Messages file.                                */
/*                                               */
/* 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_choice(char * s, int flag, char * arg)
{
  _kernel_oserror * e;

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

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

    StrNCpy0(lasttokn, s);

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

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

    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_choice: Failed\n");
      #endif

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

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

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

  return (char *) &tokens;
}

/*************************************************/
/* show_error()                                  */
/*                                               */
/* Reports an error and exits with EXIT_FAILURE  */
/*                                               */
/* Parameters:                                   */
/*    a pointer to a _kernel_oserror structure   */
/*                                               */
/* Assumptions:                                  */
/*    none (so the pointer may be NULL)          */
/*************************************************/

void show_error(_kernel_oserror *e)
{
  if (e!=NULL)
  {
    _kernel_swi_regs r;
    char             name[64];
    char             spri[13];
    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:                                   */
/*    a pointer to a _kernel_oserror structure   */
/*                                               */
/* Assumptions:                                  */
/*    none (so the pointer may be NULL)          */
/*************************************************/

void show_error_cont(_kernel_oserror *e)
{
  if (e!=NULL)
  {
    _kernel_swi_regs r;
    char             name[64];
    char             spri[13];
    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 longjmp(env,1);                 /* Else jump back to poll loop */
    }
  }
}

/*************************************************/
/* 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:                                   */
/*    a pointer to a _kernel_oserror structure   */
/*                                               */
/* Assumptions:                                  */
/*    none (so the pointer may be NULL)          */
/*************************************************/

void show_error_ret(_kernel_oserror *e)
{
  if (e!=NULL)
  {
    _kernel_swi_regs r;
    char             name[64];
    char             spri[13];
    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.                        */
/*************************************************/

void 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));
}

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

void 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));
}

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

void 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));
}

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

void 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));
}

/*************************************************/
/* show_centred()                                */
/*                                               */
/* Shows a Toolbox object centred to the screen, */
/* opened persistently where possible            */
/*                                               */
/* Parameters:                                   */
/*    An ID of any Toolbox object that will      */
/*    return it's Wimp window handle when        */
/*    Toolbox_ObjectMiscOp is called for it with */
/*    a reason code of 0 - e.g. Window, DCS,     */
/*    Colour dialogue.                           */
/*                                               */
/* Assumptions:                                  */
/*    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: f contains flags to pass to the   */
/*             Toolbox in the set extent call    */
/*             o is the object ID of the browser */
/*             window to alter                   */
/*             w is a pointer to a BBox with the */
/*             extent coordinates in it          */
/* Returns:    Pointer to a _kernel_oserror or   */
/*             NULL, if there is no error        */
/*************************************************/

_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: int w is the handle of the window */
/*             in question                       */
/* Returns:    the handle of the first non-pane  */
/*             window in front of int w, 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: Exactly 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)) Printf("register_null_claimant: Registered a claimant\n");
  #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: Nulls claimed\n");
    #endif
  }
}

/*************************************************/
/* deregister_null_claimant()                    */
/*                                               */
/* Call if you want to release null polls.       */
/*                                               */
/* Parameters: Exactly 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)) Printf("deregister_null_claimant: Deregistered a claimant\n");

    if (null_counter < 0)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess, "deregister_null_claimant: Counter is negative; deregistrations have not matched registrations");
      show_error_ret(&erb);
    }
  #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: Nulls released\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. */
/*************************************************/

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

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

  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() (above), 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() (above), 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() (above), 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() (above), 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.      */
/*                                               */
/* Returns:    See Parameters list.              */
/*                                               */
/* 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;
}

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

/*************************************************/
/* 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[2048], help[MaxHelpLen];
  int               adjust_act, select_act;
  int               adjust_cs,  select_cs;
  unsigned int      flags;

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

/*************************************************/
/* is_known_browser()                            */
/*                                               */
/* Finds out if the given browser_data struct is */
/* in the list of known structures.              */
/*                                               */
/* 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;
}

/*************************************************/
/* 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)
{
  char          * out_ptr = out;
  int             i;
  unsigned long   chunk;
  int             p = 0;

  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_strncasecmp()                           */
/*                                               */
/* Case insensitive comparisson of n chars of    */
/* two given strings.                            */
/*                                               */
/* Parameters: As strncmp                        */
/*                                               */
/* Returns:    As strncmp                        */
/*************************************************/

int utils_strncasecmp(const char * a, const char * b, unsigned int n)
{
  while(*a && *b && n--)
  {
    if (tolower(*a++) != tolower(*b++)) return 1;
  }

  return 0;
}

/*************************************************/
/* utils_stop_webserv()                          */
/*                                               */
/* Stops the WebServ application by sending it   */
/* an AppControl message.                        */
/*************************************************/

_kernel_oserror * utils_stop_webserv(void)
{
  WimpMessage       msg;
  _kernel_oserror * e;
  char            * c;
  int             * p;
  int               buffer  [32];
  char              taskname[128];
  int             * notused;
  int               t;
  int               next   = 0;
  int               handle = 0;

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

  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 (!strcmp(taskname, "Acorn WebServe")) handle = p[0];
    }
  }
  while (next >= 0 && handle == 0);

  #ifdef TRACE

    if (!handle)
    {
      /* Didn't find WebServ, 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 WebServ, so exit */

      return NULL;
    }

  #endif

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

  msg.hdr.size        = 32;
  msg.hdr.sender      = task_handle;
  msg.hdr.my_ref      = 0;
  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);
}

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