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

#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 "URI.h"     /* URI handler API, in URILib: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 "TBEvents.h"
#include "Utils.h"

#include "Authorise.h"
#include "Browser.h"
#include "Fetch.h"
#include "FetchPage.h"
#include "FontManage.h"
#include "Forms.h"
#include "Frames.h"
#include "History.h"
#include "Images.h"
#include "JavaScript.h"
#include "MiscDefs.h"
#include "Mouse.h"
#include "Printing.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Save.h"
#include "TokenUtils.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "Handlers.h"

/* Local statics */

static char last_help[MaxStaLen];

/* 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 _kernel_oserror * handle_history_menu_popup (browser_data * b, ObjectId toolbar, ComponentId left_or_right, int show_urls);
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;

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

int handle_messages(WimpMessage * m, void * handle)
{
  switch (m->hdr.action_code)
  {
    case Wimp_MQuit:quit=1;
    break;

    case Wimp_MMenusDeleted:menusrc = Menu_None;
    break;

    case Wimp_MHelpReply:
    {
      if (fixed.claimhelp)
      {
        ObjectId                  o, a = 0;
        browser_data            * b = NULL;
        WimpGetPointerInfoBlock   i;

        wimp_get_pointer_info(&i);
        if (window_wimp_to_toolbox(0, i.window_handle, i.icon_handle, &o, NULL)) break;

        /* If we can get an ancestor, the pointer is over e.g. a toolbar */
        /* - otherwise, assume it is over a browser window.              */

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

        if (a)
        {
          toolbox_get_client_handle(0, a, (void *) &b);
        }
        else toolbox_get_client_handle(0, o, (void *) &b);

        /* If we haven't got a valid client handle, exit. */

        if (!is_known_browser(b)) break;

        /* If the text is empty, there was no help for that item, */
        /* so restore the old status display, if there was a      */
        /* help display already there.                            */

        if (!*m->data.help_reply.text)
        {
          if (b->status_help != NULL)
          {
            b->status_help = NULL;
            toolbars_cancel_status(b, Toolbars_Status_Help);
          }
        }
        else
        {
          /* Otherwise update the status bar with the help text, */
          /* if the text has changed.                            */

          if (
               !b->status_help ||
               (
                 b->status_help                                  &&
                 strcmp(b->status_help, m->data.help_reply.text)
               )
             )
          {
            StrNCpy0(last_help, m->data.help_reply.text);
            b->status_help = last_help;
            toolbars_update_status(b, Toolbars_Status_Help);
          }
        }
      }
    }
    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());
    }
    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;

    case Browser_Message_PrintError:
    {
      if (m->hdr.size == 20)
      {
        /* RISC OS 2 printer manager's PrintBusy response */

        erb.errnum = Utils_Error_Custom_Message;

        StrNCpy0(erb.errmess,
                 lookup_token("PrintBusy:The printer is currently busy.",
                              0,
                              0));

        show_error_ret(&erb);
      }

      /* RISC OS 3 !Printers general error response */

      else show_error_ret((_kernel_oserror *) &m->data);
    }
    break;

    case Browser_Message_PrintTypeOdd:
    {
      WimpMessage ptk;

      if (m->hdr.your_ref && m->hdr.your_ref == printer_message_ref)
      {
        /* The printer manager sent PrintTypeOdd as a reply to this */
        /* task (not a broadcast), so go ahead and print.           */

        printer_message_ref = 0;

        /* Send PrintTypeKnown */

        ptk.hdr.size        = 20;
        ptk.hdr.your_ref    = m->hdr.my_ref;
        ptk.hdr.action_code = Browser_Message_PrintTypeKnown;

        ChkError(wimp_send_message(Wimp_EUserMessage, &ptk, m->hdr.sender, 0, NULL));

        print_print(NULL);
      }

// Commented out as the Alias$@PrintType_FF4 system variable does this job
// anyway, and if we don't claim this message then anything else which may
// have a better idea of what to do at least gets a chance to try.
//
// This currently doesn't work, incidentally; the conditions on the 'if'
// are wrong (printer_message_ref has probably been set to 0, but I never
// got the chance to properly debug this before removing it due to time
// constraints...).
//
//      else if (printer_message_ref && m->data.data_save.file_type == FileType_POUT)
//      {
//        /* If the printer doesn't understand PrintOut files, then */
//        /* it may be broken (!) / PostScript. So reply, and copy  */
//        /* the file to the printer device directly.               */
//
//        printer_message_ref = 0;
//
//        ptk.hdr.size        = 20;
//        ptk.hdr.your_ref    = m->hdr.my_ref;
//        ptk.hdr.action_code = Browser_Message_PrintTypeKnown;
//
//        ChkError(wimp_send_message(Wimp_EUserMessage, &ptk, m->hdr.sender, 0, NULL));
//
//        _swix(OS_FSControl,
//              _INR(0,3),
//
//              26,
//              m->data.data_save.leaf_name,
//              "printer:",
//              2); /* Flags - 'Force' set, but no others. */
//      }
    }
    break;

    case Wimp_MDataSaveAck:
    {
      if (m->hdr.your_ref == printer_message_ref)
      {
        WimpMessage dl;
        int         file_size;

        /* Print to a file in Printer$Temp, then send a */
        /* DataLoad to the printer manager.             */

        printer_message_ref = 0;

        print_print(m->data.data_save.leaf_name);

        _swix(OS_File,
              _INR(0,1) | _OUT(4),

              23,
              m->data.data_save.leaf_name,

              &file_size);

        dl.hdr.size        = 64;
        dl.hdr.your_ref    = m->hdr.my_ref;
        dl.hdr.action_code = Wimp_MDataLoad;

        dl.data.data_load.destination_window = m->data.data_save.destination_window;
        dl.data.data_load.destination_icon   = m->data.data_save.destination_icon;
        dl.data.data_load.estimated_size     = file_size;
        dl.data.data_load.file_type          = FileType_POUT;

        _swix(OS_File,
              _INR(0,2),

              18,
              m->data.data_save.leaf_name,
              FileType_POUT);

        strcpy(dl.data.data_load.leaf_name, m->data.data_save.leaf_name);

        ChkError(wimp_send_message(Wimp_EUserMessage, &dl, m->hdr.sender, 0, NULL));
      }
    }
    break;

    case Wimp_MDataOpen:
    {
      /* Don't want to load a text file from double-clicking, */
      /* only by dragging to a window or the icon bar icon.   */

      if (m->data.data_open.file_type == FileType_TEXT) break;

      /* Now treat as a DataLoad message ready to drop */
      /* through to the Wimp_MDataLoad case statement. */
      /* This avoids duplicating the loader code.      */

      m->data.data_load.destination_window = 0; /* Force a new window to open */
      m->data.data_load.destination_icon   = -1;
      m->data.data_load.estimated_size     = 0;
    }

    /* So let the above fall through to Wimp_MDataLoad... */

    case Wimp_MDataLoad:
    {
      /* Proceed only if it's a filetype we can handle */

      if (
           m->data.data_load.file_type == FileType_HTML ||
           m->data.data_load.file_type == FileType_TEXT ||
           m->data.data_load.file_type == FileType_URI
         )
      {
        if (m->hdr.action_code == Wimp_MDataOpen)
        {
          /* If we've fallen into the DataLoad code from the DataOpen */
          /* code above, need to send a DataLoadAck message now.      */

          WimpMessage dla = *m;

          dla.hdr.sender      = task_handle;
          dla.hdr.your_ref    = m->hdr.my_ref;
          dla.hdr.action_code = Wimp_MDataLoadAck;

          ChkError(wimp_send_message(Wimp_EUserMessage, &dla, m->hdr.sender, 0, NULL));
        }

        if (m->data.data_load.destination_window <= 0)
        {
          /* Load file to icon bar - i.e. open a new window. */

          char url[2048];

          if (m->data.data_load.file_type != FileType_URI)
          {
            StrNCpy0(url, m->data.data_load.leaf_name);
            urlutils_pathname_to_url(url, sizeof(url));
          }
          else urlutils_load_uri_file(url, sizeof(url), m->data.data_load.leaf_name);

          ChkError(windows_create_browser(url, NULL, NULL, NULL));
        }
        else
        {
          /* Load file to a browser window. Need to find it's */
          /* browser_data structure.                          */

          char           url[2048];
          ObjectId       o;
          browser_data * b;

          ChkError(window_wimp_to_toolbox(0,
                                          m->data.data_load.destination_window,
                                          m->data.data_load.destination_icon,
                                          &o,
                                          NULL));

          ChkError(toolbox_get_client_handle(0, o, (void *) &b));

          /* Is the client handle a known browser_data structure? */

          if (is_known_browser(b))
          {
            /* It is, so deal with the file */

            if (m->data.data_load.file_type != FileType_URI)
            {
              StrNCpy0(url, m->data.data_load.leaf_name);
              urlutils_pathname_to_url(url, sizeof(url));
            }
            else urlutils_load_uri_file(url, sizeof(url), m->data.data_load.leaf_name);

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

    case URI_MProcess:
    {
      URIProcessMessage * uri    = (URIProcessMessage *) &m->data;
      int                 ok;
      unsigned int        sender = m->hdr.sender;

      /* Can we handle this URI? */

      ok = urlutils_check_protocols(uri->uri);

      #ifdef TRACE
        if (tl & (1u<<21)) Printf("handle_messages: URI_MProcess '%s', ok = %d\n",uri->uri,ok);
      #endif

      /* If so, reply to the message and possibly start a fetch */

      if (ok)
      {
        /* Only fetch if the flags bits don't say we're to just */
        /* check the URI could be handled.                      */

        if (!uri->flags.bits.check)
        {
          uri_queue * entry = urlutils_find_queue_entry(uri->uri_handle);

          if (entry)
          {
            ChkError(fetchpage_postprocess_uri(entry->b,
                                               uri  ->uri,
                                               entry->flags & URIQueue_RecordInHistory ? 1 : 0));

            /* Don't remove it from the queue of uri_queue structures yet - */
            /* wait for the ReturnResult message for that.                  */
          }

          else ChkError(windows_create_browser(uri->uri, NULL, NULL, NULL));
        }

        /* Now reply, saying that we've handled the message */

        m->hdr.sender      = task_handle;
        m->hdr.your_ref    = m->hdr.my_ref;
        m->hdr.action_code = URI_MProcessAck;

        ChkError(wimp_send_message(Wimp_EUserMessage, m, sender, 0, NULL));
      }
    }
    break;

    case URI_MReturnResult:
    {
      URIReturnResultMessage * uri = (URIReturnResultMessage *) &m->data;

      #ifdef TRACE
        if (tl & (1u<<21)) Printf("handle_messages: URI_MReturnResult, not_claimed = %d\n",uri->flags.bits.not_claimed);
      #endif

      /* Remove the entry from the queue */

      ChkError(urlutils_remove_from_queue(uri->uri_handle));

      /* If the URI was not claimed by anyone, give an appropriate error */

      if (uri->flags.bits.not_claimed)
      {
        erb.errnum = Utils_Error_Custom_Message;

        StrNCpy0(erb.errmess,
                 lookup_token("CannotFetch:The browser does not have a method of fetching the requested site.",
                              0,0));

        show_error_ret(&erb);
      }
    }
    break;

    case URI_MDying:
    {
      /* If the URI handler is dying, don't try and use it anymore... */

      uri_module_present = 0;
    }
    break;

    default: return 0;
  }

  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)
{
  switch (block->user_message_acknowledge.hdr.action_code)
  {
    case Browser_Message_PrintSave:
    {
      /* The PrintSave bounced, so the printer driver must not be loaded. */
      /* Since we're not printing text, the PRMs say 'go for it'...       */

      print_print(NULL);
    }
    break;

    case Wimp_MHelpRequest:
    {
      /* If a HelpRequest bounces, there's no help on the item the pointer */
      /* is over so allow the status display to go back to status again.   */

      if (fixed.claimhelp)
      {
        ObjectId                  o, a = -1;
        browser_data            * b = NULL;
        WimpGetPointerInfoBlock   i;

        wimp_get_pointer_info(&i);
        if (window_wimp_to_toolbox(0, i.window_handle, i.icon_handle, &o, NULL)) break;

        /* If we can get an ancestor, the pointer is over e.g. a toolbar */
        /* - otherwise, assume it is over a browser window.              */

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

        if (a)
        {
          toolbox_get_client_handle(0, a, (void *) &b);
        }
        else toolbox_get_client_handle(0, o, (void *) &b);

        /* If we haven't got a valid client handle, exit */

        if (!is_known_browser(b)) break;

        /* Update the status line */

        if (b->status_help != NULL)
        {
          b->status_help = NULL;
          toolbars_cancel_status(b, Toolbars_Status_Help);
        }
      }
    }

    default: return 0;
  }

  return 1;
}

/*************************************************/
/* handle_send_helpreq()                         */
/*                                               */
/* Sends out HelpRequest messages for the item   */
/* the mouse pointer is currently over, every    */
/* 20 centiseconds or so.                        */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

int handle_send_helpreq(int eventcode, WimpPollBlock * block, IdBlock * idb, void * handle)
{
  int        time_now;
  static int last_time   = 0;
  static int last_window = 0;
  static int last_icon   = 0;

  /* Only proceed if the fixed choices say to do so */

  if (fixed.claimhelp)
  {
    /* Don't sent out requests too often */

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

    if (time_now - last_time > 20)
    {
      WimpGetPointerInfoBlock i;
      WimpMessage             m;

      last_time = time_now;

      wimp_get_pointer_info(&i);

      /* Don't send a request if the pointer isn't over a */
      /* browser-owned window.                            */

      if (task_handle == task_from_window(i.window_handle))
      {
        /* Don't send out multiple requests for the same window/icon. */

        if (i.icon_handle != last_icon || i.window_handle != last_window)
        {
          last_icon   = i.icon_handle;
          last_window = i.window_handle;
        }
        else return 0;

        /* Build the message block and send the request */

        m.hdr.size        = 40;
        m.hdr.sender      = task_handle;
        m.hdr.my_ref      = 0;
        m.hdr.your_ref    = 0;
        m.hdr.action_code = Wimp_MHelpRequest;

        m.data.help_request.mouse_x       = i.x;
        m.data.help_request.mouse_y       = i.y;
        m.data.help_request.buttons       = i.button_state;
        m.data.help_request.window_handle = i.window_handle;
        m.data.help_request.icon_handle   = i.icon_handle;

        ChkError(wimp_send_message(Wimp_EUserMessageRecorded, &m, i.window_handle, i.icon_handle, NULL));
      }
    }
  }

  return 0;
}

/*************************************************/
/* 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 = b->ancestor;
    if (!ancestor) 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[MaxUrlLen];
            int  len;

            writable[0] = 0;

            writablefield_get_value(0, idb->self_id, DisplayURL, writable, sizeof(writable), &len);

            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.keyboardctl)
            {
              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 0x00d:
          {
            char url[MaxUrlLen + 1];

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

            ChkError(writablefield_get_value(0, i, DisplayURL, url, MaxUrlLen + 1, NULL));

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

            #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));  /* ...then fetch the current URL instead.      */

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

            #else

              /* Start the new fetch */

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

            #endif

            return 1;
          }
          break;
        }
      }
    }

    /* Keypress wasn't from a URL bar, but was from something obtained */
    /* within a browser window since we could get that window's tool   */
    /* bars from an ancestor object, which a main window doesn't have. */

    else
    {
      /* Proceed if the keypress wasn't from an authorisation dialogue */
      /* or a save dialogue                                            */

      if (
           idb                                            &&
           idb->self_id != authorise_return_dialogue_id() &&
           idb->self_id != save_return_dialogue_id()
         )
      {
        if (key == 0x00d && b)
        {
          // Assume it was from an Open URL dialogue for now...

          char c[MaxUrlLen + 1];

          writablefield_get_value(0, idb->self_id, OpenWritable, c, MaxUrlLen + 1, NULL);
          ChkError(fetchpage_new(b, c, 1));

          key = 0;
        }
      }
    }
  }

  /* Keypress was not from an object obtained from a browser window, */
  /* and if we couldn't find a client handle isn't from a browser    */
  /* window either.                                                  */

  else
  {
    if (!b && key == 0x00d)
    {
      /* Not a browser window; must be a URL dialogue from the icon bar */

      char c[MaxUrlLen + 1];

      writablefield_get_value(0, idb->self_id, OpenWritable, c, MaxUrlLen + 1, NULL);

      ChkError(windows_create_browser(c, NULL, NULL, NULL));

      key = 0;
    }
  }

  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 = handle->ancestor;
  if (!ancestor) 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 (choices.keephighlight) 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 = next->ancestor;
              if (!ancestor) 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 (choices.keephighlight) 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             */

        if (
             (ancestor->selected->style & IMG)       &&
             (ancestor->selected->type & TYPE_ISMAP) &&
             !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.keyboardctl)
          {
            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, choices.clearfirst);
        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_History: 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_history_menu_popup()                   */
