/* 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   : Handlers.c                             */
/*                                                 */
/* Purpose: Event handlers for driving the browser */
/*          front-end.                             */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 07-Feb-97: Created from Main.h.        */
/***************************************************/

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

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

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

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

#include "toolbox.h"
#include "quit.h"
#include "proginfo.h"

#include "Dialler.h"

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

#include "Browser.h"
#include "ChoiceDefs.h"
#include "Choices.h"
#include "CSIM.h"
#include "Fetch.h" /* (For ISOBJECT macro) */
#include "FetchPage.h"
#include "Filetypes.h"
#include "FontManage.h"
#include "Forms.h"
#include "Frames.h"
#include "History.h"
#include "Hotlist.h"
#include "Images.h"
#include "JavaScript.h"
#include "Menus.h"
#include "Meta.h"
#include "MiscDefs.h"
#include "Mouse.h"
#include "PlugIn.h"
#include "Printing.h"
#include "Protocols.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Save.h"
#include "SaveFile.h"
#include "SetPBoxes.h"
#include "TokenUtils.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "Handlers.h"

/* Local statics */

static int  last_click_x;
static int  last_click_y;

/* Static function prototypes */

static void handlers_get_call_info   (browser_data ** bp, ObjectId * op, IdBlock * idb, ComponentId button);
static int  handlers_menu_or_toolbar (IdBlock * idb);
static void handle_go_to_with_key    (browser_data * b, char c, int clear);

/*************************************************/
/* handlers_menu_or_toolbar()                    */
/*                                               */
/* Returns 1 if an event came from a menu, else  */
/* 0 if it was a toolbar (only call this where   */
/* these are the only two possibilities!).       */
/*                                               */
/* Parameters: Pointer to the event ID block.    */
/*                                               */
/* Returns:    1 if the event came from a menu,  */
/*             else 0.                           */
/*************************************************/

static int handlers_menu_or_toolbar(IdBlock * idb)
{
  _kernel_oserror * e;
  ObjectId          tt, tb, sw;

  /* If we can't get toolbars on the ancestor, there are no */
  /* toolbars so the event certainly isn't from a toolbar.  */
  /* So if the call gives an error, return 1.               */

  e = window_get_tool_bars(InternalTopLeft | InternalBottomLeft,
                           idb->ancestor_id,
                           &tb,
                           &tt,
                           NULL,
                           NULL);
  if (e) return 1;

  if (controls.swap_bars) sw = tt, tt = tb, tb = sw;

  /* If the object ID of the event generator matches either toolbar  */
  /* object ID, the event came from that toolbar; else, from a menu. */

  if (idb->self_id == tt || idb->self_id == tb) return 0;
  else                                          return 1;
}

/*************************************************/
/* handlers_get_call_info()                      */
/*                                               */
/* For button handlers, a standard set of calls  */
/* is used for each handler - find out the       */
/* browser_data struct and toolbar, debounce any */
/* keypress and if this had to be done slab the  */
/* button manually.                              */
/*                                               */
/* Parameters: Pointer to a pointer to a         */
/*             browser_data struct which will be */
/*             filled with a browser_data * for  */
/*             the event's underlying browser    */
/*             window;                           */
/*                                               */
/*             Pointer to an ObjectId into which */
/*             the object ID of the toolbar from */
/*             which the event came is placed;   */
/*             Pointer to the event's ID block;  */
/*                                               */
/*             ComponentId of the button.        */
/*                                               */
/* Assumes:    Any pointer may be NULL, but the  */
/*             caller should check that returned */
/*             values are sensible.              */
/*************************************************/

static void handlers_get_call_info(browser_data ** bp, ObjectId * op, IdBlock * idb, ComponentId button)
{
  browser_data * b;
  ObjectId       t;
  int            key;

  /* If the object has an ancestor, the event was from a toolbar, */
  /* else it was from the window itself (e.g. due to a keyboard   */
  /* shortcut being activated).                                   */

  _swix(OS_Byte, _INR(0,1) | _OUT(1), 121, 0, &key);

  if (toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b))
  {
    ChkError(toolbox_get_client_handle(0, idb->self_id, (void *) &b));

    t = toolbars_get_upper(b);

    if (key != 255)
    {
      slab_gadget_in(t, button);
      debounce_keypress();
      slab_gadget_out(t, button);
    }
  }
  else
  {
    t = toolbars_get_upper(b);

    if (key != 255)
    {
      slab_gadget_in(t, button);
      debounce_keypress();
      slab_gadget_out(t, button);
    }
  }

  if (op) *op = t;
  if (bp) *bp = b;
}

/*************************************************/
/* handle_messages()                             */
/*                                               */
/* Deal with Wimp messages for reason codes 17   */
/* and 18 (type 19, acknowledges, are handled in */
/* handle_ack below). Parameters are as standard */
/* for a Wimp message handler.                   */
/*                                               */
/* This function farms off a lot of its          */
/* responsibilities to functions in Protocols.c. */
/*************************************************/

int handle_messages(WimpMessage * m, void * handle)
{
  #ifndef SINGLE_USER

    /* Multiuser builds will refuse to handle various message types */

    int complain = 0;

    #define ChkLogin if (!logged_in) {complain = 1; break;} else

  #else

    #define ChkLogin {}

  #endif

  switch (m->hdr.action_code)
  {
    case Wimp_MQuit: quit = 1;
    break;

    case Wimp_MMenusDeleted: menusrc = Menu_None;
    break;

    case Wimp_MHelpReply:
    {
      ChkError(protocols_ih_got_help_reply(m));
    }
    break;

    case Wimp_MModeChange:
    {
      browser_data * b;

      modechanged = 1;

      ChkError(image_mode_change());

      if (!printing) wimpt_read();

      read_os_to_points(); /* Handles the 'printing' flag internally */

      b = last_browser;

      while (b)
      {
        ChkError(fm_rescale_fonts(b));
        b = b->previous;
      }

      ChkError(windows_initialise_tool_sizes());
      ChkError(choices_mode_change());
    }
    break;

    case Wimp_MAppControl:
    {
      /* AppControl message - stop all activity */

      if (
           m->data.app_control.reason == Wimp_MAppControl_Stop &&
           m->hdr.sender != task_handle
         )
      {
        browser_data * b;
        IdBlock        idb;

        b = last_browser;

        while (b)
        {
          idb.ancestor_id = 0;
          idb.self_id     = b->self_id;

          handle_stop(0, NULL, &idb, NULL);

          b = b->previous;
        }
      }
    }
    break;

    /* App to app transfer (Message_DataSaveAck is in the multiple source section) */

    case Wimp_MDataLoadAck:             ChkLogin ChkError(protocols_atats_got_data_load_ack(m));  break;
    case Wimp_MRAMFetch:                ChkLogin ChkError(protocols_atats_got_ram_fetch(m));      break;

    case Wimp_MDataOpen:                         ChkError(protocols_atatl_got_data_open(m));      break;
    case Wimp_MDataLoad:
    {
      /* Allow certain browser systems to get a look at the message first */

      #ifndef SINGLE_USER

        if (setpboxes_check_message(m)) break;

      #endif

      ChkLogin ChkError(protocols_atatl_got_data_load(m));
    }
    break;

    case Wimp_MDataSave:                ChkLogin ChkError(protocols_atatl_got_data_save(m));      break;
    case Wimp_MRAMTransmit:             ChkLogin ChkError(protocols_atatl_got_ram_transmit(m));   break;

    /* Printing protocol (Message_DataSaveAck is in the multiple source section) */

    case Browser_Message_PrintError:    ChkLogin ChkError(protocols_pp_got_print_error(m));       break;
    case Browser_Message_PrintTypeOdd:  ChkLogin ChkError(protocols_pp_got_print_type_odd(m));    break;

    /* Multiple source messages */

    case Wimp_MDataSaveAck:             ChkLogin ChkError(protocols_multi_got_data_save_ack(m));  break;

    /* URI handler */

    case URI_MProcess:                           ChkError(protocols_auh_got_process(m));          break;
    case URI_MReturnResult:                      ChkError(protocols_auh_got_return_result(m));    break;
    case URI_MDying:                             ChkError(protocols_auh_got_dying(m));            break;

    /* ANT URL broadcast protocol */

    case Message_ANTOpenURL:            ChkLogin ChkError(protocols_aub_got_open_url(m));         break;

    /* Plug-In protocol */

    case Message_PlugIn_Opening:        ChkLogin ChkError(plugin_got_opening(m));                 break;
    case Message_PlugIn_URLAccess:      ChkLogin ChkError(plugin_got_url_access(m));              break;
    case Message_PlugIn_StreamNew:      ChkLogin ChkError(plugin_got_stream_new(m));              break;
    case Message_PlugIn_ReshapeRequest: ChkLogin ChkError(plugin_got_reshape_request(m));         break;
    case Message_PlugIn_Status:         ChkLogin ChkError(plugin_got_status(m));                  break;
    case Message_PlugIn_Busy:           ChkLogin ChkError(plugin_got_busy(m));                    break;

// For debugging. This should only ever bounce, i.e. go through handle_ack.
//
//    case Message_PlugIn_Open:
//    {
//      MPlugIn_Open * open = (MPlugIn_Open *) &m->data;
//
//      Printf("PlugIn_Open filename: '%s'\n", plugin_return_string(m, &open->file_name));
//
//      {
//        char combuf[4096];
//
//        sprintf(combuf, "Copy %s ADFS::4.$.ParamsFile ~Q~V~C~NF", plugin_return_string(m, &open->file_name));
//
//        _swix(OS_CLI,
//              _IN(0),
//
//              combuf);
//      }
//    }
//    break;

    default: return 0; break;
  }

  #ifndef SINGLE_USER

    if (complain)
    {
      erb.errnum = Utils_Error_Custom_Message;

      StrNCpy0(erb.errmess,
               lookup_token("MustLogIn:The browser cannot fetch Web pages until you log in.",
                            0,
                            0));

      show_error_ret(&erb);
    }

  #endif

  return 1;
}

