/* 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   : Main.c                                 */
/*                                                 */
/* Purpose: To run.                                */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 12-Nov-96: Created.                    */
/***************************************************/

#include "setjmp.h"
#include "signal.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <math.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 "window.h"
#include "menu.h"
#include "saveas.h"
#include "printdbox.h"
#include "gadgets.h"

#include "svcprint.h"
#include "Global.h"
#include "FromROSLib.h"
#include "TBEvents.h" /* Which includes loads of stuff... */
#include "Utils.h"

#include "Browser.h"
#include "ChoiceDefs.h"
#include "CtrlDefs.h"
#include "Encoding.h"
#include "FontManage.h"
#include "Handlers.h"
#include "History.h"
#include "ImgHistory.h"
#include "MiscDefs.h"
#include "Mouse.h"
#include "PlugIn.h"
#include "Protocols.h"
#include "RMA.h"
#include "Save.h"
#include "URLutils.h"
#include "Windows.h"

/* The following three are defined or undefined locally */

#undef INCLUDE_HEAPGRAPH
#undef INCLUDE_HIERPROF
#undef INCLUDE_MEMCHECK

#ifdef INCLUDE_HEAPGRAPH
  #include "HeapGraph.HeapGraph.h"
#endif

#ifdef INCLUDE_HIERPROF
  #define HierProf_PROFILE
  #include "HierProf:HierProf.h"
#endif

#ifdef INCLUDE_MEMCHECK
  #include "MemCheck:MemCheck.h"
#endif

/* Finally, Main.h itself */

#include "Main.h"

/* Static function prototypes */

static void              initialise_app      (void);

static _kernel_oserror * open_messages_file  (int which);
static void              close_messages_file (void * control);
static void              load_choices        (void);

#ifdef TRACE
  static void            self_test           (void);
#endif

static void              termination         (void);
static void              catch_errors        (int signum);

/* Make sure the stack starts at a reasonable size to prevent frequent */
/* stack extensions - this is a directive to the C run time system and */
/* can be removed on other systems which have no equivalent.           */

int __root_stack_size = 16384;

// /*************************************************/
// /* attach_event_handlers()                       */
// /*                                               */
// /* Called when an object is autocreated by the   */
// /* toolbox (see initialise_app). Used to get at  */
// /* the ObjectID of things without needing a      */
// /* specifically generated event to deliver it.   */
// /*************************************************/
//
// int attach_event_handlers(int eventcode,ToolboxEvent *event,IdBlock *idb,void *handle)
// {
//   ObjectId temp;
//
//   ToolboxObjectAutoCreatedEvent *c=(ToolboxObjectAutoCreatedEvent *) event;
//
//   /* We extract the relevant object Id by comparing the template name */
//   /* given in the event structure with something we know about and    */
//   /* proceeding as is relevant.                                       */
//
//   if (!strcmp(c->template_name,"Browser"))
//   {
//     main_window_id = idb->self_id;
//     show_centred(main_window_id);
//   }
//   else if (!strcmp(c->template_name,"ButtonBar"))
//   {
//     temp = idb->self_id;
//     {
//       BBox b;
//       b.xmin=0;
//       b.ymin=-144;
//       b.xmax=16384;
//       b.ymax=0;
//       set_corrected_extent(0,temp,&b);
//     }
//   }
//
//   return 1;
// }

/*************************************************/
/* initialise_app()                              */
/*                                               */
/* Initialises application as a Toolbox task.    */
/*************************************************/