/*                                               */
/* Handles clicks on a history menu popup item.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the history to show;  */
/*                                               */
/*             Object ID of the toolbar holding  */
/*             the popup;                        */
/*                                               */
/*             Component ID of the item that was */
/*             clicked on (DisplayMLeft or       */
/*             DisplayMenu - see TBEvents.h);    */
/*                                               */
/*             1 to show the URLs, 0 to show     */
/*             page titles where available.      */
/*************************************************/

static _kernel_oserror * handle_history_menu_popup(browser_data * b, ObjectId toolbar, ComponentId left_or_right, int show_urls)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   state;
  BBox                      menu;

  /* If there's already a menu open, close it */
  /* (so the action is to toggle the menu).   */

  if (menusrc == Menu_History && menuhdl == b)
  {
    menusrc = Menu_None;
    menuhdl = NULL;

    return wimp_create_menu((void *) -1, 0, 0);
  }

  /* Get the Wimp handle for the tool bar and get the window state */

  e = window_get_wimp_handle(0, toolbar, &state.window_handle);
  if (e) return e;

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

  /* Get the bounding box of the popup icon that was used */

  e = gadget_get_bbox(0, toolbar, left_or_right, &menu);
  if (e) return e;

  /* Convert that to screen coords ready for opening the menu */
  /* next to it.                                              */

  coords_box_toscreen(&menu, (WimpRedrawWindowBlock *) &state);

  /* The menu about to be shown is a History list, from the browser */
  /* window tool bar. Ask the History code to build the menu.       */

  if (left_or_right == DisplayMenu)
  {
    /* Build and show menu to right of menu icon for DisplayMenu object */

    e = (history_build_menu(b,
                            menu.xmax - 2,
                            menu.ymax,

                            show_urls,
                            0));
    if (e) return e;
  }
  else
  {
    /* Otherwise, show it to the left of the icon */

    e = history_build_menu(b,
                           menu.xmin - 4,
                           menu.ymin + 4,

                           show_urls,
                           1);
    if (e) return e;
  }

  return NULL;
}