/*************************************************/
/* handle_ack()                                  */
/*                                               */
/* Handles UserMessage_Acknowledge from the      */
/* Wimp (message bouncing, etc.).                */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int handle_ack(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  WimpMessage * m = &block->user_message_acknowledge;

  switch (m->hdr.action_code)
  {
    case Browser_Message_PrintSave: ChkError(protocols_pp_print_save_bounced(m));      break;

    case Wimp_MHelpRequest:         ChkError(protocols_ih_help_request_bounced(m));    break;

    case Wimp_MDataLoad:            ChkError(protocols_atats_data_load_bounced(m));    break;
    case Wimp_MDataOpen:            ChkError(protocols_atats_data_open_bounced(m));    break;
    case Wimp_MRAMTransmit:         ChkError(protocols_atats_ram_transmit_bounced(m)); break;

    case Wimp_MRAMFetch:            ChkError(protocols_atatl_ram_fetch_bounced(m));    break;

    case Message_PlugIn_Open:       ChkError(plugin_open_bounced(m));                  break;
    case Message_PlugIn_StreamNew:  ChkError(plugin_stream_new_bounced(m));            break;

    default:
    {
      return 0;
    }
    break;
  }

  return 1;
}

/*************************************************/
/* handle_keys()                                 */
/*                                               */
/* Deal with keyboard pressed events from the    */
/* Wimp. Parameters are as standard for a Wimp   */
/* event handler.                                */
/*************************************************/

int handle_keys(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  browser_data    * b        = NULL;
  browser_data    * ancestor = NULL;
  browser_data    * curframe = NULL;
  _kernel_oserror * e;
  int               key;

  /* Get the browser_data structure associated with either this object's */
  /* ancestor, if it has one, or this object directly, if not. If either */
  /* call fails this isn't a keypress from an ancestor object obtained   */
  /* from a browser window and it isn't from a browser window directly.  */

  if (idb->ancestor_id) e = toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b);
  else                  e = toolbox_get_client_handle(0, idb->self_id,     (void *) &b);

  /* Some key presses may come from windows that have non-zero */
  /* client handles which aren't browser_data struct pointers, */
  /* e.g. a print dialogue with an animation in it will        */
  /* return an animation frame. So if b is non-zero but not a  */
  /* known browser_data struct pointer, set it to NULL so that */
  /* later routines can quickly know there is no valid pointer */
  /* available.                                                */

  if (b && !is_known_browser(b)) b = NULL;

  if (b)
  {
    ancestor = utils_ancestor(b);
    curframe = ancestor->selected_frame;

    if (!curframe) curframe = b;
  }

  key = ((WimpKeyPressedEvent *) block)->key_code;

  /* Is this from a URL bar? To find out, get the toolbar ID of */
  /* this object, if possible.                                  */

  if (!e && idb->ancestor_id)
  {
    ObjectId i;

    i = toolbars_get_upper(b);

    if (!e && i == idb->self_id)
    {
      /* Make sure we have a client handle for the underlying browser */
      /* window before attempting to proceed                          */

      if (b)
      {
        switch (key)
        {
          /* Scrolling the page. Remember, keypresses trapped here */
          /* are from a toolbar object (probably the URL writable) */
          /* so certain key presses - such as Copy / End to go to  */
          /* the bottom of the page - should *not* be trapped, as  */
          /* they have other meanings (e.g. in the above example,  */
          /* delete character to the right).                       */

          case akbd_UpK:
          case akbd_DownK:
          case akbd_PageUpK:
          case akbd_PageDownK:
          case akbd_HomeK:
          case akbd_UpK + akbd_Ctl:
          case akbd_UpK + akbd_Ctl + akbd_Sh:
          case akbd_DownK + akbd_Ctl:
          case akbd_DownK + akbd_Ctl + akbd_Sh:
          {
            if (!browser_scroll_page_by_key(curframe, key, NULL))
            {
              key = 0;
              _swix(OS_Byte, _INR(0,1), 21, 0); /* Flush keyboard buffer */
            }
          }
          break;

          case akbd_LeftK:
          case akbd_LeftK + akbd_Sh:
          case akbd_LeftK + akbd_Ctl:
          case akbd_LeftK + akbd_Sh + akbd_Ctl:
          {
            /* For left, only scroll when the caret is at the start of the */
            /* string in the URL writable.                                 */

            if (((WimpKeyPressedEvent *) block)->caret.index == 0)
            {
              if (!browser_scroll_page_by_key(curframe, key, NULL))
              {
                key = 0;
                _swix(OS_Byte, _INR(0,1), 21, 0); /* Flush keyboard buffer */
              }
            }
          }
          break;

          case akbd_RightK:
          case akbd_RightK + akbd_Sh:
          case akbd_RightK + akbd_Ctl:
          case akbd_RightK + akbd_Sh + akbd_Ctl:
          {
            /* For right, only scroll when the caret is at the end of the */
            /* string in the URL writable.                                */

            char writable[Limits_URLBarWrit];
            int  len;

            *writable = 0;
            writablefield_get_value(0, idb->self_id, URLBarWrit, writable, sizeof(writable), &len);
            writable[sizeof(writable) - 1] = 0; /* (Ensure termination) */

            if (((WimpKeyPressedEvent *) block)->caret.index >= len)
            {
              if (!browser_scroll_page_by_key(curframe, key, NULL))
              {
                key = 0;
                _swix(OS_Byte, _INR(0,1), 21, 0); /* Flush keyboard buffer */
              }
            }
          }
          break;

          case akbd_TabK:
          {
            if (choices.keyboard_ctrl)
            {
              wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1);

              /* If there's no selected token, select one */

              if (!ancestor->selected || !ancestor->selected_owner)
              {
                /* Ensure *both* values are NULL (sanity check) */

                ancestor->selected       = NULL;
                ancestor->selected_owner = NULL;

                /* Select a token */

                browser_move_selection(curframe, akbd_DownK);
              }
              else
              {
                /* If there's a selected token, does it belong to this */
                /* window?                                             */

                if (ancestor->selected_owner == curframe)
                {
                  WimpGetWindowStateBlock s;

                  /* If it belongs to this window but it's not visible, */
                  /* find a new token that is.                          */

                  s.window_handle = curframe->window_handle;

                  if (!wimp_get_window_state(&s) && !browser_check_visible(b, &s, ancestor->selected))
                  {
                    browser_clear_selection(curframe, 0);
                    browser_move_selection(curframe, akbd_DownK);
                  }
                }
//                else
//                {
//                  /* The selected token doesn't belong to this window, */
//                  /* so select a new one here.                         */
//
//                  browser_move_selection(b, akbd_DownK);
//                }
              }
            }

// This is unfinished - it only works if the form has already
// had an input focus at least once, and won't scroll the page
// if the focus should drop off it.

            else form_give_focus(curframe);

            key = 0;
          }
          break;

          case akbd_TabK + akbd_Ctl:
          {
            char url[Limits_URLBarWrit];
            int  changed = 0;

            /* Read whatever is in the URL bar writable */

            *url = 0;
            ChkError(writablefield_get_value(0, i, URLBarWrit, url, sizeof(url), NULL));
            url[sizeof(url) - 1] = 0; /* (Ensure termination) */

            /* Try and find something appropriate in the hotlist, */
            /* then the history.                                  */

            #ifndef REMOTE_HOTLIST
              changed = hotlist_find_match(url, sizeof(url));
            #endif

            if (!changed) changed = history_find_match(url, sizeof(url));

            if (changed)
            {
              /* Update the URL writable */

              writablefield_set_value(0, i, URLBarWrit, url);
            }

            key = 0;
          }
          break;

          case akbd_TabK + akbd_Sh:
          {
            char url[Limits_URLBarWrit];
            int  changed;

            /* Read whatever is in the URL bar writable */

            *url = 0;
            ChkError(writablefield_get_value(0, i, URLBarWrit, url, sizeof(url), NULL));
            url[sizeof(url) - 1] = 0; /* (Ensure termination) */

            /* Cycle the protocol specifier (adding one in if not already present) */

            changed = urlutils_cycle_protocol(url, sizeof(url));

            if (changed)
            {
              /* Update the URL writable */

              writablefield_set_value(0, i, URLBarWrit, url);
            }

            key = 0;
          }
          break;

          case 0x00d:
          {
            char url[Limits_URLBarWrit];

            /* Read the new URL from the URL bar writable */

            *url = 0;
            ChkError(writablefield_get_value(0, i, URLBarWrit, url, sizeof(url), NULL));
            url[sizeof(url) - 1] = 0; /* (Ensure termination) */

            #ifdef ALIAS_URLS
            // Not implemented yet...
            #endif

            /* If we're displaying a temporary file and the URL hasn't changed, */
            /* treat this as an attempt to reload the page.                     */

            if (
                 b->displayed == Display_Scrap_File   &&
                 !strcmp(browser_current_url(b), url)
               )
            {
              erb.errnum = Utils_Error_Custom_Message;

              StrNCpy0(erb.errmess,
                       lookup_token("CantReload:This page cannot be reloaded, as it was sent directly from another application.",
                                    0,
                                    0));

              show_error_ret(&erb);
            }
            else
            {
              #ifdef HIDE_CGI

                /* If HIDE_CGI is defined, the URL bar may have only part of    */
                /* the URL in it - the CGI information could be stripped off.   */
                /* In that case, check if the URL matches the browser's current */
                /* one with the exception of the CGI stuff, and only do the     */
                /* fetch if not.                                                */

                if (
                     browser_current_url(b) &&                               /* If there *is* a current URL, and            */
                     !strncmp(browser_current_url(b), url, strlen(url)) &&   /* the URL bar matches the start of it...      */
                     strlen(browser_current_url(b)) > strlen(url) &&         /* ...but there's more of the current URL left */
                     browser_current_url(b)[strlen(url)] == '?'              /* ...and the first extra character is a '?',  */

                   )
                   ChkError(fetchpage_new(b, browser_current_url(b), 1, 1)); /* ...then fetch the current URL instead.      */

                else ChkError(fetchpage_new(b, url, 1, 1)); /* Otherwise do what the user asked! */

              #else

                /* Start the new fetch */

                ChkError(fetchpage_new(b, url, 1, 1));

              #endif
            }

            key = 0;
          }
          break;
        }
      }
    }
  }

  if (key) wimp_process_key(key);

  return 1;
}