static void initialise_app(void)
{
  int module_version;

  show_error(event_initialise(&idb));

  /* First handler registered is called last. This is a net to catch */
  /* miscellaneous events that might occur. Useful when there is a   */
  /* range of event codes defined for something, rather than just    */
  /* one code, for example.                                          */

  show_error(event_register_toolbox_handler(-1, -1, handle_miscellaneous_event, NULL));

  /* Register fundamental handlers and initialise as a Toolbox task */

  show_error(event_register_message_handler(Wimp_MQuit, handle_messages, NULL));
  show_error(event_register_toolbox_handler(-1, Toolbox_Error, report_toolbox_error, NULL));

  /* Check the Wimp version; for window managers of 3.8 and above, */
  /* declare a minimum version of 3.80 so borderless windows can   */
  /* still have tools. Otherwise, only ask for 3.1.                */

  _swix(Wimp_ReadSysInfo, _IN(0) | _OUT(0), 7, &module_version);

  if (module_version >= 387) nested_wimp = 1;
  else                       nested_wimp = 0;

  show_error(toolbox_initialise(0,
                                nested_wimp ? WIMPMINH : WIMPMINL,
                                messages_list,
                                event_code_list,
                                task_dir,
                                &meb,
                                &idb,
                                &wimp_version,
                                &task_handle,
                                (void *) &sprite_block));

  /* Open the Choices and Controls files */

  cob = NULL;
  chb = NULL;

  show_error(open_messages_file(0));
  show_error(open_messages_file(1));

  /* If either failed, bomb out. Can't raise a custom error as we have no */
  /* messages file to read it from...                                     */

  if (!cob || !chb) exit(EXIT_FAILURE);

  /* If the system variable 'Browse$IssueDesktopCommand' is set to 'yes', */
  /* then the AcornURI and/or TaskModule modules were started in !Run and */
  /* we must issue a *Desktop command to start their task components.     */

  {
    char combuf[96];

    sprintf(combuf, "If \"<Browse$IssueDesktopCommand>\" = \"yes\" Then WimpTask Desktop\r\n");

    _swix(OS_CLI, /* Don't want to hear about any errors */
          _IN(0),

          combuf);
  }

  /* Quit menu items are set to give the Quit_Quit event type, as */
  /* well as this event possibly being delivered by the Toolbox   */
  /* from elsewhere.                                              */

  show_error(event_register_toolbox_handler(-1, Quit_Quit, handle_quit, NULL));

  /* Called before the application's Info box is shown */

  show_error(event_register_toolbox_handler(-1, ProgInfo_AboutToBeShown, handle_show_info, NULL));

  /* Opening and closing windows */

  show_error(event_register_toolbox_handler(-1, EOpenNewWindow, windows_new_browser,  NULL));
  show_error(event_register_toolbox_handler(-1, ECloseWindow,   windows_shut_browser, NULL));

  /* Opening the Open URL dialogue */

  show_error(event_register_toolbox_handler(-1, EOpenToBeShownMisc, openurl_to_be_shown,       NULL));
  show_error(event_register_toolbox_handler(-1, EOpenToBeShownMenu, openurl_to_show_from_menu, NULL));

  /* Opening and closing the Choices dialogue */

  show_error(event_register_toolbox_handler(-1, ECDToBeShown, choices_to_be_shown, NULL));
  show_error(event_register_toolbox_handler(-1, ECDHidden,    choices_hidden,      NULL));

  /* For the Find dialogue */

  show_error(event_register_toolbox_handler(-1, EFindToBeShown, find_to_be_shown, NULL));
  show_error(event_register_toolbox_handler(-1, EFindHidden,    find_hidden,      NULL));

  /* Print and Print Style dialogues */

  show_error(event_register_toolbox_handler(-1, EPSToBeShown,             printstyle_to_be_shown, NULL));
  show_error(event_register_toolbox_handler(-1, PrintDbox_AboutToBeShown, print_to_be_shown,      NULL));

  /* Called whenever a menu item is selected */

  show_error(event_register_toolbox_handler(-1, Menu_Selection, menus_item_selected, NULL));

  /* Support the Help menu */

  show_error(event_register_toolbox_handler(-1, EHelpFromHelpString, menus_help_from_help_string, NULL));
  show_error(event_register_toolbox_handler(-1, EHelpReleaseNotes,   menus_help_release_notes,    NULL));
  show_error(event_register_toolbox_handler(-1, EHelpAboutPage,      menus_help_about_page,       NULL));

  /* Before showing and after closing menus */

  show_error(event_register_toolbox_handler(-1, EMainToBeShown,     menus_show_main,     NULL));
  show_error(event_register_toolbox_handler(-1, EUtilsToBeShown,    menus_show_utils,    NULL));
  show_error(event_register_toolbox_handler(-1, EExportToBeShown,   menus_show_export,   NULL));
  show_error(event_register_toolbox_handler(-1, EChoicesToBeShown,  menus_show_choices,  NULL));
  show_error(event_register_toolbox_handler(-1, EFileToBeShown,     menus_show_file,     NULL));
  show_error(event_register_toolbox_handler(-1, ENavigateToBeShown, menus_show_navigate, NULL));
  show_error(event_register_toolbox_handler(-1, EDocumentToBeShown, menus_show_document, NULL));
  show_error(event_register_toolbox_handler(-1, EDocumentHidden,    menus_hide_document, NULL));
  show_error(event_register_toolbox_handler(-1, EEncodingToBeShown, encoding_show_menu,  NULL));
  show_error(event_register_toolbox_handler(-1, EHistoryToBeShown,  menus_show_history,  NULL));

  /* Called when the 'Cache' option is chosen from the main menu. */

  show_error(event_register_toolbox_handler(-1, EMainCache, menus_item_selected, NULL));

  /* Called when a selection is made from the Encoding menu. */

  show_error(event_register_toolbox_handler(-1, EEncodingSelect, encoding_select, NULL));

  /* Called when the user selects "From document" in the */
  /* Encoding menu.                                      */

  show_error(event_register_toolbox_handler(-1, EEncodingFromDocument, encoding_from_document_select, NULL));

  /* General key press handler */

  show_error(event_register_wimp_handler(-1, Wimp_EKeyPressed, handle_keys, NULL));

  /* Wimp handler for menu selections in forms etc. */

  show_error(event_register_wimp_handler(-1, Wimp_EMenuSelection, handle_menus, NULL));

  /* LoseCaret event handler for grabbing the caret back */

  show_error(event_register_wimp_handler(-1, Wimp_ELoseCaret, handle_lose_caret, NULL));

  /* Pointer checking */

  show_error(event_register_wimp_handler(-1, Wimp_EPointerEnteringWindow, browser_pointer_entering, NULL));
  show_error(event_register_wimp_handler(-1, Wimp_EPointerLeavingWindow,  browser_pointer_leaving,  NULL));

  /* Related to that, end of drag handling */

  show_error(event_register_wimp_handler(-1, Wimp_EUserDrag, (WimpEventHandler *) handle_drags, NULL));

  /* General Wimp message handling */

  show_error(event_register_message_handler(Wimp_MModeChange,   handle_messages, NULL));
  show_error(event_register_message_handler(Wimp_MDataLoad,     handle_messages, NULL));
  show_error(event_register_message_handler(Wimp_MDataLoadAck,  handle_messages, NULL));
  show_error(event_register_message_handler(Wimp_MDataSave,     handle_messages, NULL));
  /* (DataSaveAck is registered in the Printing section below) */
  show_error(event_register_message_handler(Wimp_MDataOpen,     handle_messages, NULL));
  show_error(event_register_message_handler(Wimp_MRAMFetch,     handle_messages, NULL));
  show_error(event_register_message_handler(Wimp_MRAMTransmit,  handle_messages, NULL));
  show_error(event_register_message_handler(Wimp_MMenusDeleted, handle_messages, NULL));

  /* ANT protocols */

  show_error(event_register_message_handler(Message_ANTOpenURL, handle_messages, NULL));

  /* Plug-In protocol */

  show_error(event_register_message_handler(Message_PlugIn_Open,           handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_Opening,        handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_Close,          handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_Closed,         handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_URLAccess,      handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_StreamNew,      handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_ReshapeRequest, handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_Status,         handle_messages, NULL));
  show_error(event_register_message_handler(Message_PlugIn_Busy,           handle_messages, NULL));

  /* URI handler message handling */

  show_error(event_register_message_handler(URI_MDying,        handle_messages, NULL));
  show_error(event_register_message_handler(URI_MProcess,      handle_messages, NULL));
  show_error(event_register_message_handler(URI_MReturnResult, handle_messages, NULL));

  /* AppControl message handling */

  show_error(event_register_message_handler(Wimp_MAppControl, handle_messages, NULL));

  /* Printing related message handlers */

  show_error(event_register_message_handler(Browser_Message_PrintError,   handle_messages, NULL));
  show_error(event_register_message_handler(Browser_Message_PrintSave,    handle_messages, NULL));
  show_error(event_register_message_handler(Browser_Message_PrintTypeOdd, handle_messages, NULL));

  show_error(event_register_message_handler(Wimp_MDataSaveAck,            handle_messages, NULL));

  /* For message bounces */

  show_error(event_register_wimp_handler(-1, Wimp_EUserMessageAcknowledge, handle_ack, NULL));

  /* Debug build event handlers */

  #ifdef TRACE

    show_error(event_register_toolbox_handler(-1, ETraceTokenDumpByLine,   trace_dump_tokens_by_line,   NULL));
    show_error(event_register_toolbox_handler(-1, ETraceTokenDumpByStream, trace_dump_tokens_by_stream, NULL));

  #endif

  /* Event handlers for menu items that relate to toolbar buttons. */
  /* This list needs to be kept in sync with the specific list in  */
  /* Windows.c, which allows buttons in specific windows to use    */
  /* the same event codes.                                         */

  show_error(event_register_toolbox_handler(-1, EButtonBarHome,          handle_home,           NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarBack,          handle_back,           NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarForward,       handle_forwards,       NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarReload,        handle_reload,         NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarStop,          handle_stop,           NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarViewHotlist,   handle_view_hotlist,   NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarAddToHotlist,  handle_add_hotlist,    NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarViewResources, handle_view_resources, NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarLoadImages,    handle_load_images,    NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarViewSource,    handle_view_source,    NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarGoTo,          handle_go_to,          NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarGo,            handle_go,             NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarCancel,        handle_cancel,         NULL));

  show_error(event_register_toolbox_handler(-1, EButtonBarBistate,       handle_bistate,        NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarTristate,      handle_tristate,       NULL));

  show_error(event_register_toolbox_handler(-1, EButtonBarSaveSource,    handle_save_source,    NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarPrint,         handle_print,          NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarSaveAsText,    handle_save_as_text,   NULL));
  show_error(event_register_toolbox_handler(-1, EButtonBarSaveAsDraw,    handle_save_as_draw,   NULL));

  /* Miscellaneous event handlers for keyboard control of some functions */

  show_error(event_register_toolbox_handler(-1, EURLBarClearURL,      handle_clear_url,         NULL));
  show_error(event_register_toolbox_handler(-1, EURLBarToggleHistory, handle_show_history_menu, NULL));

  /* Event handlers for saving (most are registered when the dialogue opens) */

  show_error(event_register_toolbox_handler(-1, ESaveFileToBeShown,   savefile_to_be_shown,     NULL));
  show_error(event_register_toolbox_handler(-1, ESaveObjectToBeShown, saveobject_to_be_shown,   NULL));

  /* Event handlers for the Item Information dialogue */

  show_error(event_register_toolbox_handler(-1, EIIToBeShown,  iteminfo_to_be_shown, NULL));
  show_error(event_register_toolbox_handler(-1, EIIFollowLink, iteminfo_follow_link, NULL));
  show_error(event_register_toolbox_handler(-1, EIIExportItem, iteminfo_export_item, NULL));

  /* Wake up flex */

  strncpy(program_name,
          lookup_token("_TaskName", 1, 0),
          sizeof(program_name));

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

  flex_init(program_name, NULL, 0x3000000);

  flex_set_budge(1);
  flex_set_deferred_compaction(1);

  /* Initialise the HTML library. This is to set up some initial */
  /* data which can't be initialised in headers due to ROM build */
  /* considerations.                                             */

  HtmlInit();

  /* Similarly, initialise ImageLib */

  ImageLib_Init();

  /* Initialise FromROSLib routines */

  wimpt_read();

  /* Initialise Utils routines */

  read_os_to_points();

  #ifndef REMOTE_HOTLIST

    /* Initialise the hotlist */

    show_error(hotlist_initialise());

  #endif

  /* Initialise the encoding menu handler */

  show_error(encoding_init());

  /* Find the total number of animation frames for the status bar */

  animation_frames = 0;
  {
    char v[10];

    /* SpriteOp 40 is Read Info; it's just something that will give   */
    /* an error if the sprite doesn't exist.                          */

    do
    {
      sprintf(v, "a%d\0", animation_frames ++);
    }
    while (
            animation_frames < Limits_Misc_AnimFrames &&
            !(_swix(OS_SpriteOp,
                    _INR(0,2),

                    296,
                    sprite_block,
                    v))
          );

    /* animation_frames is incremented for every sprite looked at, */
    /* including the last one, which must not be found. So need to */
    /* subtract 1 now, to make it equal the number of frames.      */

    animation_frames --;
  }

  /* Similarly, find the number of bullets available */

  bullets = 0;
  {
    char v[10];

    do
    {
      sprintf(v,"b%d\0",bullets++);
    }
    while (
            bullets < Limits_Misc_Bullets &&
            !(_swix(OS_SpriteOp,
                    _INR(0,2),

                    296,
                    sprite_block,
                    v))
          );

    bullets --;
  }

  /* Is the URI handler available? */

  {
    int version;

    if (uri_version(0, &version)) uri_module_present = 0;
    else if (version >= 5)        uri_module_present = 1;
  }

  /* Find out window tool sizes */

  show_error(windows_initialise_tool_sizes());
}

/*************************************************/
/* open_messages_file()                          */
/*                                               */
/* Asks MessageTrans to open a Messages file.    */
/* Looks through a system variable for the path  */
/* to find the file in before going to a         */
/* default - see the code comments for more      */
/* information.                                  */
/*                                               */
/* Parameters: 0 to load the Choices file, or 1  */
/*             to load the Controls file.        */
/*************************************************/