/*************************************************/
/* 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)
{
  /* 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 StatsCover:   /* Clicking on a covering gadget is equivalent  */
      case DisplayStats: /* 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, DisplayURL);
        }
      }
      break;

      case DisplayMLeft: /* Drop through to DisplayMenu case */
      case DisplayMenu:
      {
        ChkError(handle_history_menu_popup(handle,
                                           idb->self_id,
                                           idb->self_component,
                                           b->mouse_click.buttons & Wimp_MouseButtonMenu ? !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;
  WimpGetPointerInfoBlock   i;
  browser_data            * ancestor = handle->ancestor;
  browser_data            * owner;

  if (!ancestor) ancestor = handle;

  owner = ancestor->selected_owner;

  /* 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.keyboardctl && 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 adjust() as this may return special information if running Full Screen. */

  adj = (fixed.ignoreadjust || eventcode < 0) ? 0 : adjust();

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

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

  if (p)
  {
    int shift;

    /* Is shift held down? */

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

    /* First - forms. */

    if (
         (p->style & FORM) &&
         (
           (p->style & INPUT)    ||
           (p->style & TEXTAREA) ||
           (p->style & SELECT)
         )
       )
    {
      ChkError(form_click_field(handle, p, 0));
      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)
             )
           )
         )
      {
        history_record_global(p->anchor);
        browser_flash_token(handle, p);
        used = 1;
      }

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

      if (!shift)
      {
        if (p->anchor)
        {
          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)
          {
            /* Image maps */

            if ((p->style & IMG) && (p->type & TYPE_ISMAP))
            {
              WimpGetWindowStateBlock   s;
              char                      coords[64];
              browser_data            * targetted;
              BBox                      box;
              int                       x, y;

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

              /* Get the image's size and position on screen */

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

              if (image_get_token_image_size(handle, p, &box)) return 0;
              if (image_get_token_image_position(handle, p, &x, &y)) return 0;

              x = coords_x_toscreen(x, (WimpRedrawWindowBlock *) &s);
              y = coords_y_toscreen(y, (WimpRedrawWindowBlock *) &s);

              /* Get the offset of the pointer position from the top left */
              /* of the image in ox and oy                                */

              ox = i.x - x;
              oy = y + (box.ymax - box.ymin) - i.y;

              if (ox >= 0 && oy >= 0)
              {
                /* Convert the coordinate to pixels and build an appropriate */
                /* CGI string including this information.                    */

                image_convert_to_pixels(handle, p, &ox, &oy);
                sprintf(coords, "?%d,%d", ox, oy);

                history_record_global(p->anchor);
                targetted = frames_find_target(handle, p);

                if (targetted || choices.full_screen)
                {
                  /* If a named target was found, open in that. Otherwise we must */
                  /* be running full screen, so can't open a new window; in this  */
                  /* case, open in the ancestor.                                  */

                  ChkError(fetchpage_new_add(targetted ? targetted : ancestor,
                                             p->anchor,
                                             1,
                                             coords,
                                             adj));
                }
                else
                {
                  /* If we've reached here, a named target wasn't found but the */
                  /* browser isn't running full screen either, so open a new    */
                  /* window with the name specified in the link.                */

                  ChkError(windows_create_browser(p->anchor,
                                                  NULL,
                                                  NULL,
                                                  p->target));
                }
              }

              used = 1;
            }

            /* Otherwise, a simple link */

            else
            {
              if (!adj)
              {
                browser_data * targetted;

                history_record_global(p->anchor);
                targetted = frames_find_target(handle, p);

                /* Don't want to ever open a new window if configured */
                /* to run full screen.                                */

                if (targetted || choices.full_screen)
                {
                  /* If a named target was found, open in that. Otherwise we must */
                  /* be running full screen, so can't open a new window; in this  */
                  /* case, open in the ancestor.                                  */

                  ChkError(fetchpage_new(targetted ? targetted : ancestor,
                                         p->anchor,
                                         1));
                }
                else
                {
                  /* If we've reached here, a named target wasn't found but the */
                  /* browser isn't running full screen either, so open a new    */
                  /* window with the name specified in the link.                */

                  ChkError(windows_create_browser(p->anchor,
                                                  NULL,
                                                  NULL,
                                                  p->target));
                }
              }

              /* Yes, this 'else' would mean that even if running */
              /* full screen, an Adjust click would open a new    */
              /* window - but note the fixed.ignoreadjust choices */
              /* option, which disables the use of adjust and can */
              /* be used in conjunction with the full screen      */
              /* option.                                          */

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

              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
//        {
//          WimpDragBox box;
//
//          box.wimp_window = handle->window_handle;
//          box.drag_type   = 12; /* Horizontal and vertical drag to scroll */
//
//          box.dragging_box.xmin = b->mouse_click.mouse_x;
//          box.dragging_box.ymin = b->mouse_click.mouse_y;
//          box.dragging_box.xmax = b->mouse_click.mouse_x;
//          box.dragging_box.ymax = b->mouse_click.mouse_y;
//
//          wimp_drag_box(&box);
//        }
      }
      else
      {
        /* If shift is held down but this isn't an image, and it's */
        /* a link, then again, follow that link.                   */
        /*                                                         */
        /* This behaviour is due to change (save link contents).   */
        /* Note that JavaScript onClick events are not activated   */
        /* if you shift+click (this is deliberate!).               */

        if (p->anchor && !(p->style & IMG))
        {
          if (!adj) ChkError(fetchpage_new(handle,
                                           p->anchor,
                                           1));

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

          used = 1;
        }

        /* If it's an image, reload it (this may load the image for */
        /* the first time if image loading had been suspended).     */

        else if (p->style & IMG)
        {
          image_reload(handle, p);
          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_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[2048];

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

  urlutils_create_home_url(home, sizeof(home));

  if (from_menu || fixed.ignoreadjust || !adjust()) ChkError(fetchpage_new(b, home, 1));
  else                                              ChkError(windows_create_browser(home, NULL, NULL, NULL));

  /* 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, ButtonBack);
  from_menu = handlers_menu_or_toolbar(idb);

  ChkError(history_fetch_backwards(b, (from_menu || fixed.ignoreadjust) ? 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, ButtonForward);
  from_menu = handlers_menu_or_toolbar(idb);

  ChkError(history_fetch_forwards(b, (from_menu || fixed.ignoreadjust) ? 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;

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

  /* If using the tristate TriState_Go_GoTo_Stop, */
  /* then stop kills everything first time. Else, */
  /* it kills the fetch, then the format, then    */
  /* the image loading each time it's used.       */

  if (b->tristate != TriState_Go_GoTo_Stop)
  {
    if (fetch_fetching(b)) fetch_cancel(b);
    else
    {
      fetch_cancel(b);
      if (reformat_formatting(b)) reformat_stop(b);
      else if (image_fetching(b)) image_delay_fetches(b);
    }
  }
  else
  {
    frames_abort_fetching(b);
  }

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

  ChkError(toolbars_set_button_states(b));

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

  if (fixed.stopwebserv && !b->ancestor) ChkError(utils_stop_webserv());

  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, ButtonReload);
  from_menu = handlers_menu_or_toolbar(idb);
  new_view  = (from_menu || fixed.ignoreadjust) ? 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 this is fails for whatever reason,     */
    /* don't forget to clear the reloadng flag before         */
    /* reporting the error.                                   */

    if (!new_view)
    {
      b->reloading = 1;
      e = fetchpage_new(b, b->urlddata, 0);

      if (e)
      {
        b->reloading = 0;
        show_error_ret(e);
      }
    }

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

    else ChkError(windows_create_browser(b->urlddata, NULL, NULL, NULL));
  }
  else
  {
    /* Otherwise, get a URL string from the URL bar and do */
    /* a fresh load of that.                               */

    char url[MaxUrlLen];

    memset(url, 0, sizeof(url));

    writablefield_get_value(0, t, DisplayURL, url, MaxUrlLen, NULL);

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

  /* 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)
{
  browser_data * b;
  char           path[2048];
  int            from_menu = 0;

  handlers_get_call_info(&b, NULL, idb, ButtonViewHot);
  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 (fixed.appendurls)
//  {
//    char url[2048];
//
//    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 (fixed.appendurls && browser_current_url(b))
  {
    int len;

    lookup_token("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 && !fixed.ignoreadjust) ChkError(windows_create_browser(path, NULL, NULL, NULL));
  else                                               ChkError(fetchpage_new(b, path, 1));

  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)
{
  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)
{
  return 0;
}

/*************************************************/
/* 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)
{
  return 0;
}

/*************************************************/
/* 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, ButtonGoTo);

  /* 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, DisplayURL);

  /* 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[MaxUrlLen + 1];
  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
  {
    writablefield_get_value(0, t, DisplayURL, url, sizeof(url), NULL);
    if (strlen(url) < MaxUrlLen)
    {
      cat[0] = c;
      cat[1] = 0;
      strcat(url, cat);
    }
  }

  writablefield_set_value(0, t, DisplayURL, 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, DisplayURL);

  /* 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, ButtonGo);

  // 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 = DisplayURL;

  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, ButtonCancel);

  /* 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 EBiStateKeyed event, which says   */
/* that the bistate button should be actioned.   */
/* 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, ButtonBi);

  /* 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 ETriStateKeyed event, which says  */
/* that the tristate button should be actioned.  */
/* 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, ButtonTri);

  /* 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_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, DisplayURL, 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, DisplayURL);

  /* 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),   */
/* presuming that gadget exists.                 */
/*                                               */
/* 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;

  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, DisplayMenu,   &test)) which = 1;
  else if (!gadget_get_bbox(0, t, DisplayMLeft,  &test)) which = 2;
  else                                                   which = 0;

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

    ChkError(handle_history_menu_popup(b,
                                       t,
                                       which == 1 ? DisplayMenu : DisplayMLeft,
                                       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)
{
  ChkError(proginfo_set_version(0,
                                idb->self_id,
                                lookup_token("Version:Unknown!",1,0)));
  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 (fixed.keepcaret)
  {
    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;
}

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