/*************************************************/
/* handle_keys_from_browser()                    */
/*                                               */
/* Called when a Wimp key pressed event is       */
/* generated for a specific browser window.      */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int handle_keys_from_browser(int eventcode, WimpPollBlock * block, IdBlock * idb, browser_data * handle)
{
  int            key = ((WimpKeyPressedEvent *) block)->key_code;
  browser_data * ancestor;
  browser_data * curframe;

  if (!handle) return 0;

  ancestor = utils_ancestor(handle);
  curframe = ancestor->selected_frame;

  if (!curframe) curframe = handle;

  form_process_key(handle, &key);

  /* If 'key' is non-zero, the forms library couldn't handle it */

  switch (key)
  {
    /* Scrolling the page. Here, unlike the code above, the */
    /* keypress is straight off the browser page, so unless */
    /* the forms library has trapped the key, things such   */
    /* as left/right and copy/end can be used to move the   */
    /* page around as well as up/down etc.                  */

    case akbd_PageUpK:
    case akbd_PageDownK:

    case akbd_HomeK:
    case akbd_CopyK: /* Or 'End' */

    case akbd_UpK:
    case akbd_UpK    + akbd_Ctl:
    case akbd_UpK    + akbd_Ctl + akbd_Sh:

    case akbd_DownK:
    case akbd_DownK  + akbd_Ctl:
    case akbd_DownK  + akbd_Ctl + akbd_Sh:

    case akbd_LeftK:
    case akbd_LeftK  + akbd_Sh:
    case akbd_LeftK  + akbd_Ctl:
    case akbd_LeftK  + akbd_Ctl + akbd_Sh:

    case akbd_RightK:
    case akbd_RightK + akbd_Sh:
    case akbd_RightK + akbd_Ctl:
    case akbd_RightK + akbd_Ctl + akbd_Sh:
    {
      int limit;

      if (browser_move_selection(curframe, key))
      {
        key = 0;
        _swix(OS_Byte, _INR(0,1), 21, 0); /* Flush keyboard buffer */

        /* Rehighlight the frame if required */

        if (controls.keep_highlight) frames_highlight_frame(curframe);
      }
      else
      {
        if (!browser_scroll_page_by_key(curframe, key, &limit))
        {
          /* If limit is set, the window is scrolled as far as it */
          /* will go - can we jump to another frame, then? (Only  */
          /* jumping out for certain key presses, though).        */

          if (
               limit &&
               (
                 key == akbd_UpK ||
                 key == akbd_DownK
               )
             )
          {
            browser_data * next = frames_find_another_frame(curframe, key == akbd_UpK ? 1 : 0);

            if (next)
            {
              /* Another frame has been identified */

              browser_clear_selection(curframe, 0);

              /* Scroll the frame appropriately */

              browser_scroll_page_v(next,
                                    NULL,
                                    key == akbd_UpK ? 0 : 1,
                                    0,
                                    0,
                                    0x1000000,
                                    NULL);

              /* Make sure the ancestor(s) are updated */

              ancestor->selected_frame = NULL;

              ancestor = utils_ancestor(next);

              ancestor->selected_frame = curframe = next;

              /* Find a selectable */

              browser_move_selection(curframe, key);

              /* Highlight the frame */

              frames_highlight_frame(curframe);
            }
          }
          else
          {
            /* Scrolled page - rehighlight the frame if required */

            if (controls.keep_highlight) frames_highlight_frame(curframe);
          }
        }

        key = 0;
        _swix(OS_Byte, _INR(0,1), 21, 0);
      }
    }
    break;

    case akbd_TabK: if (!browser_give_general_focus(curframe)) key = 0;
    break;

    case 0x00d:
    {
      browser_data * owner;

      owner = ancestor->selected_owner;

      if (ancestor->selected && browser_check_visible(owner, NULL, ancestor->selected))
      {
        /* If an item is selected and at least partially visible, */
        /* act as if it were clicked upon with Select             */

        // Optimise this? Lose the check for IMG, and rely on
        // HTMLLib returning valid information?

        if (
             (
               (
                 (ancestor->selected->style & IMG)       &&
                 (ancestor->selected->type & TYPE_ISMAP)
               )
               ||
               (
                 ancestor->selected->type & TYPE_ISCLIENTMAP
               )
             )
             && !ancestor->in_image_map
           )
        {
          /* For image maps, if not already selected, start keyboard */
          /* navigation of the map.                                  */

          BBox      box;
          HStream * map  = ancestor->selected;

          if (!image_get_token_image_size(owner, map, &box))
          {
            WimpGetWindowStateBlock s;
            int                     x, y;

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

            if (!image_get_token_image_position(owner, map, &x, &y))
            {
              x = coords_x_toscreen(x, (WimpRedrawWindowBlock *) &s);
              y = coords_y_toscreen(y, (WimpRedrawWindowBlock *) &s);

              box.xmin += x + 2;
              box.ymin += y + 2;
              box.xmax += x - 4;
              box.ymax += y - 4;

              // Re: Next line of code.
              //
              // Why? Can't easily force rectangle constraint, as
              // whenever the pointer changes shape it is unconstrained.
              // So the navigation function has to handle being called
              // with the pointer moved off the map (and it does); so
              // this isn't wanted, really...
              //
              // Still, left in just in case it's needed for something!
              //
              // /* Constrain the pointer to the image map rectangle */
              //
              // mouse_rectangle(&box, 1);

              /* Move x and y to the middle of the image map */

              x += (box.xmax - box.xmin) / 2, y += (box.ymax - box.ymin) / 2;

              /* Move pointer, and update internal details of what pointer is over */

              mouse_to(x, y, 0);

              owner->pointer_over = ancestor->pointer_over = map;
              browser_pointer_check(0, NULL,NULL, owner);
              mouse_set_pointer_shape(Mouse_Shape_Map);
              mouse_watch_pointer_control(0);
              mouse_pointer_on();

              owner->in_image_map = ancestor->in_image_map = 1;

              return 1;
            }
          }
        }
        else
        {
          /* Not an image map, or a selected map - so follow the link. */

          owner->pointer_over = NULL;
          browser_pointer_check(0, NULL,NULL, owner);

          if (choices.keyboard_ctrl)
          {
            mouse_pointer_off();
            mouse_watch_pointer_control(1);
          }

          handle_link_clicks(-1, NULL, NULL, owner);
        }
      }
    }
    break;

    default:
    {
      /* In merged status bar situations, want alphanumeric characters */
      /* to pop up the URL writable with that key in it.               */

      if (isalnum(key)) /* Wimp_ProcessKey numbers are equal to ASCII codes for alphanumerics */
      {
        handle_go_to_with_key(handle, (char) key, controls.clear_first);
        key = 0;
      }
    }
  }

  if (key) ChkError(wimp_process_key(key));

  return 1;
}

/*************************************************/
/* handle_menus()                                */
/*                                               */
/* Deal with menu selection events from the Wimp */
/* (for forms etc.). Parameters are as standard  */
/* for a Wimp event handler.                     */
/*************************************************/

int handle_menus(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  switch (menusrc)
  {

    case Menu_Form:
    {
      form_select_menu_event(block);
    }
    break;

    case Menu_LocalHist: /* Same as for global, so no break */
    case Menu_GlobalHist:
    {
      ChkError(history_menu_selection((browser_data *) menuhdl, block));
    }
    break;

    default: return 0;
  }

  return 1;
}

/*************************************************/
/* handle_scroll_requests()                      */
/*                                               */
/* Deal with Scroll Request events from the Wimp */
/* (e.g. for page up/down). Parameters are as    */
/* standard for a Wimp event handler.            */
/*************************************************/