static _kernel_oserror * open_messages_file(int which)
{
  _kernel_oserror * e;
  MessagesFD      * control;
  char            * path = NULL;

  /* Work out what to open. */

  path = save_build_messages_path(which);

  if (!path) goto open_messages_file_no_memory;

  /* Claim RMA for the control block and pathname */

  e = rma_claim(NULL, sizeof(MessagesFD) + strlen(path) + 1, (void *) &control);

  if (e)
  {
    free(path);

    return e;
  }

  /* Update the relevant global. No need for a default case as */
  /* save_build_messages_path will have caught that.           */

  switch (which)
  {
    case 0: chb = control; break;
    case 1: cob = control; break;
  }

  /* Register the block with MemCheck if required, and copy */
  /* the pathname into it.                                  */

  #ifdef INCLUDE_MEMCHECK
    MemCheck_RegisterMiscBlock((void *) control, sizeof(MessagesFD) + strlen(path) + 1);
  #endif

  strcpy((char *) ((int) control + sizeof(MessagesFD)), path);

  /* Don't need the path now */

  free(path);
  path = NULL;

  /* Open the file */

  return _swix(MessageTrans_OpenFile,
               _INR(0,2),

               control,                            /* Pointer to control block */
               (int) control + sizeof(MessagesFD), /* Filename                 */
               0);                                 /* Buffer in RMA            */

  /* Error condition exit */

open_messages_file_no_memory:

  if (control) rma_release(NULL, control);

  if (path) free(path);

  return NULL;
}

/*************************************************/
/* close_messages_file()                         */
/*                                               */
/* Closes a messges file and releases the RMA    */
/* space claimed for it.                         */
/*                                               */
/* Parameters: Pointer to the allocated chunk of */
/*             RMA space holding the             */
/*             MessageTrans control block and    */
/*             Messages file pathname.           */
/*************************************************/

static void close_messages_file(void * control)
{
  /* If the file won't close for some reason, */
  /* MessageTrans may still want to access it */
  /* - so safest *not* to release the RMA     */
  /* holding the control block and filename.  */

  if (
       _swix(MessageTrans_CloseFile,
             _IN(0),

             control)
     )
     return;

  /* Release the claimed RMA space holding the */
  /* control block and messages file name.     */

  rma_release(NULL, control);
}

/*************************************************/
/* colour_table_leaf_to_path()                   */
/*                                               */
/* Called by ImageLib. Print a full lookup table */
/* file pathname into path given the leafname.   */
/*                                               */
/* Parameters: Pointer to output buffer for full */
/*             pathname;                         */
/*                                               */
/*             Pointer to leaf name.             */
/*                                               */
/* Assumes:    Output buffer big enough :)       */
/*************************************************/

void colour_table_leaf_to_path(char *path, const char *leaf)
{
  sprintf(path, "%s.%s", task_dir, leaf);
}

/*************************************************/
/* load_choices()                                */
/*                                               */
/* Reads in the choices from the Messages file,  */
/* filling in the global_choices structure,      */
/* 'choices' (see Global.c and Global.h).        */
/*************************************************/