int handle_scroll_requests(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  if (b->scroll_request.yscroll)
  {
    ChkError(browser_scroll_page_v(handle,
                                   &b->scroll_request.open,
                                   b->scroll_request.yscroll > 0,
                                   b->scroll_request.yscroll == 2 || b->scroll_request.yscroll == -2,
                                   b->scroll_request.yscroll == 1 || b->scroll_request.yscroll == -1,
                                   0,
                                   NULL));
  }

  if (b->scroll_request.xscroll)
  {
    ChkError(browser_scroll_page_h(handle,
                                   &b->scroll_request.open,
                                   b->scroll_request.xscroll < 0,
                                   b->scroll_request.xscroll == 2 || b->scroll_request.xscroll == -2,
                                   b->scroll_request.xscroll == 1 || b->scroll_request.xscroll == -1,
                                   0,
                                   NULL));
  }

  return 1;
}

/*************************************************/
/* handle_clicks()                               */
/*                                               */
/* Deal with mouse click events from the wimp,   */
/* for specific object IDs. Parameters are as    */
/* standard for a Wimp event handler.            */
/*************************************************/

int handle_clicks(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  int shift;

  /* Is Shift being pressed? */

  _swix(OS_Byte,
        _INR(0,1) | _OUT(1),

        121,
        128,

        &shift);

  /* Process the event only if the browser_data structure contents */
  /* match the ancestor ID of the item clicked upon - i.e. if a    */
  /* toolbar has been clicked upon.                                */

  if (idb->ancestor_id == handle->self_id)
  {
    /* If the toolbox hasn't filled in the component ID, e.g. because */
    /* icon flags were forced to change to give a button type the     */
    /* Toolbox didn't expect, fill it in now.                         */

    if (idb->self_component == -1)
    {
      if (
           window_wimp_to_toolbox(0,
                                  b->mouse_click.window_handle,
                                  b->mouse_click.icon_handle,
                                  &idb->self_id,
                                  &idb->self_component)
         )
         return 0;
    }

    switch (idb->self_component)
    {
      case StatusBarStatusCover:  /* Clicking on a covering gadget is equivalent  */
      case StatusBarStatus:       /* to clicking on the underlying gadget instead */
      {
        /* If the URL writable and status display are merged, want to */
        /* now swap the display for the writable and put the caret in */
        /* the field.                                                 */

        if (handle->merged_url)
        {
          toolbars_merged_to_url(handle, idb->self_id);
          gadget_set_focus(0, idb->self_id, URLBarWrit);
        }
      }
      break;

      case URLBarHistoryMenuL: /* Drop through to URLBarHistoryMenuR case */
      case URLBarHistoryMenuR:
      {
        ChkError(history_menu_popup(handle,
                                    idb->self_id,
                                    idb->self_component,
                                    shift,
                                    b->mouse_click.buttons & Wimp_MouseButtonAdjust ? !choices.show_urls : choices.show_urls));
      }
      break;
    }

    /* Grey / ungrey buttons, as the state may change as */
    /* a result of being selected.                       */

    toolbars_set_button_states(handle);

    return 1;
  }

  return 0;
}

/*************************************************/
/* handle_link_clicks()                          */
/*                                               */
/* Deal with mouse click events from the wimp,   */
/* on gadgets in the browser window. Parameters  */
/* are as standard for a Wimp event handler.     */
/*************************************************/

int handle_link_clicks(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  HStream                 * p = NULL;
  int                       ox, oy, adj, used = 0;
  int                       shift, ctrl;
  WimpGetPointerInfoBlock   i;
  browser_data            * ancestor = utils_ancestor(handle);
  browser_data            * owner;

  owner = ancestor->selected_owner;

  /* Is Shift held down? (Do this very early to minimise the */
  /* changes of missing the keypress).                       */

  _swix(OS_Byte,
        _INR(0,1) | _OUT(1),

        121,
        128,

        &shift);

  /* Similarly, for Control */

  _swix(OS_Byte,
        _INR(0,1) | _OUT(1),

        121,
        129,

        &ctrl);

  /* Remember where the click was so other functions can find this  */
  /* without calling Wimp_GetPointerInfo, by which time the pointer */
  /* may have moved a bit.                                          */

  last_click_x = b->mouse_click.mouse_x;
  last_click_y = b->mouse_click.mouse_y;

  /* There are circumstances under which pointer watching, to see if */
  /* the mouse pointer should be turned off, is disabled but should  */
  /* be enabled at this stage. If this is so, turn it back on.       */

  if (choices.keyboard_ctrl && ancestor->selected)
  {
    owner->pointer_over = NULL;
    browser_pointer_check(0, NULL, NULL, owner);
    mouse_watch_pointer_control(1);
  }

  /* If this is entered with an event code of -1, this signals that */
  /* the keyboard handler is calling the function. In that case,    */
  /* the routine will treat the item in handle->selected as if it   */
  /* were clicked on with Select.                                   */
  /*                                                                */
  /* Otherwise, the token that was clicked on is discovered         */
  /* from the pointer position, and the mouse button used from the  */
  /* actual mouse button state.                                     */

  if (eventcode >= 0 && b->mouse_click.buttons & Wimp_MouseButtonMenu) return 0;

  /* Use the 'adjust' function as this may return special information if running Full Screen. */

  adj = (controls.ignore_adjust || eventcode < 0) ? 0 : adjust();

  /* This may be a drag, not a click; also, if the browser has */
  /* children, this must be a frame border resize event.       */

  if (
       b->mouse_click.buttons == 16 || /* (Adjust-drag) */
       b->mouse_click.buttons == 64    /* (Select-drag) */
     )
  {
    /* For now, we have no defined action for drags */

    return 1;
  }

  /* For dragging borders, don't do this on the drag event (i.e. keep */
  /* this code in a position to execute after the check for a drag).  */
  /* Otherwise, when the routine calls Wimp_GetPointerInfo, it'll get */
  /* the original starting coordinates just after the drag begins.    */
  /* This makes the frames 'twitch' briefly back to their starting    */
  /* position.                                                        */

  if (handle->nchildren)
  {
    drag_in_progress = 1;
    handle->dragging = 1;

    register_null_claimant(Wimp_ENull, (WimpEventHandler *) handle_drags, handle);

    /* Call it now to minimise the time during which the pointer */
    /* could wander when it's meant to be constrained            */

    handle_drags(0, NULL, NULL, handle);

    return 1;
  }

  /* Get the token that was clicked upon, if any. */

  ChkError(wimp_get_pointer_info(&i));

  if (eventcode >= 0) p = browser_get_pointer_token(handle, &i, &ox, &oy);
  else                p = ancestor->selected, handle = owner;

  if (p)
  {
    /* First - forms. */

    if (
         (p->style & FORM) &&
         (
           (p->tagno == TAG_INPUT)    ||
           (p->tagno == TAG_TEXTAREA) ||
           (p->tagno == TAG_SELECT)
         )
       )
    {
      int x = 0, y = 0;

      /* Get the offset into an IMAGE button type */

      if (p->tagno == TAG_INPUT && HtmlINPUTtype(p) == inputtype_IMAGE && eventcode >= 0)
      {
        ChkError(image_return_click_offset(handle, p, &i, &x, &y));
      }

      ChkError(form_click_field(handle, p, 0, x, y));
      used = 1;
    }
    else
    {
      /* If the token is an anchor, flash it briefly. This isn't done */
      /* for images unless they're turned off and the image has ALT   */
      /* text, which would show the highlight.                        */

      if (
           p->anchor &&
           (
             !(p->style & IMG) ||
             (
               (p->style & IMG)                            &&
               p->text                                     &&
               handle->displayed != Display_External_Image &&
               !image_token_fetched(handle, p)
             )
           )
         )
         browser_flash_token(handle, p);

      /* If shift and control are not held down, and we have a link, follow the link */

      if (!shift && !ctrl)
      {
        if (p->anchor || (p->type & TYPE_ISCLIENTMAP)) /* An anchor or client-side map */
        {
          int ignore = 0;

          /* First, do we have JavaScript code to deal with? */

          if (p->onclick && *p->onclick) ChkError(javascript_href_onclick(handle, p, &ignore));

          /* If ignore is zero, we're supposed to deal with the HREF attribute */
          /* on the link - otherwise, ignore it.                               */

          if (!ignore)
          {
            /* Client-side image maps */

            if (p->type & TYPE_ISCLIENTMAP)
            {
              char * url;
              char * target;
              char * alt;

              /* Find out which pixel we clicked on */

              ChkError(image_return_click_offset(handle, p, &i, &ox, &oy));

              csim_return_info(handle,
                               p,
                               ox,
                               oy,
                               &url,
                               &target,
                               &alt);

              if (url && *url)
              {
                ChkError(fetchpage_fetch_targetted(handle,
                                                   url,
                                                   target,
                                                   NULL,
                                                   adj));

                used = 1;
              }
            }

            /* Server-side image maps */

            if (
                 !used            &&
                 (p->style & IMG) &&
                 (p->type & TYPE_ISMAP)
               )
            {
              char coords[64];

              if (eventcode < 0)
              {
                ChkError(wimp_get_pointer_info(&i));
                p = browser_get_pointer_token(handle, &i, NULL, NULL);
                if (!p) return 0;
              }

              /* Find out which pixel we clicked on */

              ChkError(image_return_click_offset(handle, p, &i, &ox, &oy));

              if (ox >= 0 && oy >= 0)
              {
                /* Build an appropriate CGI string including this information. */

                sprintf(coords, "?%d,%d", ox, oy);

                ChkError(fetchpage_fetch_targetted(handle,
                                                   p->anchor,
                                                   p->target,
                                                   coords,
                                                   adj));
              }

              used = 1;
            }

            /* Otherwise, a simple link */

            if (!used)
            {
              /* Note that running full screen will cause non-targetted links */

              ChkError(fetchpage_fetch_targetted(handle,
                                                 p->anchor,
                                                 p->target,
                                                 NULL,
                                                 adj));

              used = 1;
            }
          }
          else
          {
            /* The JavaScript routines said that the HREF contents of the link */
            /* should be ignored; so just flag that we've dealt with this, but */
            /* do nothing else.                                                */

            used = 1;
          }
        }
      }
      else
      {
        /* If Shift is held down, open the object into a save dialogue. */
        /* Always do this through a new window, if the Choices say so;  */
        /* this will be a 'small fetch' window rather than a full,      */
        /* large browser.                                               */

        if (shift)
        {
          if (p->anchor)
          {
            if (!adj && !controls.use_small)
            {
              if (!handle->save_oldstore && handle->source)
              {
                /* If there is store and its size hasn't already been remembered, */
                /* keep a record of the old source store size before the new file */
                /* save clears the data away.                                     */

                handle->save_oldstore = flex_size((flex_ptr) &handle->source);
              }

              /* Set the save_link flag and start the fetch */

              handle->save_link = 1;

              /* If control is held down, set the reloading flag to bypass the cache */

              if (ctrl) handle->reloading = 1;

              ChkError(fetchpage_new(handle,
                                     p->anchor,
                                     0,
                                     1));
            }
            else
            {
              /* Open the link in a new window */

              ChkError(windows_create_browser(p->anchor,
                                              NULL,
                                              NULL,
                                              NULL,
                                              Windows_CreateBrowser_SaveToFile));

              /* Again, if control is held down, set the reloading flag to bypass the cache */

              if (ctrl) last_browser->reloading = 1;
            }

            used = 1;
          }
        }
        else
        {
          /* For Control, if it's an image, show it. This overrides */
          /* any 'don't show images' flags and will load the image  */
          /* if it isn't already fetched.                           */

          if ((p->style & IMG) || (ISOBJECT(p)))
          {
            image_reload(handle, p); /* (Slightly misleading function name) */
            used = 1;
          }
        }
      }
    }
  }

  /* For mouse clicks, if nothing has flagged that the click was used */
  /* in some way, place the input focus generally into the ancestor   */
  /* window and mark the frame as selected.                           */

  if (eventcode >= 0 && !used)
  {
    wimp_set_caret_position(ancestor->window_handle, -1, 0, 0, -1, -1);

    ancestor->selected_frame = handle;
    frames_highlight_frame(handle);

    /* If there's an object selected in another frame, must move */
    /* the selection to this one - otherwise, keyboard movement  */
    /* would jump back to the other one.                         */

    if (ancestor->selected && ancestor->selected_owner != handle)
    {
      if (ancestor->selected_owner) browser_clear_selection(ancestor->selected_owner, 0);

      ancestor->selected_owner = NULL; /* Make sure these */
      ancestor->selected       = NULL; /* are cleared...  */

      browser_move_selection(handle, akbd_DownK);
    }
  }

  return 1;
}

/*************************************************/
/* handle_drags()                                */
/*                                               */
/* A null event handler to handle dragging on    */
/* the page. Parameters are as standard for a    */
/* Wimp event handler, though only the last      */
/* parameter, a pointer to the browser_data      */
/* struct for which the drag applies, is used.   */
/*************************************************/

int handle_drags(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  static browser_data     * browser_resizing = NULL;
  static int                resizing_row     = -1;
  static int                resizing_col     = -1;
  static int                resizing_ofsleft = 0;
  static int                resizing_ofstop  = 0;

  WimpGetPointerInfoBlock   info;
  WimpGetWindowStateBlock   state;
  int                       wx, wy;

  /* If handle is NULL, this was called for a Wimp_EUserDrag */
  /* event from the Wimp. In that case, cancel the global    */
  /* drag handling flag and allow various event handlers     */
  /* elsewhere to now progress normally but wait until the   */
  /* null event handler calls this function to deregister    */
  /* any browser-specific stuff.                             */

  if (!handle)
  {
    drag_in_progress = 0;
    return 0;
  }

  /* Get the pointer position and browser window state */

  if (wimp_get_pointer_info(&info)) return 0;

  state.window_handle = handle->window_handle;
  if (wimp_get_window_state(&state)) return 0;

  if (!info.button_state)
  {
    /* No button being pressed - the drag has ended */

    if (handle->dragging)
    {
      drag_in_progress = 0;
      handle->dragging = 0;
      deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) handle_drags, handle);

      if (browser_resizing)
      {
        /* If we were resizing a frameset, lock the new positions in place */

        frames_lock_frameset(browser_resizing);

        /* Reset the various state variables ready for the next drag */

        browser_resizing = NULL;
        resizing_row     = -1;
        resizing_col     = -1;
      }

      return 0;
    }
  }

  /* Work out where the pointer is relative to the window work area */

  wx = coords_x_toworkarea(info.x, (WimpRedrawWindowBlock *) &state);
  wy = coords_y_toworkarea(info.y, (WimpRedrawWindowBlock *) &state);

  /* The mouse button is still held down. So what are we doing? */

  if (handle->nchildren)
  {
    /* If the frame has children, then the pointer must be over a blank bit  */
    /* - i.e. a border between frames. In that case, this is a frame resize. */

    browser_data              * child;
    WimpGetWindowOutlineBlock   co;
    int                         delta_x = 0, delta_y = 0;
    int                         num_rows, num_cols;
    int                         i, c;
    BBox                        span;

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

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

    /* A parent frame has got the click, so it must be in the border between */
    /* other frames in its defined frameset.                                 */

    if (!browser_resizing)
    {
      _kernel_oserror * e;

      e = frames_find_pointer_in_frameset(handle,
                                          last_click_x,
                                          last_click_y,
                                          &resizing_row,
                                          &resizing_col,
                                          &resizing_ofsleft,
                                          &resizing_ofstop,
                                          1);

      if (e || (resizing_row < 0 && resizing_col < 0))
      {
        browser_resizing = NULL;
        resizing_row     = -1;
        resizing_col     = -1;

        return 0;
      }

      browser_resizing = handle;

    /* Closure of 'if (!browser_resizing)' */
    }

    /* Whether or not we had to work out the initial drag */
    /* conditions above, deal with moving the frames now. */

    c = (resizing_row > 0 ? resizing_row : 0) * num_cols + (resizing_col > 0 ? resizing_col : 0);
    if (c >= handle->nchildren) return 0;

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

    if (wimp_get_window_outline(&co)) return 0;

    if (resizing_row > 0) delta_y = (info.y - co.outline.ymax) - resizing_ofstop;
    if (resizing_col > 0) delta_x = resizing_ofsleft - (co.outline.xmin - info.x);

    /* Deal with resizing rows first. */

    span.xmin = span.ymin = 0x1000000; /* Very big to start with */
    span.xmax = span.ymax = -1;        /* Small to start with    */

    if (delta_y)
    {
      /* Resize the row above the pointer */

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

        c = i + (resizing_row - 1) * num_cols;

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

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

          if (!wimp_get_window_outline(&co))
          {
            /* Alter its visible area appropriately */

            co.outline.ymin += delta_y;

            /* Keep a record of the region over which */
            /* things have moved for redraw purposes  */

            if (co.outline.xmin < span.xmin) span.xmin = co.outline.xmin;
            if (co.outline.ymin < span.ymin) span.ymin = co.outline.ymin;
            if (co.outline.xmax > span.xmax) span.xmax = co.outline.xmax;
            if (co.outline.ymax > span.ymax) span.ymax = co.outline.ymax;

            /* Resize any child frames */

            ChkError(frames_resize_frame(child, &co.outline));
          }
        }
      }

      /* Same again for the row below */

      for (i = 0; i < num_cols; i++)
      {
        c = i + resizing_row * num_cols;

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

          if (!wimp_get_window_outline(&co))
          {
            co.outline.ymax += delta_y;

            if (co.outline.xmin < span.xmin) span.xmin = co.outline.xmin;
            if (co.outline.ymin < span.ymin) span.ymin = co.outline.ymin;
            if (co.outline.xmax > span.xmax) span.xmax = co.outline.xmax;
            if (co.outline.ymax > span.ymax) span.ymax = co.outline.ymax;

            ChkError(frames_resize_frame(child, &co.outline));
          }
        }
      }

    /* Closure of 'if (delta_y)' */
    }

    /* Now do columns; first, to the left of the pointer */

    if (delta_x)
    {
      for (i = 0; i < num_rows; i++)
      {
        c = i * num_cols + resizing_col - 1;

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

          if (!wimp_get_window_outline(&co))
          {
            co.outline.xmax += delta_x;

            if (co.outline.xmin < span.xmin) span.xmin = co.outline.xmin;
            if (co.outline.ymin < span.ymin) span.ymin = co.outline.ymin;
            if (co.outline.xmax > span.xmax) span.xmax = co.outline.xmax;
            if (co.outline.ymax > span.ymax) span.ymax = co.outline.ymax;

            ChkError(frames_resize_frame(child, &co.outline));
          }
        }
      }

      /* Next, columns to the right of the pointer */

      for (i = 0; i < num_rows; i++)
      {
        c = i * num_cols + resizing_col;

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

          if (!wimp_get_window_outline(&co))
          {
            co.outline.xmin += delta_x;

            if (co.outline.xmin < span.xmin) span.xmin = co.outline.xmin;
            if (co.outline.ymin < span.ymin) span.ymin = co.outline.ymin;
            if (co.outline.xmax > span.xmax) span.xmax = co.outline.xmax;
            if (co.outline.ymax > span.ymax) span.ymax = co.outline.ymax;

            ChkError(frames_resize_frame(child, &co.outline));
          }
        }
      }

    /* Closure of 'if (delta_x)' */
    }

    /* Now redraw the parent to ensure borders are correctly plotted */

    if (
         span.xmin >= 0 &&
         span.ymin >= 0 &&
         span.xmax >= 0 &&
         span.ymax >= 0
       )
    {
      coords_box_toworkarea(&span, (WimpRedrawWindowBlock *) &state);

      ChkError(wimp_force_redraw(state.window_handle,
                                 span.xmin,
                                 span.ymin,
                                 span.xmax,
                                 span.ymax));
    }

  /* Closure of 'if (handle->nchildren)' */
  }

  return 0;
}