static void load_choices(void)
{
  /* First, the user-configurable options from the Choices file. */

  /* Set the default home page */

  choices.home_page = malloc(strlen(lookup_choice("HomePage:http://www.acorn.co.uk/", 0, 0)) + 1);

  if (choices.home_page) strcpy(choices.home_page, tokens);
  else show_error(make_no_memory_error(32));

  /* Get the visit history, image history and hotlist paths */
  /* by a similar mechanism.                                */

  /* Image history */

  choices.image_history_path = malloc(strlen(lookup_choice("ImageHistoryPath:Browse:User.Images", 0, 0)) + 1);

  if (choices.image_history_path) strcpy(choices.image_history_path, tokens);
  else show_error(make_no_memory_error(32));

  /* Visit history */

  choices.history_path = malloc(strlen(lookup_choice("HistoryPath:Browse:User.History", 0, 0)) + 1);

  if (choices.history_path) strcpy(choices.history_path, tokens);
  else show_error(make_no_memory_error(32));

  /* Hotlist */

  choices.hotlist_path = malloc(strlen(lookup_choice("HotlistPath:Browse:User.Hotlist",0,0)) + 1);

  if (choices.hotlist_path) strcpy(choices.hotlist_path, tokens);
  else show_error(make_no_memory_error(32));

  /* Set the various default colours */

  choices.background_colour = (unsigned int) strtoul(lookup_choice("BackColour:0xdddddd00", 0, 0), NULL, 16);
  choices.text_colour       = (unsigned int) strtoul(lookup_choice("TextColour:0x00000000", 0, 0), NULL, 16);
  choices.link_colour       = (unsigned int) strtoul(lookup_choice("LinkColour:0xff000000", 0, 0), NULL, 16);
  choices.used_colour       = (unsigned int) strtoul(lookup_choice("UsedColour:0xbb008800", 0, 0), NULL, 16);
  choices.followed_colour   = (unsigned int) strtoul(lookup_choice("FollColour:0x0000ff00", 0, 0), NULL, 16);
  choices.selected_colour   = (unsigned int) strtoul(lookup_choice("SeleColour:0x00bb0000", 0, 0), NULL, 16);

  /* Tables */

  if      (!strcmp(lookup_choice("SupportTables:yes", 0, 0),"yes"))    choices.support_tables = 1;

  if      (!strcmp(lookup_choice("TableOuter",        0, 0), "2d"))    choices.table_outer    = Choices_TableOuter_Always2D;
  else if (!strcmp(lookup_choice("TableOuter",        0, 0), "3d"))    choices.table_outer    = Choices_TableOuter_Always3D;
  else if (!strcmp(lookup_choice("TableOuter",        0, 0), "never")) choices.table_outer    = Choices_TableOuter_Never;
  else                                                                 choices.table_outer    = Choices_TableOuter_Auto;

  if      (!strcmp(lookup_choice("TableInner",        0, 0), "2d"))    choices.table_inner    = Choices_TableInner_Always2D;
  else if (!strcmp(lookup_choice("TableInner",        0, 0), "3d"))    choices.table_inner    = Choices_TableInner_Always3D;
  else if (!strcmp(lookup_choice("TableInner",        0, 0), "never")) choices.table_inner    = Choices_TableInner_Never;
  else                                                                 choices.table_inner    = Choices_TableInner_Auto;

  /* Font usage */

  choices.font_size = atoi(lookup_choice("FontSize:12", 0, 0));

  if (choices.font_size < 6  * 16) choices.font_size = 6  * 16;
  if (choices.font_size > 24 * 16) choices.font_size = 24 * 16;

  choices.tt_aspect = atoi(lookup_choice("TTAspect:90", 0, 0));

  if (choices.tt_aspect < 50)  choices.tt_aspect = 50;
  if (choices.tt_aspect > 150) choices.tt_aspect = 150;

  if (!strcmp(lookup_choice("SystemFont:no", 0, 0),"yes")) choices.system_font = 1;
  if (choices.system_font) choices.font_size = FM_Standard_Size;

  /* Wake up the font library */

  fm_init(choices.system_font, choices.font_size);

  show_error(fm_define_default_typefaces());

  /* Look up the typeface definitions */

  {
    int  face;
    char tokenname[12];

    /* Arbitrary limit... Typeface1 to Typeface99 */

    for (face = 1; face < 100; face ++)
    {
      sprintf(tokenname, "Typeface%d", face);

      /* Look up the token, exit if it fails */

      lookup_choice(tokenname, 0, 0);
      if (*tokens == '!') break;

      /* Otherwise, define the typeface */

      show_error(fm_define_typeface(tokens));
    }
  }

  /* Claim basic typefaces based on the above */

  fm_claim_basic_typefaces(choices.font_size);

  /* Default document encoding */

  choices.encoding = atoi(lookup_choice("Encoding:4",  0, 0));

  /* Page display */

  if (!strcmp(lookup_choice("UnderlineLinks:yes", 0, 0), "yes")) choices.underline_links = 1;
  if (!strcmp(lookup_choice("UseSourceCols:yes",  0, 0), "yes")) choices.use_source_cols = 1;
  if (!strcmp(lookup_choice("ShowForeground:yes", 0, 0), "yes")) choices.show_foreground = 1;
  if (!strcmp(lookup_choice("ShowBackground:yes", 0, 0), "yes")) choices.show_background = 1;

  /* Page layout */

  choices.left_margin  = atoi(lookup_choice("LeftMargin:1600",   0, 0));
  choices.right_margin = atoi(lookup_choice("RightMargin:6400",  0, 0));
  choices.quote_margin = atoi(lookup_choice("QuoteMargin:19200", 0, 0));
  choices.leading      = atoi(lookup_choice("Leading:4",         0, 0));
  choices.left_indent  = atoi(lookup_choice("LeftIndent:12800",  0, 0));

  /* Fetch controls */

  /* Limit the number of simultaneous image fetches (I recommend */
  /* a minimum of 2, rather than 1, as most efficient).          */

  choices.maximages = atoi(lookup_choice("MaxImages:5", 0, 0));
  if (choices.maximages <= 0) choices.maximages = 1;

  if (!strcmp(lookup_choice("ClientPull:yes",    0, 0), "yes")) choices.client_pull    = 1;
  if (!strcmp(lookup_choice("SupportFrames:yes", 0, 0), "yes")) choices.support_frames = 1;
  if (!strcmp(lookup_choice("SupportObject:yes", 0, 0), "yes")) choices.support_object = 1;

  if      (!strcmp(lookup_choice("PlugInControl", 0, 0), "never"))  choices.plugin_control = Choices_PlugIns_Never;
  else if (!strcmp(lookup_choice("PlugInControl", 0, 0), "viewed")) choices.plugin_control = Choices_PlugIns_Viewed;
  else                                                              choices.plugin_control = Choices_PlugIns_ASAP;

  if (!strcmp(lookup_choice("SeeFetches:no", 0, 0), "yes")) choices.see_fetches = 1;

  /* Hotlist controls */

  if      (!strcmp(lookup_choice("SaveHotlist", 0, 0), "never"))  choices.save_hotlist = Choices_SaveHotlist_Never;
  else if (!strcmp(lookup_choice("SaveHotlist", 0, 0), "always")) choices.save_hotlist = Choices_SaveHotlist_Always;
  else                                                            choices.save_hotlist = Choices_SaveHotlist_Once;

  if (!strcmp(lookup_choice("AddHotlist", 0, 0), "bottom")) choices.add_hotlist  = Choices_AddHotlist_Bottom;
  else                                                      choices.add_hotlist  = Choices_AddHotlist_Top;

  if (!strcmp(lookup_choice("HotlistType", 0, 0), "urls"))  choices.hotlist_show = Choices_HotlistType_URLs;
  else                                                      choices.hotlist_show = Choices_HotlistType_Descriptions;

  choices.auto_open_delay = atoi(lookup_choice("AutoOpenDelay:100", 0, 0));
  if (choices.auto_open_delay > 1000) choices.auto_open_delay = 1000;
  if (choices.auto_open_delay < 0)    choices.auto_open_delay = 0;

  choices.auto_scroll_delay = atoi(lookup_choice("AutoScrollDelay:50", 0, 0));
  if (choices.auto_scroll_delay > 1000) choices.auto_scroll_delay = 1000;
  if (choices.auto_scroll_delay < 0)    choices.auto_scroll_delay = 0;

  choices.auto_scroll_margin = atoi(lookup_choice("AutoScrollMargin:48", 0, 0));
  if (choices.auto_scroll_margin > 256) choices.auto_scroll_margin = 256;
  if (choices.auto_scroll_margin < 0)   choices.auto_scroll_margin = 0;

  /* History limits */

  choices.max_size         = atoi(lookup_choice("MaxSize:16",             0, 0));
  choices.image_max_size   = atoi(lookup_choice("ImageMaxSize:0",         0, 0));
  choices.expiry_age       = atoi(lookup_choice("ExpiryAge:604800",       0, 0));
  choices.image_expiry_age = atoi(lookup_choice("ImageExpiryAge:1209600", 0, 0));

  choices.max_size         *= 1024; /* (Convert K to bytes)       */
  choices.image_max_size   *= 1024;

  if (choices.max_size   < 2048 && choices.max_size)   choices.max_size   = 2048; /* (NB, Note that there's no lower limit placed on image history details) */
  if (choices.expiry_age < 60   && choices.expiry_age) choices.expiry_age = 60;

  if (!strcmp(lookup_choice("ShowURLs:no", 0, 0), "yes")) choices.show_urls = 1;

  if      (!strcmp(lookup_choice("SaveHistory", 0, 0), "never"))  choices.save_history = Choices_SaveHistory_Never;
  else if (!strcmp(lookup_choice("SaveHistory", 0, 0), "always")) choices.save_history = Choices_SaveHistory_Always;
  else                                                            choices.save_history = Choices_SaveHistory_Once;

  if      (!strcmp(lookup_choice("SaveImageHistory", 0, 0), "never"))  choices.save_image_history = Choices_SaveImageHistory_Never;
  else if (!strcmp(lookup_choice("SaveImageHistory", 0, 0), "always")) choices.save_image_history = Choices_SaveImageHistory_Always;
  else                                                                 choices.save_image_history = Choices_SaveImageHistory_Once;

  /* Toolbar controls */

  if (!strcmp(lookup_choice("URLbar:yes",    0, 0), "yes")) choices.url_bar    = 1;
  if (!strcmp(lookup_choice("ButtonBar:yes", 0, 0), "yes")) choices.button_bar = 1;
  if (!strcmp(lookup_choice("StatusBar:yes", 0, 0), "yes")) choices.status_bar = 1;

  if      (!strcmp(lookup_choice("MoveGadgets", 0, 0), "never"))  choices.move_gadgets = Choices_MoveGadgets_Never;
  else if (!strcmp(lookup_choice("MoveGadgets", 0, 0), "at end")) choices.move_gadgets = Choices_MoveGadgets_AtEnd;
  else                                                            choices.move_gadgets = Choices_MoveGadgets_During;

  /* Window controls */

  choices.width      = atoi(lookup_choice("Width:1024",  0, 0));
  choices.height     = atoi(lookup_choice("Height:1280", 0, 0));
  choices.override_x = atoi(lookup_choice("OverrideX:0",    0, 0));
  choices.override_y = atoi(lookup_choice("OverrideY:0",    0, 0));

  if      (!strcmp(lookup_choice("SolidResize", 0, 0), "no"))     choices.solid_resize = Choices_SolidResize_No;
  else if (!strcmp(lookup_choice("SolidResize", 0, 0), "always")) choices.solid_resize = Choices_SolidResize_Always;
  else                                                            choices.solid_resize = Choices_SolidResize_Yes;

  if (!strcmp(lookup_choice("FullScreen:no", 0, 0), "yes")) choices.full_screen = 1;

  if      (!strcmp(lookup_choice("HScroll", 0, 0), "no"))  choices.h_scroll = Choices_HScroll_No;
  else if (!strcmp(lookup_choice("HScroll", 0, 0), "yes")) choices.h_scroll = Choices_HScroll_Yes;
  else                                                     choices.h_scroll = Choices_HScroll_Auto;

  if      (!strcmp(lookup_choice("VScroll", 0, 0), "no"))  choices.v_scroll = Choices_HScroll_No;
  else if (!strcmp(lookup_choice("VScroll", 0, 0), "yes")) choices.v_scroll = Choices_HScroll_Yes;
  else                                                     choices.v_scroll = Choices_HScroll_Auto;

  /* Reformatter controls */

  if (!strcmp(lookup_choice("RefoWait:no", 0, 0), "yes")) choices.refo_wait = 1;
  if (!strcmp(lookup_choice("RefoHang:no", 0, 0), "yes")) choices.refo_hang = 1;

  choices.refo_time = atoi(lookup_choice("RefoTime:500", 0, 0));
  if (choices.refo_time < 25)   choices.refo_time = 25;
  if (choices.refo_time > 2000) choices.refo_time = 2000;

  /* Input device controls */

  if (!strcmp(lookup_choice("FixedPtr:yes",    0, 0), "yes")) choices.fixed_pointer     = 1;
  if (!strcmp(lookup_choice("HighlightLks:no", 0, 0), "yes")) choices.highlight_links = 1;
  if (!strcmp(lookup_choice("KeyboardCtl:no",  0, 0), "yes")) choices.keyboard_ctrl  = 1;

  /* Multiuser environments and proxying */

  if (!strcmp(lookup_choice("UseProxy:no",   0, 0), "yes")) choices.use_proxy   = 1;
  if (!strcmp(lookup_choice("StartProxy:no", 0, 0), "yes")) choices.start_proxy = 1;

  lookup_choice("ProxyAddress:http://127.0.0.1/", 0, 0);
  choices.proxy_address = malloc(strlen(tokens) + 1);
  if (choices.proxy_address)
  {
    strcpy(choices.proxy_address, tokens);
  }
  else
  {
    show_error(make_no_memory_error(103));
  }

  if (!strcmp(lookup_choice("Clone:yes",   0, 0), "yes")) choices.clone     = 1;

  #ifndef SINGLE_USER

    /* If compiling for a multiuser environment, set up */
    /* a post_in and a post_out path.                   */

    choices.post_in = malloc(strlen(lookup_choice("PostIn:<none>", 0, 0)) + 1);

    if (choices.post_in) strcpy(choices.post_in, tokens);
    else show_error(make_no_memory_error(32));

    choices.post_out = malloc(strlen(lookup_choice("PostOut:<none>", 0, 0)) + 1);

    if (choices.post_out) strcpy(choices.post_out, tokens);
    else show_error(make_no_memory_error(32));

    /* Set also the server timeout */

    choices.log_in_timeout = atoi(lookup_choice("LITimeout:30", 0, 0));

    if (choices.log_in_timeout < 20)  choices.log_in_timeout = 20;
    if (choices.log_in_timeout > 120) choices.log_in_timeout = 120;

  #endif

  /* Non user-configurable options from the Controls file */

  /* Animation controls */

  controls.anim_delay = atoi(lookup_control("AnimSpeed:4", 0, 0));

  if (!strcmp(lookup_control("AnimDrift:no", 0, 0), "yes")) controls.anim_drift = 1;
  if (!strcmp(lookup_control("DBoxAnims:no", 0, 0), "yes")) controls.dbox_anims = 1;

  /* Main window and general toolbar controls */

  controls.minimum_convergence = atoi(lookup_control("MinConvergence:480", 0, 0));

  if      (!strcmp(lookup_control("DontGrey", 0, 0), "none"))    controls.dont_grey = Controls_DontGrey_GreyNone;
  else if (!strcmp(lookup_control("DontGrey", 0, 0), "history")) controls.dont_grey = Controls_DontGrey_GreyHistoryOnly;
  else                                                           controls.dont_grey = Controls_DontGrey_GreyAll;

  if (!strcmp(lookup_control("SwapBars:no",   0, 0), "yes")) controls.swap_bars   = 1;
  if (!strcmp(lookup_control("BackWindow:no", 0, 0), "yes")) controls.back_window = 1;
  if (!strcmp(lookup_control("UseSmall:yes",  0, 0), "yes")) controls.use_small   = 1;

  /* Main and dialler status controls */

  if (!strcmp(lookup_control("ClaimHelp:no", 0, 0), "yes")) controls.claim_help = 1;

  controls.show_help_for         = atoi(lookup_control("ShowHelpFor:600",   0, 0));
  controls.show_dstat_for        = atoi(lookup_control("ShowDStatFor:300",  0, 0));
  controls.show_links_for        = atoi(lookup_control("ShowLinksFor:200",  0, 0));
  controls.show_misc_for         = atoi(lookup_control("ShowMiscFor:50",    0, 0));
  controls.quantise              = atoi(lookup_control("Quantise:5",        0, 0));
  controls.progress_update_delay = atoi(lookup_control("ProgressDelay:50",  0, 0));

  /* Progress indicator controls */

  if (!strcmp(lookup_control("AppendStatus:no", 0, 0), "yes")) controls.append_status = 1;
  if (!strcmp(lookup_control("UseBrackets:yes", 0, 0), "yes")) controls.use_brackets  = 1;

  /* The ColourProgress option in Controls is a little unusual; it holds */
  /* 'no' (NotAColour, see CtrlDefs.h) or a Wimp colour number. Default  */
  /* to 11 (red, in the standard Wimp palette).                          */

  if (!strcmp(lookup_control("ColourProgress:11", 0, 0), "no")) controls.colour_progress = Controls_ColourProgress_NotAColour;
  else
  {
    controls.colour_progress = atoi(lookup_control("ColourProgress:11", 0, 0));
    if (controls.colour_progress > 15) controls.colour_progress = 11;
  }

  /* Frame controls */

  controls.minimum_frame_height = atoi(lookup_control("MinFrmHeight:48", 0, 0));
  controls.minimum_frame_width  = atoi(lookup_control("MinFrmWidth:48",  0, 0));

  if (!strcmp(lookup_control("KeepHighlight:no", 0, 0), "yes")) controls.keep_highlight = 1;

  /* Input device controls */

  if (!strcmp(lookup_control("KeepCaret:no",    0, 0), "yes")) controls.keep_caret    = 1;
  if (!strcmp(lookup_control("ClearFirst:yes",  0, 0), "yes")) controls.clear_first   = 1;
  if (!strcmp(lookup_control("LockToLine:no",   0, 0), "yes")) controls.lock_to_line  = 1;
  if (!strcmp(lookup_control("IgnoreAdjust:no", 0, 0), "yes")) controls.ignore_adjust = 1;

  /* Remote hotlist support */

  if (!strcmp(lookup_control("AppendURLs:no", 0, 0), "yes")) controls.append_urls = 1;

  /* Fetch controls */

  if (!strcmp(lookup_control("BrickWall:no",    0, 0), "yes")) controls.brick_wall  = 1;
  if (!strcmp(lookup_control("StopWebProxy:no", 0, 0), "yes")) controls.stop_proxy  = 1;
  if (!strcmp(lookup_control("RefoSingle:no",   0, 0), "yes")) controls.refo_single = 1;

  controls.back_off_at = atoi(lookup_control("BackOffAt:128", 0, 0));

  /* Mouse pointer active point offsets */

  {
    int offset;

    offset = atoi(lookup_control("PtrLnkActvX:5", 0,0)); controls.ptrlnkactvx  = (char) offset;
    offset = atoi(lookup_control("PtrLnkActvY:1", 0,0)); controls.ptrlnkactvy  = (char) offset;
    offset = atoi(lookup_control("PtrMapActvX:7", 0,0)); controls.ptrmapactvx  = (char) offset;
    offset = atoi(lookup_control("PtrMapActvY:7", 0,0)); controls.ptrmapactvy  = (char) offset;
    offset = atoi(lookup_control("PtrUDActvX:5",  0,0)); controls.ptrudactvx   = (char) offset;
    offset = atoi(lookup_control("PtrUDActvY:8",  0,0)); controls.ptrudactvy   = (char) offset;
    offset = atoi(lookup_control("PtrLRActvX:8",  0,0)); controls.ptrlractvx   = (char) offset;
    offset = atoi(lookup_control("PtrLRActvY:5",  0,0)); controls.ptrlractvy   = (char) offset;
    offset = atoi(lookup_control("PtrUDLRActvX:8",0,0)); controls.ptrudlractvx = (char) offset;
    offset = atoi(lookup_control("PtrUDLRActvY:5",0,0)); controls.ptrudlractvy = (char) offset;
    offset = atoi(lookup_control("PtrNoRActvX:7", 0,0)); controls.ptrnoractvx  = (char) offset;
    offset = atoi(lookup_control("PtrNoRActvY:7", 0,0)); controls.ptrnoractvy  = (char) offset;
    offset = atoi(lookup_control("PtrToSActvX:0", 0,0)); controls.ptrtosactvx  = (char) offset;
    offset = atoi(lookup_control("PtrToSActvY:0", 0,0)); controls.ptrtosactvy  = (char) offset;
    offset = atoi(lookup_control("PtrScrActvX:8", 0,0)); controls.ptrscractvx  = (char) offset;
    offset = atoi(lookup_control("PtrScrActvY:8", 0,0)); controls.ptrscractvy  = (char) offset;
  }

  #ifdef TRACE
    self_test();
  #endif

  /* Install any general handlers that might be needed as a */
  /* result of the choices just loaded.                     */

  show_error(event_register_wimp_handler(-1, Wimp_ELoseCaret, handle_lose_caret, NULL));

  if (controls.claim_help)
  {
    /* Interactive help support for showing help in the status bar */

    register_null_claimant(Wimp_ENull, protocols_ih_send_help_request, NULL);
    show_error(event_register_message_handler(Wimp_MHelpReply, handle_messages, NULL));
  }
}