/*************************************************/
/* handle_close_browser()                        */
/*                                               */
/* Close a browser window, and frames within it. */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int handle_close_browser(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  frames_collapse_set(handle);
  windows_close_browser(handle);

  return 1;
}

/*************************************************/
/* handle_home()                                 */
/*                                               */
/* Handles clicks on the Home button. Parameters */
/* are as standard for a Toolbox event handler.  */
/*************************************************/

int handle_home(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  int            from_menu = 0;
  browser_data * b;
  char           home[Limits_URL];

  handlers_get_call_info(&b, NULL, idb, ButtonBarHome);
  from_menu = handlers_menu_or_toolbar(idb);

  urlutils_create_home_url(home, sizeof(home));

  if (from_menu || controls.ignore_adjust || !adjust()) ChkError(fetchpage_new(b, home, 1, 1));
  else
  {
    ChkError(windows_create_browser(home,
                                    NULL,
                                    NULL,
                                    NULL,
                                    Windows_CreateBrowser_Normal));

    ChkError(browser_inherit(b, last_browser));
  }

  /* Grey / ungrey buttons, as the state may change as */
  /* a result of being selected.                       */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_back()                                 */
/*                                               */
/* Handles clicks on the Back button. Parameters */
/* are as standard for a Toolbox event handler.  */
/*************************************************/

int handle_back(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  int            from_menu = 0;
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarBack);
  from_menu = handlers_menu_or_toolbar(idb);

  ChkError(history_fetch_backwards(b, (from_menu || controls.ignore_adjust) ? 0 : adjust()));

  /* Grey / ungrey buttons, as the state may change as */
  /* a result of being selected.                       */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_forward()                              */
/*                                               */
/* Handles clicks on the Forward button.         */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_forwards(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  int            from_menu = 0;
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarForward);
  from_menu = handlers_menu_or_toolbar(idb);

  ChkError(history_fetch_forwards(b, (from_menu || controls.ignore_adjust) ? 0 : adjust()));

  /* Grey / ungrey buttons, as the state may change as */
  /* a result of being selected.                       */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_stop()                                 */
/*                                               */
/* Handles clicks on the Stop button. Parameters */
/* are as standard for a Toolbox event handler.  */
/*************************************************/

int handle_stop(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  #ifdef SINGLE_USER

    int shift;

    /* Is Shift being pressed? */

    _swix(OS_Byte,
          _INR(0,1) | _OUT(1),

          121,
          128,

          &shift);

  #endif

  /* Find out where we were called from */

  handlers_get_call_info(&b, NULL, idb, ButtonBarStop);

  /* If using the tristate TriState_Go_GoTo_Stop, */
  /* then stop kills everything first time. Else, */
  /* it stops everything except image fetches,    */
  /* then stops image fetches too.                */

  if (b->tristate != TriState_Go_GoTo_Stop)
  {
    if (frames_fetching(b)) frames_abort_fetching(utils_ancestor(b), 0, 1);
    else frames_abort_fetching(utils_ancestor(b), 1, 1);
  }
  else frames_abort_fetching(utils_ancestor(b), 1, 1);

  /* Clear the flag saying this is a History based fetch */

  b->from_history = 0;

  /* Cancel local client pull */

  meta_cancel_refresh(b);

  /* Grey / ungrey buttons, as the state may change as */
  /* a result of being selected.                       */

  ChkError(toolbars_set_button_states(b));

  /* Ensure everything is up to date in the toolbars */

  toolbars_update_progress(b);

  #ifdef SINGLE_USER

    /* Broadcast an AppControl message to stop any further     */
    /* activity in WebServe - this will eventually be directed */
    /* straight at WebServe rather than broadcast...           */

//  if (controls.stop_proxy && !b->ancestor) ChkError(utils_stop_proxy());

    if (
         (
           controls.stop_proxy &&
           !shift
         )
         ||
         (
           !controls.stop_proxy &&
           shift
         )
       )
       ChkError(utils_stop_proxy());

  #endif

  return 1;
}

/*************************************************/
/* handle_reload()                               */
/*                                               */
/* Handles clicks on the Reload button.          */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_reload(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  _kernel_oserror * e;
  browser_data    * b;
  ObjectId          t;
  int               new_view;
  int               from_menu = 0;

  handlers_get_call_info(&b, &t, idb, ButtonBarReload);

  /* If the browser is displaying a temporary file, can't reload it */

  if (b->displayed == Display_Scrap_File)
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("CantReload:This page cannot be reloaded, as it was sent directly from another application.",
                          0,
                          0));

    show_error_ret(&erb);

    return 0;
  }

  from_menu = handlers_menu_or_toolbar(idb);
  new_view  = (from_menu || controls.ignore_adjust) ? 0 : adjust();

  /* If there's a displayed URL already, reload it */

  if (b->urlddata)
  {
    /* If not going to a new view, set the reloading flag */
    /* and get the URL.                                   */

    if (!new_view)
    {
      b->reloading   = 1;
      b->reload_lock = 1;

      e = fetchpage_new(b, b->urlddata, 0, 1);

      if (e) show_error_ret(e);
    }

    /* Otherwise, just fetch the URL in a new window. */

    else
    {
      ChkError(windows_create_browser(b->urlddata,
                                      NULL,
                                      NULL,
                                      NULL,
                                      Windows_CreateBrowser_Normal));

      ChkError(browser_inherit(b, last_browser));
    }
  }
  else
  {
    /* Otherwise, get a URL string from the URL bar and do */
    /* a fresh load of that.                               */

    char url[Limits_URLBarWrit];

    *url = 0;
    writablefield_get_value(0, t, URLBarWrit, url, sizeof(url), NULL);
    url[sizeof(url) - 1] = 0; /* (Ensure termination) */

    if (*url)
    {
      if (!new_view) ChkError(fetchpage_new(b, url, 1, 1));
      else
      {
        ChkError(windows_create_browser(url,
                                        NULL,
                                        NULL,
                                        NULL,
                                        Windows_CreateBrowser_Normal));

        ChkError(browser_inherit(b, last_browser));
      }
    }
  }

  /* Grey / ungrey buttons, as the state may change as */
  /* a result of being selected.                       */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_view_hotlist()                         */
/*                                               */
/* Handles clicks on the View Hotlist button.    */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_view_hotlist(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  #ifndef REMOTE_HOTLIST

    int from_menu = handlers_menu_or_toolbar(idb);
    int adjust    = 0;

  #else

    browser_data * b;
    char           path[Limits_URL];
    int            from_menu = 0;

  #endif

  #ifndef SINGLE_USER

    /* In multiuser builds, if not logged in, claim the event */
    /* but don't do anything with it.                         */

    if (!logged_in) return 1;

  #endif

  #ifndef REMOTE_HOTLIST

    /* If not from a menu, allow Adjust to toggle the display type */
    /* at open time to the opposite of that specified by the       */
    /* HotlistShow option in the Choices file.                     */

    if (!from_menu)
    {
      WimpGetPointerInfoBlock info;

      ChkError(wimp_get_pointer_info(&info));

      if (info.button_state & Wimp_MouseButtonAdjust) adjust = 1;
    }

    /* Open the hotlist */

    ChkError(hotlist_open(Toolbox_ShowObject_Centre,
                          NULL,

                          adjust
                          ?
                          choices.hotlist_show == Choices_HotlistType_Descriptions
                          :
                          choices.hotlist_show == Choices_HotlistType_URLs));

  #else

    handlers_get_call_info(&b, NULL, idb, ButtonBarViewHotlist);
    from_menu = handlers_menu_or_toolbar(idb);

    /* Create the hotlist URL */

    urlutils_create_hotlist_url(path, sizeof(path));

    /* Deal with appending the current URL, if necessary */

  // For future implementation:
  //
  //  if (controls.append_urls)
  //  {
  //    char url[Limits_URL];
  //
  //    if (browser_current_url(b))
  //    {
  //      StrNCpy0(url, browser_current_url(b));
  //      history_record_local(b, url);
  //    }
  //
  //    history_pull_local_last(b, url, sizeof(url));
  //
  // Then do the rest on url, instead of browser_current_url.

    if (controls.append_urls && browser_current_url(b))
    {
      int len;

      lookup_control("AppendWith:?url=",0,0);

      /* Need to translate some chars, so working out the length of the final */
      /* string is a little complex.                                          */

      len = strlen(path) + strlen(tokens);

      if (len + 1 < sizeof(path))
      {
        char * p = browser_current_url(b);

        strcat(path, tokens);

        while (*p && len + 4 < sizeof(path)) /* +4 = +1 for terminator, +3 for maximum step size within loop */
        {
          if (isalnum(*p)) path[len] = *p, len++;
          else
          {
            sprintf(path + len, "%%%02X", *p);
            len += 3;
          }

          p++;
        }

        if (len >= sizeof(path)) len = sizeof(path) - 1;
        path[len] = 0;
      }
    }

    /* Finally, fetch the required hotlist */

    if (adjust() && !from_menu && !controls.ignore_adjust)
    {
      ChkError(windows_create_browser(path,
                                      NULL,
                                      NULL,
                                      NULL,
                                      Windows_CreateBrowser_Normal));

      ChkError(browser_inherit(b, last_browser));
    }
    else ChkError(fetchpage_new(b, path, 1, 1));

  #endif

  return 1;
}

/*************************************************/
/* handle_add_hotlist()                          */
/*                                               */
/* Handles clicks on the Add To Hotlist button.  */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_add_hotlist(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  #ifndef REMOTE_HOTLIST

    browser_data * b;
    int            from_menu = 0;
    int            adjust    = 0;

    handlers_get_call_info(&b, NULL, idb, ButtonBarAddToHotlist);
    from_menu = handlers_menu_or_toolbar(idb);

    /* If not from a menu, allow Adjust to add to the other end of the */
    /* hotlist compared to that specified in the AddHotlist Choices    */
    /* file item.                                                      */

    if (!from_menu)
    {
      WimpGetPointerInfoBlock info;

      ChkError(wimp_get_pointer_info(&info));

      if (info.button_state & Wimp_MouseButtonAdjust) adjust = 1;
    }

    /* Can't proceed if we don't have a URL */

    if (b && browser_current_url(b) && *browser_current_url)
    {
      char title[Limits_Title];

      ChkError(window_get_title(0,
                                b->self_id,
                                title,
                                sizeof(title),
                                NULL));

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

      ChkError(hotlist_add(title,
                           browser_current_url(b),

                           adjust
                           ?
                           choices.add_hotlist == Choices_AddHotlist_Top
                           :
                           choices.add_hotlist == Choices_AddHotlist_Bottom));
    }

  #else

    #ifdef TRACE

      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess, "There is no defined action for Add To Hotlist in REMOTE_HOTLIST builds");
      show_error_ret(&erb);

    #endif

  #endif

  return 0;
}

/*************************************************/
/* handle_view_resources()                       */
/*                                               */
/* Handles clicks on the View Resources button.  */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_view_resources(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  return 0;
}

/*************************************************/
/* handle_load_images()                          */
/*                                               */
/* Handles clicks on the Load Images button.     */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_load_images(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;
  ObjectId       t;

  handlers_get_call_info(&b, &t, idb, ButtonBarLoadImages);

  ChkError(browser_set_look(b, 0, -1, -1, 1, 1));

  return 1;
}

/*************************************************/
/* handle_view_source()                          */
/*                                               */
/* Handles clicks on the View Source button.     */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_view_source(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  char           path[Limits_URL]; /* Generated from a URL, but can't be any longer than one */
  browser_data * b;
  ObjectId       t;
  FILE         * file;
  int            len, bytes;

  handlers_get_call_info(&b, &t, idb, ButtonBarViewSource);

  if (!is_known_browser(b) || !b->source) return 1;

  /* If the item is loaded from a file (not a scrap file, */
  /* or image, obviously), then load from there. Else, go */
  /* via. Scrap.                                          */

  if (
       browser_current_url(b)                                           &&
       b->displayed == Display_Fetched_Page                             &&
       !strncmp(browser_current_url(b), FileMethod, strlen(FileMethod))
     )
  {
    StrNCpy0(path, browser_current_url(b));

    urlutils_url_to_pathname(path, sizeof(path));
  }
  else
  {
    /* Save the source to a scrap file */

    file = fopen(Save_ScrapFile, "wb");

    if (!file) goto handle_view_source_exit;

    len   = flex_size((flex_ptr) &b->source);
    bytes = fwrite(b->source,
                   1,
                   len,
                   file);

    fclose(file);

    if (len < bytes) goto handle_view_source_exit;

    /* Set the filetype */

    ChkError(_swix(OS_File,
                   _INR(0,2),

                   18,
                   Save_ScrapFile,
                   FileType_HTML));

    /* Record that we've used a scrap file */

    StrNCpy0(path, Save_ScrapFile);
  }

  /* Broadcast a Message_DataOpen for the file, claiming the */
  /* filetype is text (which is what the Filer does...).     */

  ChkError(protocols_atats_send_data_open(FileType_TEXT, path));

  return 1;

  /* Exit for errors whilst writing the scrap file */

handle_view_source_exit:

  erb.errnum = Utils_Error_Custom_Message;

  StrNCpy0(erb.errmess,
           lookup_token("CannotShow:Can't show the source this way (problem with the scrap file).",
                        0,0));

  show_error_ret(&erb);

  return 1;
}

/*************************************************/
/* handle_go_to()                                */
/*                                               */
/* Handles clicks on the Go To button.           */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_go_to(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;
  ObjectId       t;

  handlers_get_call_info(&b, &t, idb, ButtonBarGoTo);

  /* Ensure the URL bar is up to date, if the URL writable and */
  /* status displays are merged then change to the URL display */
  /* and finally give the window the input focus.              */

  toolbars_update_url(b);
  if (b->merged_url) toolbars_merged_to_url(b, t);
  if (t) gadget_set_focus(0, t, URLBarWrit);

  /* Update the buttons and exit */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_go_to_with_key()                       */
/*                                               */
/* Handles going to a 'go to' state (merged      */
/* toolbar showing the URL writable instead of   */
/* the status display) with a given character    */
/* entered into that writable.                   */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the toolbar;          */
/*                                               */
/*             The character to append to the    */
/*             writable field's contents;        */
/*                                               */
/*             1 to in fact clear the writable   */
/*             before putting the character in,  */
/*             else 0 to append to existing      */
/*             contents.                         */
/*************************************************/

static void handle_go_to_with_key(browser_data * b, char c, int clear)
{
  ObjectId t;
  char     url[Limits_URLBarWrit];
  char     cat[2];

  t = toolbars_get_upper(b);

  /* Ensure the URL bar is up to date, if the URL writable and */
  /* status displays are merged then change to the URL display */
  /* and finally give the window the input focus.              */

  toolbars_update_url(b);

  /* (Append the given character) */

  if (clear)
  {
    url[0] = c;
    url[1] = 0;
  }
  else
  {
    *url = 0;
    writablefield_get_value(0, t, URLBarWrit, url, sizeof(url), NULL);
    url[sizeof(url) - 1] = 0;

    if (strlen(url) < sizeof(url))
    {
      cat[0] = c;
      cat[1] = 0;
      strcat(url, cat);
    }
  }

  writablefield_set_value(0, t, URLBarWrit, url);

  /* (Show the writable / give the input focus) */

  if (b->merged_url) toolbars_merged_to_url(b, t);
  if (t) gadget_set_focus(0, t, URLBarWrit);

  /* Update the buttons and exit. */

  ChkError(toolbars_set_button_states(b));

  return;
}

/*************************************************/
/* handle_go()                                   */
/*                                               */
/* Handles clicks on the Go button. Parameters   */
/* are as standard for a Toolbox event handler.  */
/*************************************************/

int handle_go(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data        * b;
  WimpKeyPressedEvent   keys;
  IdBlock               id;

  handlers_get_call_info(&b, NULL, idb, ButtonBarGo);

  // Nasty... But then robust, as quite a lot goes on in the key handler
  // and duplicating the code would lead to problems if it gets out of
  // sync.

  /* Fake a keyboard pressed event from the URL bar for the key handler */

  keys.key_code     = 0x00d;
  id.ancestor_id    = b->self_id;
  id.self_id        = toolbars_get_upper(b);
  id.self_component = URLBarWrit;

  return handle_keys(eventcode, (WimpPollBlock *) &keys, &id, NULL);
}

/*************************************************/
/* handle_cancel()                               */
/*                                               */
/* Handles clicks on the Cancel button.          */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarCancel);

  /* If the URL writable and status display are merged, switch to */
  /* status display. Update the URL bar with the original text,   */
  /* cancelling any changes made by the user, and set the caret   */
  /* position by the general method (so focus may go to a form,   */
  /* the URL writable, or the page generally).                    */

  if (b->merged_url) toolbars_merged_to_status(b, toolbars_get_upper(b));
  toolbars_update_url(b);
  browser_give_general_focus(b);

  /* Update the buttons and exit */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_bistate()                              */