#ifdef TRACE

  /*************************************************/
  /* self_test()                                   */
  /*                                               */
  /* Run through a few standard startup trace      */
  /* build output routines.                        */
  /*************************************************/

  static void self_test(void)
  {
    /* This list is somewhat out of date...! Still, it served */
    /* its purpose in the early days of the above routines,   */
    /* and gives a useful overview for the Rout debug option  */
    /* during startup.                                        */

    if (tl & (1u<<5))
    {
      Printf("\nWidth: %d\n"
               "Height: %d\n",
                choices.width,
                choices.height);

      Printf("\nBack colour: %p\n"
               "Text colour: %p\n"
               "Link colour: %p\n"
               "Used colour: %p\n",
                (void *) choices.background_colour,
                (void *) choices.text_colour,
                (void *) choices.link_colour,
                (void *) choices.used_colour);

      Printf("\nSystem font: %d\n"
               "Show foreground images: %d\n"
               "Show background images: %d\n"
               "Fixed pointer: %d\n"
               "Underline links: %d\n"
               "Use document colours: %d\n"
               "URL bar: %d\n"
               "Button bar: %d\n"
               "Status bar: %d\n"
               "Move gadgets: %d\n"
               "Use a proxy: %d\n\n",
                choices.system_font,
                choices.show_foreground,
                choices.show_background,
                choices.fixed_pointer,
                choices.underline_links,
                choices.use_source_cols,
                choices.url_bar,
                choices.button_bar,
                choices.status_bar,
                choices.move_gadgets,
                choices.use_proxy);
    }

    /* Test URL to leafname translation */

    if (tl & (1<<26))
    {
      char   leafname[1024];
      char * canonicalised = NULL;

      #define TestLeaf(str) {urlutils_leafname_from_url((str), leafname, sizeof(leafname)); Printf("%s ", leafname);}

      Printf("Checking URL to Leafname translation\n");
      Printf("====================================\n\n");

      /* First, with protocols */

      TestLeaf("http://www.acorn.com/");
      TestLeaf("http://www.acorn.com");
      TestLeaf("http:/www.acorn.com/");
      TestLeaf("http:www.acorn.com/");
      TestLeaf("http:www.acorn.com");
      TestLeaf("http://www.acorn.com:80/");
      TestLeaf("http://www.acorn.com:80");
      TestLeaf("http:/www.acorn.com:80/");
      TestLeaf("http:www.acorn.com:80/");
      TestLeaf("http:www.acorn.com:80");

      Printf("\n");

      TestLeaf("http://www.acorn.com/thing1.html");
      TestLeaf("http:/www.acorn.com/thing2.html");
      TestLeaf("http:www.acorn.com/thing3.html");
      TestLeaf("http://www.acorn.com:80/thing4.html");
      TestLeaf("http:/www.acorn.com:80/thing5.html");
      TestLeaf("http:www.acorn.com:80/thing6.html");

      Printf("\n");

      TestLeaf("http://www.acorn.com/dir/dthing1.html");
      TestLeaf("http:/www.acorn.com/dir/dthing2.html");
      TestLeaf("http:www.acorn.com/dir/dthing3.html");
      TestLeaf("http://www.acorn.com:80/dir/dthing4.html");
      TestLeaf("http:/www.acorn.com:80/dir/dthing5.html");
      TestLeaf("http:www.acorn.com:80/dir/dthing6.html");

      Printf("\n");

      TestLeaf("http://www.acorn.com/dir/dthing1.tz.html");
      TestLeaf("http:/www.acorn.com/dir/dthing2.tz.html");
      TestLeaf("http:www.acorn.com/dir/dthing3.tz.html");
      TestLeaf("http://www.acorn.com:80/dir/dthing4.tz.html");
      TestLeaf("http:/www.acorn.com:80/dir/dthing5.tz.html");
      TestLeaf("http:www.acorn.com:80/dir/dthing6.tz.html");

      Printf("\n");

      TestLeaf("http://www.acorn.com/dir1/");
      TestLeaf("http:/www.acorn.com/dir2/");
      TestLeaf("http:www.acorn.com/dir3/");
      TestLeaf("http://www.acorn.com:80/dir4/");
      TestLeaf("http:/www.acorn.com:80/dir5/");
      TestLeaf("http:www.acorn.com:80/dir6/");

      Printf("\n");

      TestLeaf("http://www.acorn.com/dirs1");
      TestLeaf("http:/www.acorn.com/dirs2");
      TestLeaf("http:www.acorn.com/dirs3");
      TestLeaf("http://www.acorn.com:80/dirs4");
      TestLeaf("http:/www.acorn.com:80/dirs5");
      TestLeaf("http:www.acorn.com:80/dirs6");

      Printf("\n");

      TestLeaf("http://www.acorn.com/dirs1#anc1");
      TestLeaf("http:/www.acorn.com/dirs2#anc2");
      TestLeaf("http:www.acorn.com/dirs3#anc3");
      TestLeaf("http://www.acorn.com:80/dirs4#anc4");
      TestLeaf("http:/www.acorn.com:80/dirs5#anc5");
      TestLeaf("http:www.acorn.com:80/dirs6#anc6");

      Printf("\n");

      TestLeaf("http://www.acorn.com/d/irs1#danc1");
      TestLeaf("http:/www.acorn.com/d/irs2#danc2");
      TestLeaf("http:www.acorn.com/d/irs3#danc3");
      TestLeaf("http://www.acorn.com:80/d/irs4#danc4");
      TestLeaf("http:/www.acorn.com:80/d/irs5#danc5");
      TestLeaf("http:www.acorn.com:80/d/irs6#danc6");

      Printf("\n\n");

      /* Now without protocols */

      TestLeaf("//www.acorn.com/");
      TestLeaf("//www.acorn.com");
      TestLeaf("/www.acorn.com/");
      TestLeaf("www.acorn.com/");
      TestLeaf("www.acorn.com");
      TestLeaf("//www.acorn.com:80/");
      TestLeaf("//www.acorn.com:80");
      TestLeaf("/www.acorn.com:80/");
      TestLeaf("www.acorn.com:80/");
      TestLeaf("www.acorn.com:80");

      Printf("\n");

      TestLeaf("//www.acorn.com/thing1.html");
      TestLeaf("/www.acorn.com/thing2.html");
      TestLeaf("www.acorn.com/thing3.html");
      TestLeaf("//www.acorn.com:80/thing4.html");
      TestLeaf("/www.acorn.com:80/thing5.html");
      TestLeaf("www.acorn.com:80/thing6.html");

      Printf("\n");

      TestLeaf("//www.acorn.com/dir/dthing1.html");
      TestLeaf("/www.acorn.com/dir/dthing2.html");
      TestLeaf("www.acorn.com/dir/dthing3.html");
      TestLeaf("//www.acorn.com:80/dir/dthing4.html");
      TestLeaf("/www.acorn.com:80/dir/dthing5.html");
      TestLeaf("www.acorn.com:80/dir/dthing6.html");

      Printf("\n");

      TestLeaf("//www.acorn.com/dir/dthing1.tz.html");
      TestLeaf("/www.acorn.com/dir/dthing2.tz.html");
      TestLeaf("www.acorn.com/dir/dthing3.tz.html");
      TestLeaf("//www.acorn.com:80/dir/dthing4.tz.html");
      TestLeaf("/www.acorn.com:80/dir/dthing5.tz.html");
      TestLeaf("www.acorn.com:80/dir/dthing6.tz.html");

      Printf("\n");

      TestLeaf("//www.acorn.com/dir1/");
      TestLeaf("/www.acorn.com/dir2/");
      TestLeaf("www.acorn.com/dir3/");
      TestLeaf("//www.acorn.com:80/dir4/");
      TestLeaf("/www.acorn.com:80/dir5/");
      TestLeaf("www.acorn.com:80/dir6/");

      Printf("\n");

      TestLeaf("//www.acorn.com/dirs1");
      TestLeaf("/www.acorn.com/dirs2");
      TestLeaf("www.acorn.com/dirs3");
      TestLeaf("//www.acorn.com:80/dirs4");
      TestLeaf("/www.acorn.com:80/dirs5");
      TestLeaf("www.acorn.com:80/dirs6");

      Printf("\n");

      TestLeaf("//www.acorn.com/dirs1#anc1");
      TestLeaf("/www.acorn.com/dirs2#anc2");
      TestLeaf("www.acorn.com/dirs3#anc3");
      TestLeaf("//www.acorn.com:80/dirs4#anc4");
      TestLeaf("/www.acorn.com:80/dirs5#anc5");
      TestLeaf("www.acorn.com:80/dirs6#anc6");

      Printf("\n");

      TestLeaf("//www.acorn.com/d/irs1#danc1");
      TestLeaf("/www.acorn.com/d/irs2#danc2");
      TestLeaf("www.acorn.com/d/irs3#danc3");
      TestLeaf("//www.acorn.com:80/d/irs4#danc4");
      TestLeaf("/www.acorn.com:80/d/irs5#danc5");
      TestLeaf("www.acorn.com:80/d/irs6#danc6");

      Printf("\n\n");

      /* Minimal URLs */

      TestLeaf("www");
      TestLeaf("www/dirs");
      TestLeaf("www:80/dirs");
      TestLeaf("http:www");
      TestLeaf("http:www/dirs");
      TestLeaf("http:www:80/dirs");
      TestLeaf("http:/www");
      TestLeaf("http:/www/dirs");
      TestLeaf("http:/www:80/dirs");

      Printf("\n");

      TestLeaf("www/");
      TestLeaf("www/dirs/");
      TestLeaf("www:80/dirs/");
      TestLeaf("http:www/");
      TestLeaf("http:www/dirs/");
      TestLeaf("http:www:80/dirs/");
      TestLeaf("http:/www/");
      TestLeaf("http:/www/dirs/");
      TestLeaf("http:/www:80/dirs/");

      Printf("\n");

      TestLeaf("http:///notahost");
      TestLeaf("http:///notahost/");
      TestLeaf("http:///notahost2.html");
      TestLeaf("http:///notahost2.dir/");
      TestLeaf("http:///notahost3/find");

      Printf("\n\n");

      /* Mixed and broken anchor specifications */

      TestLeaf("http://www.acorn.com/#d/irs1#danc1");
      TestLeaf("http:/www.acorn.com/#d/irs2#danc2");
      TestLeaf("http:www.acorn.com/#d/irs3#danc3");
      TestLeaf("http://www.acorn.com:80/#d/irs4#danc4");
      TestLeaf("http:/www.acorn.com:80/#d/irs5#danc5");
      TestLeaf("http:www.acorn.com:80/#d/irs6#danc6");

      Printf("\n");

      TestLeaf("http://www.acorn.com/#canc1");
      TestLeaf("http:/www.acorn.com/#canc2");
      TestLeaf("http:www.acorn.com/#canc3");
      TestLeaf("http://www.acorn.com:80/#canc4");
      TestLeaf("http:/www.acorn.com:80/#canc5");
      TestLeaf("http:www.acorn.com:80/#canc6");

      Printf("\n");

      TestLeaf("http://www.acorn.com/#");
      TestLeaf("http:/www.acorn.com/#");
      TestLeaf("http:www.acorn.com/#");
      TestLeaf("http://www.acorn.com:80/#");
      TestLeaf("http:/www.acorn.com:80/#");
      TestLeaf("http:www.acorn.com:80/#");

      Printf("\n");

      TestLeaf("http://www.acorn.com/pre1#");
      TestLeaf("http:/www.acorn.com/pre2#");
      TestLeaf("http:www.acorn.com/pre3#");
      TestLeaf("http://www.acorn.com:80/pre4#");
      TestLeaf("http:/www.acorn.com:80/pre5#");
      TestLeaf("http:www.acorn.com:80/pre6#");

      Printf("\n\n");
      Printf("Checking pathname canonicalisation\n");
      Printf("==================================\n\n");

      #define TestCanonicalise(str) {                                                    \
                                      strcpy(leafname, (str));                           \
                                      Printf("In : '%s'\n",leafname);                    \
                                                                                         \
                                      utils_canonicalise_path(leafname, &canonicalised); \
                                                                                         \
                                      if (canonicalised)                                 \
                                      {                                                  \
                                        Printf("Out: '%s'\n\n", canonicalised);          \
                                                                                         \
                                        free(canonicalised);                             \
                                        canonicalised = NULL;                            \
                                      }                                                  \
                                      else Printf("Out: (Error)\n\n");                   \
                                    }

      TestCanonicalise("<Browse$Dir>.User.Hotlist");
      TestCanonicalise("Browse:User.Hotlist");
      TestCanonicalise("<Choices$Write>.WWW.Browser.Hotlist");
      TestCanonicalise("Choices:WWW.Browser.Hotlist");

      /*
       * Maybe we don't want this every time...!
       *
       * Printf("\n\n");
       * Printf("Checking path building\n");
       * Printf("======================\n\n");
       *
       * #define TestBuild(str) {                                                \
       *                          _kernel_oserror * e = utils_build_tree((str)); \
       *                                                                         \
       *                          if (!e) Printf("OK : '%s'\n", (str));          \
       *                          else                                           \
       *                          {                                              \
       *                            Printf("Err: '%s'\n", (str));                \
       *                            Printf("     '%s'\n", e->errmess);           \
       *                          }                                              \
       *                        }
       *
       * TestBuild("Mem::Sprites.$.This");
       * TestBuild("Mem::Sprites.$.This.That");
       * TestBuild("Mem::Sprites.$.This.That.The.Other");
       * TestBuild("ADFS::4.$.Hello.This.Is.A.Test");
       *
       */
    }
  }