/*                                               */
/* Handles the EButtonBarBistate event, which    */
/* says that the bistate button should be        */
/* activated.                                    */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_bistate(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarBistate);

  /* Deal with each button type individually */

  switch (b->bistate)
  {
    case BiState_Cancel_Back:
    {
      if (!b->bistate_state) return handle_cancel(eventcode, event, idb, handle);
      else                   return handle_back(eventcode, event, idb, handle);
    }
    break;
  }

  return 0;
}

/*************************************************/
/* handle_tristate()                             */
/*                                               */
/* Handles the EButtonBarTristate event, which   */
/* says that the tristate button should be       */
/* activated.                                    */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_tristate(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarTristate);

  /* Deal with each button type individually */

  switch (b->bistate)
  {
    case TriState_Go_GoTo_Stop:
    {
      switch (b->tristate_state)
      {
        case 0: return handle_go(eventcode, event, idb, handle);
        break;
        case 1: return handle_go_to(eventcode, event, idb, handle);
        break;
        case 2: return handle_stop(eventcode, event, idb, handle);
        break;
      }
    }
    break;
  }

  return 0;
}

/*************************************************/
/* handle_save_source()                          */
/*                                               */
/* Handles clicks on the Save button. Parameters */
/* are as standard for a Toolbox event handler.  */
/*************************************************/

int handle_save_source(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarSaveSource);

  if (is_known_browser(b) && b->source) ChkError(savefile_open_for(b, save_as_html));

  return 1;
}

/*************************************************/
/* handle_print()                                */
/*                                               */
/* Handles clicks on the Print button in the     */
/* toolbar of browser windows. Parameters are as */
/* standard for a Toolbox event handler.          */
/*************************************************/

int handle_print(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarPrint);

  if (is_known_browser(b) && b->cell && b->cell->nlines) ChkError(print_open_for(b, idb->self_id));

  return 1;
}

/*************************************************/
/* handle_save_as_text()                         */
/*                                               */
/* Handles clicks on the Save As Text button.    */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_save_as_text(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarSaveAsText);

  if (is_known_browser(b) && b->cell && b->cell->nlines) ChkError(savefile_open_for(b, save_as_text));

  return 1;
}

/*************************************************/
/* handle_save_as_draw()                         */
/*                                               */
/* Handles clicks on the Save As Draw button.    */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_save_as_draw(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  handlers_get_call_info(&b, NULL, idb, ButtonBarSaveAsDraw);

  if (is_known_browser(b) && b->cell && b->cell->nlines) ChkError(savefile_open_for(b, save_as_draw));

  return 1;
}

/*************************************************/
/* handle_clear_url()                            */
/*                                               */
/* Clears the URL writable (like Ctrl+U),        */
/* assuming the input focus is in the relevant   */
/* place...                                      */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_clear_url(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  WimpGetCaretPositionBlock   caret;
  browser_data              * b;
  ObjectId                    t;
  char                        url[2];

  ChkError(wimp_get_caret_position(&caret));

  if (caret.icon_handle < 0 || caret.height < 0 || caret.index < 0) return 0;

  ChkError(toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b));

  if (!b) return 0;

  t = toolbars_get_upper(b);

  /* Ensure the URL bar is up to date, if the URL writable and */
  /* status displays are merged then change to the URL display */
  /* and finally give the window the input focus.              */

  toolbars_update_url(b);

  /* (Append the given character) */

  url[0] = 0;
  url[1] = 0;

  writablefield_set_value(0, t, URLBarWrit, url);

  /* (Show the writable / give the input focus) */

  if (b->merged_url) toolbars_merged_to_url(b, t);
  if (t) gadget_set_focus(0, t, URLBarWrit);

  /* Update the buttons and exit. */

  ChkError(toolbars_set_button_states(b));

  return 1;
}

/*************************************************/
/* handle_show_history_menu()                    */
/*                                               */
/* Opens the history menu near the menu popup    */
/* gadget (acts as if that were clicked upon).   */
/* If the gadget doesn't exist, the opening      */
/* position is undefined.                        */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_show_history_menu(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;
  ObjectId       t;
  BBox           test;
  int            which;
  int            shift;

  /* Is Shift being pressed? */

  _swix(OS_Byte,
        _INR(0,1) | _OUT(1),

        121,
        128,

        &shift);

  /* Which browser is this for? */

  ChkError(toolbox_get_client_handle(0,
                                     idb->ancestor_id ? idb->ancestor_id : idb->self_id,
                                     (void *) &b));
  if (!b) return 0;

  t = toolbars_get_upper(b);

  /* Is the popup present (either the 'to the left' or */
  /* the 'to the right' version)?                      */

  if      (!gadget_get_bbox(0, t, URLBarHistoryMenuR,   &test)) which = 1;
  else if (!gadget_get_bbox(0, t, URLBarHistoryMenuL,   &test)) which = 2;
  else                                                          which = 0;

  if (which)
  {
    /* Show the menu */

    ChkError(history_menu_popup(b,
                                t,
                                which == 1 ? URLBarHistoryMenuR : URLBarHistoryMenuL,
                                shift,
                                choices.show_urls));
    return 1;
  }

  /* Gadget isn't present, so can't open the menu */

  return 0;
}

/*************************************************/
/* handle_show_info()                            */
/*                                               */
/* Put version number in program info window;    */
/* called on a ProgInfo_AboutToBeShown event.    */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_show_info(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  #ifndef SINGLE_USER

    /* Make sure that the tokens buffer is invalidated, */
    /* or we could end up appending multiple '(M)'s to  */
    /* the string if lookup_token thinks it has no      */
    /* reason to look up the token again.               */

    *lasttokn = 0;

  #endif

  lookup_token("Version:(Unknown!)",1,0);

  #ifndef SINGLE_USER

    strcat(tokens, " (M)");

  #endif

  ChkError(proginfo_set_version(0,
                                idb->self_id,
                                tokens));

  return 1;
}

/*************************************************/
/* handle_quit()                                 */
/*                                               */
/* Deal with a Quit_Quit event from any source;  */
/* parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_quit(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;

  /* Ensure the pointer is visible */

  mouse_pointer_on();

  /* Close all open windows, thereby freeing memory and terminating */
  /* any open connections. This may take some time, so put up an    */
  /* hourglass.                                                     */

  _swix(Hourglass_Start, _IN(0), 10);

  while (last_browser)
  {
    b = last_browser;

    while (b && b->ancestor) b = b->previous;

    if (b && !b->ancestor) handle_close_browser(0, NULL, NULL, b);
  }

  _swix(Hourglass_Off, 0);

  /* Signal that the application should exit through setting 'quit'. */

  quit = 1;

  return 1;
}

/*************************************************/
/* handle_lose_caret()                           */
/*                                               */
/* Call to drag the input focus back to the      */
/* window in case it is lost.                    */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int handle_lose_caret(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  if (controls.keep_caret)
  {
    WimpGetPointerInfoBlock i;
    int                     handle;

    /* Only grab the caret if this task owns the window */
    /* the pointer is currently over.                   */

    ChkError(wimp_get_pointer_info(&i));

    handle = task_from_window(i.window_handle);

    if (handle == task_handle)
    {
      WimpGetCaretPositionBlock caret;

      /* The pointer is over a browser-owned window, but if the */
      /* caret is also in a browser-owned window, don't want to */
      /* move it right now.                                     */

      if (wimp_get_caret_position(&caret)) return 0;

      handle = task_from_window(caret.window_handle);

      /* If we don't own the window the caret has moved to, */
      /* grab it back into the browser window.              */

      if (handle != task_handle) browser_give_general_focus(last_browser);

      return 1;
    }
  }

  return 0;
}

/*************************************************/
/* handle_dialler_display()                      */
/*                                               */
/* A null event handler to update the dialler    */
/* status display field, if present.             */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int handle_dialler_display(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  if (handle->url_bar) ChkError(toolbars_update_dialler_time(handle));

  return 0;
}

/*************************************************/
/* handle_dialler_service()                      */
/*                                               */
/* Handles Message_Service messages from the     */
/* TaskModule module, which may be (for example) */
/* watching out for 'status changed' service     */
/* calls from the Dialler.                       */
/*                                               */
/* Parameters are as standard for a Wimp message */
/* handler.                                      */
/*************************************************/

int handle_dialler_service(WimpMessage * m, void * handle)
{
  browser_data     * b = (browser_data *) handle;
  _kernel_swi_regs * r;

  if (!is_known_browser(b)) return 0;

  if (m->hdr.action_code != Message_Service) return 0;

  r = (_kernel_swi_regs *) &m->data;

  if (r->r[1] == Service_DiallerStatus)
  {
    if (b->url_bar) ChkError(toolbars_update_dialler_status(b));

    return 1;
  }

  return 0;
}

/*************************************************/
/* handle_miscellaneous_event()                  */
/*                                               */
/* Handle any other user event not already       */
/* covered by other functions, passing it on if  */
/* we don't know what to do with it.             */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int handle_miscellaneous_event(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  if (eventcode >= EHelpGenericBase && eventcode <= EHelpGenericTop)
  {
    browser_data * b;

    if (toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b))
    {
      ChkError(toolbox_get_client_handle(0, idb->self_id, (void *) &b));
    }

    if (!is_known_browser(b)) b = NULL;

    menus_help_miscellaneous(b, eventcode - EHelpGenericBase);

    return 1;
  }

  return 0;
}