#endif

/*************************************************/
/* termination()                                 */
/*                                               */
/* Called by registration through the atexit     */
/* function. Shuts down core functions prior to  */
/* the browser exitting (e.g. the Font Manager   */
/* can get very tetchy about having font handles */
/* left claimed, so must release them).          */
/*************************************************/

void termination(void)
{
  #ifdef TRACE
    if (tl & (1u<<5)) Printf("termination() called\n");
  #endif

  if (taskmodule_ds_registered)
  {
    /* Not interested in any errors, if it fails we can't really */
    /* do anything about it at this stage.                       */

    _swix(TaskModule_DeRegisterService,
          _INR(0,2),

          0,
          0,
          task_handle);
  }

  close_messages_file(cob); cob = NULL;
  close_messages_file(chb); chb = NULL;

  fm_shutdown();
  plugin_shutdown();
  rma_shutdown();
}

/*************************************************/
/* main()                                        */
/*                                               */
/* That which runs before all others.            */
/*************************************************/

int main(int argc, char * argv[])
{
  WimpPollBlock b;
  int           eventcode, time;

  #ifdef SINGLE_USER

    int         argp     = 1;
    int         done_one = 0;

  #endif

  #ifdef USE_MALLOC_REPLACEMENT
    MemHeap_Initialise("Blimey, does this work?");
  #endif

  #ifdef INCLUDE_HEAPGRAPH
    HeapGraph_RedirectAllocFns(NULL);
  #endif

  #ifdef INCLUDE_HIERPROF
    HierProf_ProfileAllFunctions();
  #endif

  #ifdef INCLUDE_MEMCHECK

    MemCheck_Init();
    MemCheck_InterceptSCLStringFunctions();
    MemCheck_RegisterArgs(argc, argv);
    MemCheck_SetStoreMallocFunctions(1);
    MemCheck_SetReportFrees(1);
    MemCheck_SetAutoOutputBlocksInfo(0);

  #endif

  #ifdef TRACE

    /* Non-debug builds have a WimpSlot close to the actual startup */
    /* requirements. Debug builds have a huge slotsize because of   */
    /* all of the debug information, despite this being copied away */
    /* by the debugger on startup. This leaves a very large free    */
    /* chunk within the WimpSlot size in which malloc may operate,  */
    /* and thus creates a distinctly different environment for the  */
    /* memory system in a debug build.                              */
    /*                                                              */
    /* To try and get closer to the non-debug memory usage, TRACE   */
    /* builds will malloc the following large block in an attempt   */
    /* to soak up that initial free space.                          */

    malloc(2*1024*1024);

  #endif

  /* NB, don't forget to echo any changes here with the duplicate code */
  /* just below the setjmp call later.                                 */

  signal(SIGOSERROR, catch_errors); /* OS error            */
  signal(SIGILL,     catch_errors); /* Illegal instruction */
  signal(SIGSEGV,    catch_errors); /* Segment violation   */
  signal(SIGSTAK,    catch_errors); /* Stack overflow      */
  signal(SIGFPE,     catch_errors); /* FPE error           */

  /* Before initialisation, find out where we ran from - this */
  /* software can support different application names, so the */
  /* existance of a specific system variable cannot be relied */
  /* upon (with the exception of ROM builds).                 */

  {
    int    len;
    char * item;

    /* Work out what path to go through */

    #ifdef ROM

      item = "Resources:$.Resources.Browse";
      len  = strlen(item);

    #else

      item = argv[0];
      len  = strlen(item) - strlen(".!RunImage");

    #endif

    /* Allocate the space, bomb out if it fails */

    task_dir = malloc(len + 1);

    if (!task_dir)
    {
      erb.errnum = Utils_Error_Custom_Fatal;
      strcpy(erb.errmess, "There is insufficient memory to start the browser.");
      show_error(&erb);
    }

    /* Copy the information and ensure it is terminated correctly */

    strncpy(task_dir, item, len);
    task_dir[len] = 0;
  }

  #ifdef TRACE

    malloccount = flexcount = 0;

    /* Handle -d[ebug] CLI switch; see Global.c for more information. */
    /* This must be the first command line argument.                  */

    if (argc >= argp + 1)
    {
      if (!strcmp(argv[argp],"-debug") | !strcmp(argv[argp],"-d"))
      {
        if (strstr(argv[argp + 1], "MsgT")) tl |= (1u<<0);
        if (strstr(argv[argp + 1], "TBar")) tl |= (1u<<1);
        if (strstr(argv[argp + 1], "Null")) tl |= (1u<<2);
        if (strstr(argv[argp + 1], "Wind")) tl |= (1u<<3);
        if (strstr(argv[argp + 1], "Menu")) tl |= (1u<<4);
        if (strstr(argv[argp + 1], "Rout")) tl |= (1u<<5);
        if (strstr(argv[argp + 1], "Fetc")) tl |= (1u<<6);
        if (strstr(argv[argp + 1], "Memo")) tl |= (1u<<7);
        if (strstr(argv[argp + 1], "Refo")) tl |= (1u<<8);
        if (strstr(argv[argp + 1], "Redr")) tl |= (1u<<9);
        if (strstr(argv[argp + 1], "Font")) tl |= (1u<<10);
        if (strstr(argv[argp + 1], "BBox")) tl |= (1u<<11);
        if (strstr(argv[argp + 1], "LMem")) tl |= (1u<<12);
        if (strstr(argv[argp + 1], "CMal")) tl |= (1u<<13);
        if (strstr(argv[argp + 1], "CFle")) tl |= (1u<<14);
        if (strstr(argv[argp + 1], "Imag")) tl |= (1u<<15);
        if (strstr(argv[argp + 1], "Hist")) tl |= (1u<<16);
        if (strstr(argv[argp + 1], "Fram")) tl |= (1u<<17);
        if (strstr(argv[argp + 1], "Stre")) tl |= (1u<<18);
        if (strstr(argv[argp + 1], "Circ")) tl |= (1u<<19);
        if (strstr(argv[argp + 1], "Tabl")) tl |= (1u<<20);
        if (strstr(argv[argp + 1], "URIH")) tl |= (1u<<21);
        if (strstr(argv[argp + 1], "KeyC")) tl |= (1u<<22);
        if (strstr(argv[argp + 1], "RBox")) tl |= (1u<<23);
        if (strstr(argv[argp + 1], "JScr")) tl |= (1u<<24);
        if (strstr(argv[argp + 1], "Hotl")) tl |= (1u<<25);
        if (strstr(argv[argp + 1], "Save")) tl |= (1u<<26);
        if (strstr(argv[argp + 1], "Drag")) tl |= (1u<<27);
        if (strstr(argv[argp + 1], "MsgP")) tl |= (1u<<28);
        if (strstr(argv[argp + 1], "Choi")) tl |= (1u<<29);
        if (strstr(argv[argp + 1], "Plug")) tl |= (1u<<30);

        if (strstr(argv[argp + 1], "All"))  tl  = 0xffffffff;

        argp += 2;
      }
    }

    if (tl & (1u<<5)) Printf("\nmain: Initialising\n");

  #endif

  /* Now do the bulk of application initialisation */

  initialise_app();

  #ifdef TRACE
    if (tl & (1u<<5)) Printf("main: Loading choices\n");
  #endif

  load_choices();

  #ifndef SINGLE_USER

    /* Multiuser builds need a unique filename base. Do this */
    /* checking Post_In / Post_Out as the Set Post_In /      */
    /* Post_Out dialogue box handler will expect to be able  */
    /* to go straight to multiuser_login().                  */

    multiuser_create_unique_postbox_filename();

    /* Want to check for Post_On / Post_Out exist at least */
    /* as readable directories or image files. If not, we  */
    /* should open the Set Post_In / Post_Out dialogue box */
    /* rather than log in, and let that start up the       */
    /* log-in procedure for us.                            */

    {
      int in_ok  = 0;
      int out_ok = 0;

      setpboxes_check_boxes(&in_ok, &out_ok);

      if (!in_ok || !out_ok) show_error(setpboxes_show_dialogue());
      else                   show_error(multiuser_login());
    }

  #else

    /* Load the visit history, image history and hotlist  */
    /* (this will be the single user version of the login */
    /* call).                                             */

    show_error(multiuser_login());

  #endif

  /* Try to start the proxy server if necessary */

  if (choices.start_proxy)
  {
    unsigned int handle = 0;

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

    if (!handle)
    {
      #ifdef TRACE
        if (tl & (1u<<5)) Printf("main: Starting proxy server\n");
      #endif

      _swix(Wimp_StartTask,
            _IN(0),

            lookup_token("ProxyComm:Filer_Run WebServe:!Run",0,0));
    }

    #ifdef TRACE

      else
      {
        if (tl & (1u<<5)) Printf("main: Proxy server already running\n");
      }

    #endif
  }

  #ifdef TRACE
    if (tl & (1u<<5)) Printf("main: Handling CLI arguments\n");
  #endif

  /* If using keyboard control, watch the pointer for movement, */
  /* turning it off if not moved for 5 seconds.                 */

  if (choices.keyboard_ctrl) mouse_watch_pointer_control(1);

  /* Command line arguments aren't of interest to a multiuser build  */
  /* because it will never have logged in by this point, so it can't */
  /* sensibly deal with fetching pages yet.                          */

  #ifdef SINGLE_USER

    /* Keep advancing argp if the arguments are dealt with;  */
    /* only continue to check the arguments if we haven't    */
    /* pushed argp past argc, the total number of arguments. */

    done_one = 1;

    while (argc >= argp && done_one)
    {
      done_one = 0;

      /* Handle -html (HTML files) */

      if (argc >= argp + 1)
      {
        if (!strcmp(argv[argp], "-html"))
        {
          char url[Limits_URL];

          #ifdef TRACE
            if (tl & (1u<<5)) Printf("main: Handling -html CLI argument\n");
          #endif

          StrNCpy0(url, argv[argp + 1]);
          urlutils_pathname_to_url(url, sizeof(url));

          windows_create_browser(url, NULL, NULL, NULL, Windows_CreateBrowser_Normal);

          argp += 2, done_one = 1;
        }
      }

      /* Handle -uri (URI files) */

      if (argc >= argp + 1)
      {
        if (!strcmp(argv[argp], "-uri"))
        {
          char url[Limits_URL];

          #ifdef TRACE
            if (tl & (1u<<5)) Printf("main: Handling -uri CLI argument\n");
          #endif

          urlutils_load_uri_file(url, sizeof(url), NULL, 0, argv[argp + 1]);

          windows_create_browser(url, NULL, NULL, NULL, Windows_CreateBrowser_Normal);

          argp += 2, done_one = 1;
        }
      }

      /* Handle -url (URL strings) */

      if (argc >= argp + 1)
      {
        if (!strcmp(argv[argp],"-url") || !strcmp(argv[argp],"-u"))
        {
          #ifdef TRACE
            if (tl & (1u<<5)) Printf("main: Handling -url CLI argument\n");
          #endif

          windows_create_browser(argv[argp+1], NULL, NULL, NULL, Windows_CreateBrowser_Normal);

          argp += 2, done_one = 1;
        }
      }
    }

  #endif

  #ifdef TRACE
    if (tl & (1u<<5)) Printf("main: Polling\n");
  #endif

  atexit(termination);

  /* Long jump handler - most nasty or generally unexpected */
  /* errors will come back to here. The OS error abort      */
  /* handler jumps back here to deal with the error as we   */
  /* then have a clear stack; this is to avoid 'no stack    */
  /* for trap handler' errors caused by a SWI corrupting    */
  /* the value of R10.                                      */

  if (setjmp(env) == Main_FromCatchErrors)
  {
    char         * tok        = NULL;
    unsigned int * regdump    = NULL;
    unsigned int * os_regdump = NULL;
    char           pc[16];

    print_abort_print();

    /* Sort out the register dump */

    _swix(OS_ChangeEnvironment,
          _INR(0,3) | _OUT(3),

          7, /* Call back */
          0,
          0,
          0,

          &regdump); /* Where the C library put the registers */

    _swix(OS_ChangeEnvironment,
          _INR(0,3) | _OUT(1),

          13, /* Exception registers */
          0,
          0,
          0,

          &os_regdump); /* Where *ShowRegs gets them from */

    /* Copy the C register dump into the OS space */

    if (regdump && os_regdump) memcpy(os_regdump, regdump, 4 * 16);

    /* Store a more sensible error in the error block 'erb' where possible. */

    switch (erb.errnum)
    {
      case 0x80000000: tok = "EZeros0"; break;
      case 0x80000001: tok = "EZeros1"; break;
      case 0x80000002: tok = "EZeros2"; break;
      case 0x80000003: tok = "EZeros3"; break;
      case 0x80000005: tok = "EZeros5"; break;
    }

    if (tok)
    {
      char * error;

      /* If we know the PC, put this in the message */

      if (!regdump) sprintf(pc, "&deaddead");
      else          sprintf(pc, "&%08X", os_regdump[15] &~ 0xfc000003);

      error = lookup_token(tok, 0, pc);

      /* If the message token wasn't found, use the OS error, */
      /* otherwise copy the new one into the error block.     */

      if (strcmp(error, "!")) StrNCpy0(erb.errmess, error);
    }

    /* Need to reinstall the signal handlers since the run-time */
    /* system will have removed them 'for your saftey and       */
    /* convenience (TM)'. Don't forget to keep this list up to  */
    /* date with the code near the top of the function.         */

    signal(SIGOSERROR, catch_errors); /* OS error            */
    signal(SIGILL,     catch_errors); /* Illegal instruction */
    signal(SIGSEGV,    catch_errors); /* Segment violation   */
    signal(SIGSTAK,    catch_errors); /* Stack overflow      */
    signal(SIGFPE,     catch_errors); /* FPE error           */

    show_error_cont(&erb);
  }

  while (!quit)
  {
    /* We use flex's deferred compaction, so ensure the */
    /* heap is as small as possible.                    */

    flex_compact();

    /* What time is it? (For Wimp_PollIdle) */

    _swix(OS_ReadMonotonicTime,
          _OUT(0),

          &time);

    /* Use PollIdle, but want drag events to be as responsive as possible */

    ChkError(event_poll_idle(&eventcode,
                             &b,
                             time + !drag_in_progress,
                             NULL));
  }

  #ifdef TRACE
    if (tl & (1u<<5))  Printf("\nmain: Calling exit()\n\n");
    if (tl & (1u<<13)) Printf("Near exit, malloccount: \0216%d\0217\n",malloccount);
    if (tl & (1u<<14)) Printf("Near exit, flexcount  : %d\n",flexcount);
  #endif

  /* Save hotlist, histories etc., and logout if a multiuser build */

  show_error_ret(multiuser_logout());

  /* This will call the termination() function in passing */

  exit(EXIT_SUCCESS);
}

/*************************************************/
/* catch_errors()                                */
/*                                               */
/* Catch OS errors and report them with the      */
/* opportunity to continue or quit (done inside  */
/* main itself).                                 */
/*                                               */
/* This is the last function in the file since   */
/* it plays around with stack checking, and you  */
/* can't read the previous state. If this was in */
/* the middle of the source and someone wrote a  */
/* #pragma above it, endless confusion could     */
/* otherwise arise as to why the instruction had */
/* no effect on some of the functions here...    */
/*                                               */
/* Parameters: The signal number (ignored).      */
/*************************************************/

#pragma no_check_stack

static void catch_errors(int signum)
{
  /* Store the error locally */

  erb = *_kernel_last_oserror();

  /* Go back to main to report the error */

  longjmp(env, Main_FromCatchErrors);

  /* Just in case... */

  exit(EXIT_FAILURE);
}