/* 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   : Hotlist.c                              */
/*                                                 */
/* Purpose: Managing a hotlist in the browser.     */
/*                                                 */
/* Author : D.T.A.Brown                            */
/*                                                 */
/* History: 06-Aug-97: Created.                    */
/*          22-Aug-97: (ADH/DTAB) Integrated into  */
/*                     main browser code.          */
/***************************************************/


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

#include "swis.h"

#include "toolbox.h"
#include "wimp.h"
#include "wimplib.h"
#include "menu.h"
#include "event.h"
#include "gadgets.h"

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

#include "FetchPage.h"
#include "Mouse.h"
#include "Save.h"
#include "Toolbars.h"
#include "URLUtils.h"
#include "Windows.h"

#include "Hotlist.h"

/* To be made choices/controls */

#define AUTOSCROLL_DELAY                    50
#define HOTLIST_SCROLL_BOUNDARY_SIZE        32

/* Local definitions */

#define HotlistWrite(fn) {written = (fn); if (written < 0) return _kernel_last_oserror();}

/* Local statics */

static int            autoopen_oldtime;                   /* Base time for autoopen directory */
static hotlist_item * hotlist_root = NULL;                /* Pointer to the hotlist root directory item */
static int            hotlist_windowid;                   /* Object ID of the hotlist window */

static hotlist_item * hotlist_newitem;                    /* When ever a new item is created this points to it */

static int            menu_itemno = 0;                    /* Item over which menu was pressed */

static int            menu_select        = 0;             /* 1 if an item had to be selected when the menu was opened */
static unsigned int   last_selected_item = 0xffffffff;    /* The last item which was selected */

static int            hotlist_dragging   = 0;             /* Holds a set of values for different dragging cases (see Hotlist.h) */
static unsigned int   highlighted_itemno;                 /* During dragging, the number of a directory item using the '...+' sprite */
static hotlist_item * hotlist_current_highlighted = NULL; /* To save scanning lists, pointer to the highlighted item */
static int            hl_show_urls;                       /* 0 when url descriptions to be shown, 1 to show urls */


static unsigned int   hotlist_bbar_size = 0;              /* Height in OS units of the button bar */
static unsigned int   alter_new;                          /* Remembers if the edit dialogue is Alter or New; see   */
                                                          /* HOTLIST_MENUSECTION_NEW and HOTLIST_MENUSECTION_ALTER */

static hotlist_item * hotlist_save_item = NULL;           /* Item whose URL is being saved */
static int            hotlist_ram_transfer_sent;          /* Number of bytes which have already been sent by ram transfer */
static int            hotlist_save_type = HL_SAVE_NONE;   /* Variety of save currently in progress */

/* Event handler prototypes */

// Write these out in full, to clearly state exactly what parameters are needed?
// Or is this in fact more robust, as the compiler will fault mismatched functions
// in this source as opposed to wherever a registration of the handler occurs?

static _kernel_oserror     * hotlist_selection_box_start(void);

static WimpEventHandler      hotlist_redraw_handler;
static WimpEventHandler      hotlist_mouse_click_handler;
static WimpEventHandler      hotlist_drag_completed_handler;
static WimpEventHandler      hotlist_null_handler;
static WimpEventHandler      hotlist_null_drag_select_handler;

static ToolboxEventHandler   hotlist_menuopen_handler;
static ToolboxEventHandler   hotlist_menuclose_handler;
static ToolboxEventHandler   hotlist_menu_selectall_handler;
static ToolboxEventHandler   hotlist_menu_clearselect_handler;
static ToolboxEventHandler   hotlist_menu_openall_handler;
static ToolboxEventHandler   hotlist_menu_closeall_handler;
static ToolboxEventHandler   hotlist_menu_delete_handler;
static ToolboxEventHandler   hotlist_show_editurl_handler;
static ToolboxEventHandler   hotlist_show_rendirectory_handler;
static ToolboxEventHandler   hotlist_show_newurl_handler;
static ToolboxEventHandler   hotlist_show_newdirectory_handler;
static ToolboxEventHandler   hotlist_newedit_url_handler;
static ToolboxEventHandler   hotlist_newren_directory_handler;
static ToolboxEventHandler   hotlist_reset_url_handler;
static ToolboxEventHandler   hotlist_reset_directory_handler;
static ToolboxEventHandler   hotlist_show_descriptions_handler;
static ToolboxEventHandler   hotlist_show_urls_handler;
static ToolboxEventHandler   hotlist_drag_stop_handler;

static WimpMessageHandler    hotlist_data_save_ack_handler;
static WimpMessageHandler    hotlist_ram_fetch_handler;

/* Debug functions */

#ifdef TRACE

  static void hotlist_display_item(hotlist_item * item);
  static void hotlist_display_tree(hotlist_item * list, int indent);

#endif

/* List enquiry and manupulation */

static _kernel_oserror * hotlist_link                    (hotlist_item * item, hotlist_item * target, unsigned int position);
static void              hotlist_unlink                  (hotlist_item * item);
static _kernel_oserror * hotlist_new_directory           (hotlist_item * parent, char * directory_name, unsigned int position, hotlist_item ** new);
static _kernel_oserror * hotlist_new_url                 (hotlist_item * parent, unsigned int position, char * url_description, char * url);
static void              hotlist_delete_item             (hotlist_item * item);
static _kernel_oserror * hotlist_move_item               (hotlist_item * source, hotlist_item * target, unsigned int position);
static _kernel_oserror * hotlist_copy_item               (hotlist_item * source, hotlist_item * target, unsigned int position, hotlist_item ** new_item);

/* Item enquiry and manipulation */

static _kernel_oserror * hotlist_get_entry_sizes         (unsigned int * item_height, unsigned int * item_dir_width, unsigned int * item_url_width);
static int               hotlist_set_flags               (hotlist_item * list, hotlist_type type, unsigned int flags);
static int               hotlist_clear_flags             (hotlist_item * list, hotlist_type type, unsigned int flags);

/* Finding items */

static hotlist_item    * hotlist_find_item               (hotlist_item * list, unsigned int item_no);
static hotlist_item    * hotlist_find_item_r             (hotlist_item * list, unsigned int item_no, int * curr_item);
static int               hotlist_find_no_from_item       (hotlist_item * item);
static int               hotlist_find_no_from_item_r     (hotlist_item * list, hotlist_item * item, int * curr_item);
static hotlist_item    * hotlist_find_selected_item      (void);
static hotlist_item    * hotlist_find_selected_item_r    (hotlist_item * list);

/* Counting items */

static unsigned int      hotlist_count_selected_items    (void);
static void              hotlist_count_selected_items_r  (hotlist_item * list, int * count);
static unsigned int      hotlist_count_displayed_items   (hotlist_item * list);
static void              hotlist_count_displayed_items_r (hotlist_item * list, int * count);

// Categorisation work in progress...

static _kernel_oserror * hotlist_draw                    (hotlist_item * list, unsigned int first_item, unsigned int last_item);
static _kernel_oserror * hotlist_draw_r                  (hotlist_item *list, unsigned int first_item, unsigned int last_item, int * curr_item, unsigned int indent, unsigned int item_height, unsigned int item_dir_width, unsigned int item_url_width);
static unsigned int      hotlist_contents_selected       (hotlist_item * item);
static unsigned int      hotlist_no_contents_selected    (hotlist_item * item);
static unsigned int      hotlist_get_max_width           (hotlist_item * list);
static _kernel_oserror * hotlist_get_max_width_r         (hotlist_item *list, unsigned int indent, int * max_width, unsigned int item_height, unsigned int item_dir_width, unsigned int item_url_width);
static void              hotlist_redraw_now              (void);
static void              hotlist_redraw_now_r            (hotlist_item * list, int * curr_item);
static void              hotlist_get_shape               (unsigned int * xmin, unsigned int * xmax, hotlist_item * item);
static void              hotlist_directory_open_close    (hotlist_item * item, unsigned int itemno);
static void              hotlist_redraw_items            (unsigned int firstitem, unsigned int lastitem);
static void              hotlist_clear_selection         (void);
static void              hotlist_launch_url              (hotlist_item * item);
static _kernel_oserror * hotlist_process_click_on_item   (unsigned int itemno, hotlist_item * item, int buttons, int x, int y);
static _kernel_oserror * hotlist_process_click           (int x, int y, int buttons);
static int               hotlist_preopen                 (void);
static void              hotlist_set_menu_details        (ObjectId menuid);
static _kernel_oserror * hotlist_save_entries            (FILE * fileptr, hotlist_item * list, int type);
static void              hotlist_lower_tags              (char * string);
static _kernel_oserror * hotlist_load_directory          (FILE * fileptr, hotlist_item * target);
static void              hotlist_drag_renderer           (hotlist_item * item, unsigned int item_height, unsigned int item_dir_width, unsigned int item_url_width);
static void              hotlist_start_drag              (void);
static _kernel_oserror * hotlist_modified                (unsigned int type);
static void              hotlist_convert_drag_selection  (hotlist_item * item);
_kernel_oserror        * hotlist_autoscroll              (int window);
/* Save Protocol */

static _kernel_oserror * hotlist_initiate_uri_save       (hotlist_item *sourceitem);
static _kernel_oserror * hotlist_initiate_html_save      (char *filename);

#ifdef TRACE

  /*************************************************/
  /* hotlist_display_item()                        */
  /*                                               */
  /* This function display the data held by a      */
  /* single hotlist_item                           */
  /*                                               */
  /* Parameters: Pointer to a hotlist_item         */
  /*************************************************/

  static void hotlist_display_item(hotlist_item * item)
  {
    Printf("\nhotlist_display_item for %p\n",item);
    Printf("--------------------\n");
    Printf("type              = %d\n", item->type);
    Printf("name              = %s\n", item->name);
    Printf("flags             = %d\n", item->flags);
    Printf("parent            = %p\n", item->parent);
    Printf("previous          = %p\n", item->previous);
    Printf("next              = %p\n", item->next);
    Printf("data.generic_data = %p ",  item->data.generic_data);

    switch(item->type)
    {
      case hl_url:
      {
        Printf("URL(%s)\n", item->data.url);
      }
      break;

      case hl_directory:
      {
        Printf("sub directory pointer\n");
      }
      break;

      default:
      {
        Printf("\n");
      }
      break;
    }
  }

  /*************************************************/
  /* hotlist_display_tree()                        */
  /*                                               */
  /* Recursivly display the hotlist tree starting  */
  /* from the passed item                          */
  /*                                               */
  /* Parameters: Pointer to a hotlist_item         */
  /*             value to start indent at          */
  /*             recommended 0                     */
  /*************************************************/

  static void hotlist_display_tree(hotlist_item * list, int indent)
  {
    int count;

    Printf("\nhotlist_display_tree for %p, indent %d\n", list, indent);
    Printf("--------------------\n\n");

    while (list)
    {
      for (count = 0; count < indent; count++) Printf("| ");

      switch (list->type)
      {
        case hl_url:
        {
          Printf("%s:URL(%s)\n", list->name, list->data.url);
        }
        break;

        case hl_directory:
        {
          Printf("%s:DIRECTORY", list->name);
        }

        if (list->flags & HOTLIST_D_IS_OPEN)
        {
          Printf(" (Open)\n");
          hotlist_display_tree(list->data.directory_content, indent + 1);
        }
        else
        {
          Printf(" (Closed)\n");
          hotlist_display_tree(list->data.directory_content, indent + 1);
        }
        break;

        default:
        {
          Printf("%s:UNRECOGNISED TYPE\n", list->name);
        }
        break;
      }
      list = list->next;
    }
  }

#endif

/*************************************************/
/* hotlist_link()                                */
/*                                               */
/* This function links the passed item to the    */
/* passed target.                                */
/*                                               */
/* It can link the item in four different ways;  */
/* before or after the target, or if the target  */
/* is a directory, at the beginning or end of    */
/* that directory's contents.                    */
/*                                               */
/* Parameters: Pointer to the hotlist_item       */
/*             struct to link in;                */
/*                                               */
/*             Pointer to the hotlist_item       */
/*             struct to link to;                */
/*                                               */
/*             Position to link to -             */
/*             HOTLIST_POSITION_BEGINNING        */
/*             HOTLIST_POSITION_END              */
/*             HOTLIST_POSITION_BEFORE or        */
/*             HOTLIST_POSITION_AFTER.           */
/*                                               */
/* Assumes:    The item and target pointers are  */
/*             not NULL and are valid;           */
/*                                               */
/*             That if adding to the beginning   */
/*             or end, the target is a directory */
/*             (in both cases, an error will be  */
/*             raised in TRACE builds if the     */
/*             assumptions are violated).        */
/*************************************************/

static _kernel_oserror * hotlist_link(hotlist_item * item, hotlist_item * target, unsigned int position)
{
  #ifdef TRACE

    /* Test certain basic assumptions are not violated */

    if (!item || !target)
    {
      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess, "NULL hotlist item or target pointer in hotlist_link");
      show_error_ret(&erb);
      return NULL;
    }

    if (
         (
           position == HOTLIST_POSITION_BEGINNING ||
           position == HOTLIST_POSITION_END
         )
         && target->type != hl_directory
       )
    {
      erb.errnum = Utils_Error_Custom_Normal;
      strcpy(erb.errmess, "Cannot insert at item at the beginning or end when the target is not a directory, in hotlist_link");
      show_error_ret(&erb);
      return NULL;
    }

  #endif

  switch (position)
  {
    case HOTLIST_POSITION_BEFORE:
    {
      /* Simple insertion above the target item */

      item->next     = target;
      item->previous = target->previous;
      item->parent   = target->parent;

      target->previous = item;

      if (item->previous) item->previous->next = item;
      if (item->parent && item->parent->data.directory_content == target) item->parent->data.directory_content = item;
    }
    break;

    default: /* We'll allow a default to adding after the target item */

    case HOTLIST_POSITION_AFTER:
    {
      /* Again, a simple insertion after the target item */

      item->previous = target;
      item->next     = target->next;
      item->parent   = target->parent;

      target->next = item;

      if (item->next) item->next->previous = item;
    }
    break;

    case HOTLIST_POSITION_BEGINNING:
    {
      /* A bit harder - insert at the top of a directory. */
      /* First a quick sanity check - TRACE builds will   */
      /* already have faulted this, but For non-TRACE     */
      /* builds, at least this will stop things dying.    */

      if (target->type != hl_directory) return NULL;

      /* This item is going to be at the beginning of a */
      /* directory, so there is never a previous item.  */

      item->previous = NULL;

      /* The next item is the one that used to be at the top */
      /* of the directory, and the parent for this item will */
      /* obviously be the target.                            */

      item->next   = target->data.directory_content;
      item->parent = target;

      /* If we have a next item, make sure its previous field */
      /* now points to the new item at the top.               */

      if (item->next) item->next->previous = item;

      /* The target should point to the new top item, too. */

      target->data.directory_content = item;
    }
    break;

    case HOTLIST_POSITION_END:
    {
      hotlist_item * last;

      /* Similarly, add to the end of the directory */

      if (target->type != hl_directory) return NULL;

      /* As before the parent must be the target item */

      item->parent = target;

      /* Now look from the start of the directory downwards for */
      /* the last item currently present.                       */

      last = target->data.directory_content;

      while(last && last->next) last = last->next;

      /* The new item must point to that one as in its previous field, */
      /* and has no next item to point to.                             */

      item->previous = last;
      item->next     = NULL;

      if (last)
      {
        /* If there were any items in the directory, make sure that the */
        /* last one points to the new bottom entry.                     */

        last->next = item;
      }
      else
      {
        /* Otherwise, the parent directory should point to this item */

        target->data.directory_content = item;
      }
    }
    break;
  }

  /* Finished... */

  return NULL;
}

/*************************************************/
/* hotlist_unlink()                              */
/*                                               */
/* This function unlinks the passed item from    */
/* the items linked in before and after it.      */
/*                                               */
/* Parameters: Pointer to the hotlist_item       */
/*             struct to remove.                 */
/*************************************************/

static void hotlist_unlink(hotlist_item *item)
{
  if (item->parent && item->parent->data.directory_content == item) item->parent->data.directory_content = item->next;
  if (item->previous) item->previous->next = item->next;
  if (item->next) item->next->previous = item->previous;

  /* Not strictly needed, but added to ensure robustness */

  item->next     = NULL;
  item->previous = NULL;
  item->parent   = NULL;
}

/*************************************************/
/* hotlist_new_directory()                       */
/*                                               */
/* This function creates a new directory         */
/* at the beginning of the given parent.         */
/*                                               */
/*                                               */
/* Parameters: Pointer to a hotlist_item of type */
/*             directory that represents the     */
/*             parent item for this new entry;   */
/*                                               */
/*             Name of new directory to create;  */
/*                                               */
/*             Position to create the new item   */
/*             relative to the parent (as for    */
/*             hotlist_link);                    */
/*                                               */
/*             Pointer to a pointer to a         */
/*             hotlist_item struct, which will   */
/*             be filled in with the address of  */
/*             the new item (unless there is an  */
/*             error returned).                  */
/*                                               */
/* Assumes:    That the pointer to a pointer to  */
/*             a hotlist_item struct is not NULL */
/*             (it would make little sense to    */
/*             allow this...).                   */
/*************************************************/

static _kernel_oserror * hotlist_new_directory(hotlist_item * parent, char * directory_name, unsigned int position, hotlist_item ** new)
{
  char             * perm_dirname;
  hotlist_item     * item;
  _kernel_oserror  * e;

  if (!directory_name) return NULL;

  /* Allocate space for the item and its name */

  item = malloc(sizeof(hotlist_item));

  if (!item) return make_no_memory_error(4);

  perm_dirname = malloc(strlen(directory_name) + 1);

  if (!perm_dirname)
  {
    free(item);

    return make_no_memory_error(5);
  }

  /* Copy the name into the new buffer */

  strcpy(perm_dirname, directory_name);

  /* Initialise the new hotlist item */

  item->type                   = hl_directory;
  item->flags                  = DIRECTORY_FLAGS;
  item->name                   = perm_dirname;
  item->data.directory_content = NULL;

  if (parent)
  {
    /* If we have been given a parent item, link this new */
    /* directory to it.                                   */

    e = hotlist_link(item, parent, position);

    /* If there's an error, free the item structure and name, */
    /* and return that error.                                 */

    if (e)
    {
      free(item);
      free(perm_dirname);

      return e;
    }
  }
  else
  {
    /* If there's no parent item, fill the various pointers */
    /* to other things in the list with NULL.               */

    item->next     = NULL;
    item->previous = NULL;
    item->parent   = NULL;
  }

  /* Record the new item in the hotlist_newitem static */

  hotlist_newitem = item;

  /* Return the address of the new item and exit */

  *new = item;

  return NULL;
}

/*************************************************/
/* hotlist_new_url()                             */
/*                                               */
/* This function creates a new url               */
/* at the end of the passed directory            */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             representing a directory to add   */
/*             the URL to;                       */
/*                                               */
/*             Position within that directory    */
/*             to link to (number of items from  */
/*             the start);                       */
/*                                               */
/*             Description of the URL (e.g. from */
/*             the page title);                  */
/*                                               */
/*             Pointer to the URL itself.        */
/*************************************************/

static _kernel_oserror * hotlist_new_url(hotlist_item * parent, unsigned int position, char * url_description, char * url)
{
  char            * perm_url_desc;
  char            * perm_url;
  hotlist_item    * item;
  _kernel_oserror * e;

  /* Allocate a new hotlist_item structure */

  if ((item = malloc(sizeof(hotlist_item))) == NULL)
  {
    #ifdef TRACE
        if (tl & (1<<25)) Printf("Error: Could not allocate room for new URL item\n");
    #endif

    goto hotlist_new_url_no_memory; /* See bottom of function */
  }

  /* Allocate space for the description string */

  perm_url_desc = malloc(strlen(url_description) + 1);

  if (perm_url_desc == NULL) goto hotlist_new_url_no_memory; /* See bottom of function */

  /* Allocate space for the URL */

  perm_url = malloc(strlen(url) + 1);

  if (perm_url == NULL)
  {
    free(perm_url_desc);
    goto hotlist_new_url_no_memory; /* See bottom of function */
  }

  /* Copy the given description and URL to the allocated space */

  strcpy(perm_url_desc, url_description);
  strcpy(perm_url, url);

  /* Fill in miscellaneous parts of the hotlist_item structure */

  item->type     = hl_url;
  item->flags    = URL_FLAGS;
  item->name     = perm_url_desc;
  item->data.url = perm_url;

  /* Link the item to the rest of the hotlist */

  e = hotlist_link(item, parent, position);

  if (e)
  {
    free(item);
    free(perm_url_desc);
    free(perm_url);

    return e;
  }

  /* Remember which item was added in the hotlist_newitem static */
  /* and exit with no error.                                     */

  hotlist_newitem = item;

  return NULL;

  /* Code for a common error case */

hotlist_new_url_no_memory:

  erb.errnum = Utils_Error_Custom_Normal;

  StrNCpy0(erb.errmess,
           lookup_token("NoMemURL:There is not enough free memory to add another URL to the hotlist.",
           0,
           0));

  return &erb;
}

/*************************************************/
/* hotlist_delete_item()                         */
/*                                               */
/* This function deletes an item from the        */
/* hotlist structure; it will recursively delete */
/* directories.                                  */
/*                                               */
/* Parameters: Pointer to the hotlist_item       */
/*             struct to delete.                 */
/*************************************************/

static void hotlist_delete_item(hotlist_item * item)
{
  if (item)
  {
    switch (item->type)
    {
      /* For a URL item, unlink it from the list */
      /* and free associated memory.             */

      case hl_url:
      {
        hotlist_unlink(item);
        free(item->name);
        free(item->data.url);
        free(item);
      }
      break;

      case hl_directory:
      {
        /* Recursively delete all items in the directory */

        while (item->data.directory_content)
        {
          hotlist_delete_item(item->data.directory_content);
        }

        /* Now unlink the item */

        hotlist_unlink(item);

        /* Free associated memory */

        free(item);
        free(item->name);

      }
      break;

      default:
      {
        #ifdef TRACE

          /* A 'should never happen' case! */

          erb.errnum = Utils_Error_Custom_Message;
          strcpy(erb.errmess, "Unrecognised item type in hotlist_delete_item - possibly memory corruption?");
          show_error_ret(&erb);

        #endif
      }
      break;
    }
  }
}

/*************************************************/
/* hotlist_move_item()                           */
/*                                               */
/* This function takes an item and moves its     */
/* position within the directory tree.           */
/*                                               */
/* Parameters: Pointer to the hotlist_item       */
/*             struct to move;                   */
/*                                               */
/*             Pointer to the hotlist_item       */
/*             structure to move to;             */
/*                                               */
/*             Position relative to that struct  */
/*             for the item to go to, as for     */
/*             hotlist_link.                     */
/*                                               */
/* Assumes:    It is assumed that if the object  */
/*             is a directory it is not being    */
/*             moved into it self or one of its  */
/*             children. If this is not the case */
/*             then both it and its children     */
/*             will become unlinked from the     */
/*             hotlist structure (nasty...).     */
/*************************************************/

static _kernel_oserror * hotlist_move_item(hotlist_item * source, hotlist_item * target, unsigned int position)
{
  _kernel_oserror * e;
  hotlist_item    * newdir, * tempptr;

  if (!(source->type == hl_directory && !hotlist_contents_selected(source->data.directory_content)))
  {
    /* Unlink item from directory structure */

    hotlist_unlink(source);

    /* Link into new position in directory structure */

    return hotlist_link(source, target, position);
  }
  else
  {
    /* Special case - moving a directory whose contents are only partially selected. */
    /* We can't move a directory whose contents are only partially to be moved,      */
    /* there would be nowhere to leave the items which were not moved with it.       */

    /* So, create a new directory based on the old */

    RetError(hotlist_new_directory(target, source->name, position, &newdir));

    /* Move the contents recursively into the new directory */

    source = source->data.directory_content;

    while(source)
    {
      tempptr = source->next;

      if (source->flags & HOTLIST_G_IS_SELECTED)
      {
        hotlist_move_item(source, newdir, HOTLIST_POSITION_END);
        source->flags &= ~HOTLIST_G_IS_SELECTED;
      }

      source = tempptr;
    }
  }
  return NULL;
}

/*************************************************/
/* hotlist_copy_item()                           */
/*                                               */
/* This function copies an item, and in the case */
/* of it being a directory, its children, to the */
/* specified place.                              */
/*                                               */
/* Parameters: Pointer to the hotlist_item       */
/*             struct to copy;                   */
/*                                               */
/*             Pointer to the hotlist_item       */
/*             struct to copy to;                */
/*                                               */
/*             Position relative to that item to */
/*             copy to, as for hotlist_link;     */
/*                                               */
/*             Pointer to a pointer to a         */
/*             hotlist_item struct, in which the */
/*             address of the new item is        */
/*             returned.                         */
/*                                               */
/* Assumes:    It is assumed that if the object  */
/*             is a directory it is not being    */
/*             copied into it self or one of its */
/*             children - if it is the function  */
/*             will keep recursing and           */
/*             eventually run out of stack;      */
/*                                               */
/*             The pointer to the pointer to the */
/*             hotlist_item struct may be NULL.  */
/*************************************************/

static _kernel_oserror * hotlist_copy_item(hotlist_item * source, hotlist_item * target,
                                           unsigned int position, hotlist_item ** new_item)
{
  _kernel_oserror * e;
  hotlist_item    * newdir;

  newdir = NULL;

  switch(source->type)
  {
    /* For a URL, create a new item based on the source one */

    case hl_url:
    {
      return hotlist_new_url(target, position, source->name, source->data.url);
    }
    break;

    /* For a directory, first create a new item based on the source one */

    case hl_directory:
    {
      hotlist_item * content;

      RetError(hotlist_new_directory(target, source->name, position, &newdir));

      /* Now copy the contents recursively */

      content = source->data.directory_content;

      while (content)
      {
        if (content->flags & HOTLIST_G_IS_SELECTED)
        {
          RetError(hotlist_copy_item(content, newdir, HOTLIST_POSITION_END, NULL));
        }

        content = content->next;
      }
    }
    break;
  }

  /* Fill in the new_item return value */

  if (new_item)
  {
    if (newdir) *new_item = newdir;
    else        *new_item = hotlist_newitem;
  }

  return NULL;
}

/*************************************************/
/* hotlist_get_entry_sizes()                     */
/*                                               */
/* This function reads the size of the sprites   */
/* to be used by the hotlist and from them       */
/* determines the size of the hotlist entries.   */
/*                                               */
/* Parameters: Pointer to an int, in which the   */
/*             height of an item is placed in    */
/*             OS units;                         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             minimum width of a directory item */
/*             is returned, in OS units;         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             minimum width of a URL item is    */
/*             returned, in OS units.            */
/*                                               */
/* Assumes:    Any of the pointers may be NULL.  */
/*************************************************/

static _kernel_oserror * hotlist_get_entry_sizes(unsigned int * item_height, unsigned int * item_dir_width, unsigned int * item_url_width)
{
  _kernel_oserror * e;
  int               width, height;

  /* Get the open directory sprite size */

  RetError(read_sprite_size(OPEN_DIRECTORY_SPRITE, &width, &height));

  /* Use this for the minimum width of a directory entry */
  /* and the height of an item.                          */

  if (item_dir_width) *item_dir_width = width;
  if (item_height)    *item_height    = height;

  /* Now read the closed sprite; if the size is greater */
  /* than the width or height found above, use the new  */
  /* sizes instead.                                     */

  RetError(read_sprite_size(CLOSED_DIRECTORY_SPRITE, &width, &height));

  if (item_height    && height > *item_height)    *item_height    = height;
  if (item_dir_width && width  > *item_dir_width) *item_dir_width = width;

  /* Similarly for the insert item sprite. */

  RetError(read_sprite_size(INSERT_DIRECTORY_SPRITE, &width, &height));

  if (item_height    && height > *item_height)    *item_height    = height;
  if (item_dir_width && width  > *item_dir_width) *item_dir_width = width;

  /* Find the URL sprite size, and if required increase the */
  /* minimum entry height again based on this.              */

  RetError(read_sprite_size(URL_SPRITE, &width, &height));

  if (item_height && height > *item_height) *item_height= height;

  /* Set the URL width to the value found above and add 8 to */
  /* all of them for aesthetics.                             */

  if (item_url_width) *item_url_width = width + 8;
  if (item_dir_width) *item_dir_width += 8;
  if (item_height)    *item_height    += 8;

  return NULL;
}

/*************************************************/
/* hotlist_set_flags()                           */
/*                                               */
/* This function will recursively set flags for  */
/* either a specified type of hotlist_item or    */
/* all hotlist_items, to the given value. All    */
/* items which are changed will have their       */
/* HOTLIST_G_REDRAW_NOW bit set.                 */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start on;                      */
/*                                               */
/*             Type of hotlist_item to set flags */
/*             for, or hl_ALL for all types;     */
/*                                               */
/*             Flags to set.                     */
/*                                               */
/* Returns:    1 if any flags were set, else 0.  */
/*************************************************/

static int hotlist_set_flags(hotlist_item * list, hotlist_type type, unsigned int flags)
{
  int changed = 0;

  while (list)
  {
    /* Alter all items, or those of the correct type */

    if (type == hl_ALL || type == list->type)
    {
      if (list->flags | flags != list->flags)
      {
        list->flags |= HOTLIST_G_REDRAW_NOW;
        changed = 1;
      }

      list->flags |= flags;
    }

    /* Recursive call for directories */

    if (list->type == hl_directory)
    {
      if (hotlist_set_flags(list->data.directory_content, type, flags)) changed = 1;
    }

    list = list->next;
  }

  return changed;
}

/*************************************************/
/* hotlist_clear_flags()                         */
/*                                               */
/* This function will recursively clear flags    */
/* for either a specified type of hotlist_item   */
/* or all hotlist_items. All items changed will  */
/* have their HOTLIST_G_REDRAW_NOW bit set,      */
/* unless, of course, the routine is called to   */
/* clear that bit.                               */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start on;                      */
/*                                               */
/*             Type of hotlist_item to set flags */
/*             for, or hl_ALL for all types;     */
/*                                               */
/*             Flags clear word (any bits set in */
/*             this word are cleared in the item */
/*             flags).                           */
/*                                               */
/* Returns:    1 if flags were cleared, else 0.  */
/*************************************************/

static int hotlist_clear_flags(hotlist_item * list, hotlist_type type, unsigned int flags)
{
  int changed = 0;

  while (list)
  {
    /* Only alter the requested item types */

    if (type == hl_ALL || type == list->type)
    {
      if ((list->flags & ~flags) != list->flags)
      {
        list->flags |= HOTLIST_G_REDRAW_NOW;
        changed = 1;
      }

      list->flags &= ~flags;
    }

    /* Recursive call for directories */

    if (list->type == hl_directory)
    {
      if (hotlist_clear_flags(list->data.directory_content, type, flags)) changed = 1;
    }

    list = list->next;
  }

  return changed;
}

/*************************************************/
/* hotlist_find_item()                           */
/*                                               */
/* This function will recursivly scan through    */
/* a hotlist structure and return a pointer to   */
/* the n'th item.  It will only recurse through  */
/* open directories.                             */
/*                                               */
/* Parameters: Pointer to a hotlist_item at the  */
/*             top of the directory to scan;     */
/*                                               */
/*             The nth item to return within it. */
/*                                               */
/* Returns:    Pointer to the requested item or  */
/*             NULL if it does not exist.        */
/*************************************************/

static hotlist_item * hotlist_find_item(hotlist_item * list, unsigned int item_no)
{
  int curr_item = 0;

  return hotlist_find_item_r(list, item_no, &curr_item);
}

/*************************************************/
/* hotlist_find_item_r()                         */
/*                                               */
/* Recursive back-end to hotlist_find_item.      */
/*                                               */
/* Parameters: Pointer to a hotlist_item at the  */
/*             top of the directory to scan;     */
/*                                               */
/*             The nth item to return within it; */
/*                                               */
/*             Pointer to an int, in which the   */
/*             number of the current item is     */
/*             accumulated (initialised to an    */
/*             appropriate value, usually 0).    */
/*                                               */
/* Returns:    As hotlist_find_item.             */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static hotlist_item * hotlist_find_item_r(hotlist_item * list, unsigned int item_no, int * curr_item)
{
  hotlist_item * temp;

  while (list)
  {
    if (*curr_item == item_no) return list; /* Found the list item */

    /* Increment the item counter */

    *curr_item += 1;

    /* Recursively scan open directories */

    if (list->type == hl_directory && list->flags & HOTLIST_D_IS_OPEN)
    {
      temp = hotlist_find_item_r(list->data.directory_content, item_no, curr_item);
      if (temp) return temp;
    }

    /* Move to the next list item */

    list = list->next;
  }

  return NULL; /* List does not extend far enough */
}

/*************************************************/
/* hotlist_find_no_from_item()                   */
/*                                               */
/* This function will recursivly scan through    */
/* a hotlist structure and return the position   */
/* of the specified item. It will only recurse   */
/* through open directories.                     */
/*                                               */
/* Parameters: Pointer to a hotlist_item to      */
/*             start at;                         */
/*                                               */
/*             Pointer to the item whos position */
/*             is to be returned.                */
/*                                               */
/* Returns:    The item position or -1 if it is  */
/*             not found.                        */
/*************************************************/

static int hotlist_find_no_from_item(hotlist_item * item)
{
  int curr_item = 0;

  return hotlist_find_no_from_item_r(hotlist_root->data.directory_content, item, &curr_item);
}

/*************************************************/
/* hotlist_find_no_from_item_r()                 */
/*                                               */
/* Recursive back-end to                         */
/* hotlist_find_no_from_item.                    */
/*                                               */
/* Parameters: Pointer to a hotlist_item to      */
/*             start at;                         */
/*                                               */
/*             Pointer to the item whos position */
/*             is to be returned;                */
/*                                               */
/*             Pointer to an int, in which the   */
/*             number of the current item is     */
/*             accumulated (initialised to an    */
/*             appropriate value, usually 0).    */
/*                                               */
/* Returns:    As hotlist_find_no_from_item.     */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static int hotlist_find_no_from_item_r(hotlist_item * list, hotlist_item * item, int * curr_item)
{
  /* Start the search at the given item */

  while (list)
  {
    if (item == list) return *curr_item; /* Found the list item */

    *curr_item += 1;

    /* Recursively scan open directories */

    if (list->type == hl_directory && list->flags & HOTLIST_D_IS_OPEN)
    {
      int found;

      found = hotlist_find_no_from_item_r(list->data.directory_content, item, curr_item);
      if (found >= 0) return found;
    }

    /* Move to the next item */

    list = list->next;
  }

  /* Didn't find it */

  return -1;
}

/*************************************************/
/* hotlist_find_selected_item()                  */
/*                                               */
/* This function returns a pointer to the first  */
/* selected item found.                          */
/*                                               */
/* Returns:    Pointer to a hotlist_item struct  */
/*             which was the first selected item */
/*             found in a search starting        */
/*             from the root, or NULL if there   */
/*             is nothing selected.              */
/*************************************************/

static hotlist_item * hotlist_find_selected_item(void)
{
  return hotlist_find_selected_item_r(hotlist_root);
}

/*************************************************/
/* hotlist_find_selected_item_r()                */
/*                                               */
/* Recursive back-end to                         */
/* hotlist_find_selected_item.                   */
/*                                               */
/* Parameters: Pointer to a hotlist_item         */
/*             struct to start at.               */
/*                                               */
/* Returns:    As hotlist_find_selected_item.    */
/*************************************************/

hotlist_item * hotlist_find_selected_item_r(hotlist_item * list)
{
  while (list)
  {
    if (list->flags & HOTLIST_G_IS_SELECTED) return list; /* Found it */

    /* Recursively scan all directories */

    if (list->type == hl_directory)
    {
      hotlist_item * found;

      found = hotlist_find_selected_item_r(list->data.directory_content);
      if (found) return found;
    }

    /* Move to the next item */

    list = list->next;
  }

  /* Didn't find it */

  return NULL;
}

/*************************************************/
/* hotlist_count_selected_items()                */
/*                                               */
/* Count the number of items that are currently  */
/* selected in the hotlist window.               */
/*                                               */
/* Returns:    The number of selected items.     */
/*************************************************/

static unsigned int hotlist_count_selected_items(void)
{
  int count = 0;

  hotlist_count_selected_items_r(hotlist_root->data.directory_content, &count);

  return count;
}

/*************************************************/
/* hotlist_count_selected_items_r()              */
/*                                               */
/* Recursive back-end to                         */
/* hotlist_count_selected_items.                 */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             representing the first item in a  */
/*             directory to count;               */
/*                                               */
/*             Pointer to an int, in which the   */
/*             total is accumulated.             */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static void hotlist_count_selected_items_r(hotlist_item * list, int * count)
{
  while (list)
  {
    if (list->flags & HOTLIST_G_IS_SELECTED)
    {
      *count += 1;
    }
    else
    {
      /* Only recurse through directories which are not selected */

      if (list->type == hl_directory)
      {
        hotlist_count_selected_items_r(list->data.directory_content, count);
      }
    }

    list = list->next;
  }
}

/*************************************************/
/* hotlist_count_displayed_items()               */
/*                                               */
/* This routine counts the number of items       */
/* displayed in the hotlist window.              */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start at.                      */
/*                                               */
/* Returns:    The number of displayed items.    */
/*************************************************/

static unsigned int hotlist_count_displayed_items(hotlist_item * list)
{
  int count = 0;

  hotlist_count_displayed_items_r(list, &count);

  return count;
}

/*************************************************/
/* hotlist_count_displayed_items_r()             */
/*                                               */
/* Recursive back-end to                         */
/* hotlist_count_displayed_items.                */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start at;                      */
/*                                               */
/*             Pointer to an int, in which the   */
/*             total is accumulated.             */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static void hotlist_count_displayed_items_r(hotlist_item *list, int * count)
{
  while (list)
  {
    /* Recursive scan for open directories */

    if (list->type == hl_directory && list->flags & HOTLIST_D_IS_OPEN)
    {
      hotlist_count_displayed_items_r(list->data.directory_content, count);
    }

    list = list->next;

    *count += 1;
  }
}

/*************************************************/
/* hotlist_draw()                                */
/*                                               */
/* Redraws a specified region of the hotlist.    */
/* Assumes graphics rectangles are set up        */
/* appropriately (e.g. the function is called    */
/* during a Wimp redraw session).                */
/*                                               */
/* Parameters: Pointer to a hotlist_item giving  */
/*             the list that we're to draw;      */
/*                                               */
/*             First item number to draw;        */
/*                                               */
/*             Last item number to draw.         */
/*************************************************/

static _kernel_oserror * hotlist_draw(hotlist_item * list, unsigned int first_item, unsigned int last_item)
{
  _kernel_oserror * e;
  int               curr_item = 0;
  unsigned int      item_height, item_dir_width, item_url_width;

  RetError(hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width));

  return hotlist_draw_r(list,
                        first_item,
                        last_item,
                        &curr_item,
                        0,
                        item_height,
                        item_dir_width,
                        item_url_width);
}

/*************************************************/
/* hotlist_draw_r()                              */
/*                                               */
/* Recursive back-end to hotlist_draw.           */
/*                                               */
/* Parameters: Pointer to a hotlist_item giving  */
/*             the list that we're to draw;      */
/*                                               */
/*             First item number to draw;        */
/*                                               */
/*             Last item number to draw;         */
/*                                               */
/*             Pointer to an int, which is used  */
/*             to accumulate the current item    */
/*             number - the contents should be   */
/*             initialised to an appropriate     */
/*             value for the first three         */
/*             parameters (usually, zero);       */
/*                                               */
/*             Level of indentation (OS units);  */
/*                                               */
/*             Height of an item (OS units);     */
/*                                               */
/*             Width of a directory item (OS     */
/*             units);                           */
/*                                               */
/*             Width of a URL item (OS units).   */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static _kernel_oserror * hotlist_draw_r(hotlist_item *list, unsigned int first_item, unsigned int last_item,
                                        int * curr_item, unsigned int indent, unsigned int item_height,
                                        unsigned int item_dir_width, unsigned int item_url_width)
{
  _kernel_oserror * e;
  WimpIconBlock     icon;
  unsigned int      temp_width;
  int               text_width;

  while (list)
  {
    /* Break out if done all items to be displayed */

    if (*curr_item > last_item) return NULL;

    /* Don't draw until we reach the required first item */

    if (*curr_item >= first_item)
    {
      /* Construct an icon block for Wimp_PlotIcon. First, icon flags. */

      icon.flags = HOTLIST_SPRITE_ICON_FLAGS;

      if (((list->flags & HOTLIST_G_IS_SELECTED) ? 1 : 0) ^
          ((list->flags & HOTLIST_G_DRAG_SELECTED) ? 1 : 0)
         ) icon.flags |= WimpIcon_Selected;

      /* Point to the main sprite pool */

      icon.data.is.sprite_area = (void*) sprite_block;

      /* Set type-dependent characteristics */

      switch(list->type)
      {
        case hl_url:
        {
          /* A URL item; set the sprite name and item width accordingly */

          icon.data.is.sprite             = URL_SPRITE;
          icon.data.is.sprite_name_length = strlen(URL_SPRITE);
          temp_width = item_url_width;
        }
        break;

        case hl_directory:
        {
          /* Directories are a bit more complex, as they can be open, */
          /* closed, or showing a '+' if a dragged item is hovering   */
          /* over it and would drop 'into' the directory if released. */

          temp_width = item_dir_width; /* Width is based on the widest sprite of the three */

          if (list->flags & HOTLIST_D_IS_HIGHLIGHTED)
          {
            /* The item is either selected (something is 'hovering' over it)... */

            icon.data.is.sprite             = INSERT_DIRECTORY_SPRITE;
            icon.data.is.sprite_name_length = strlen(INSERT_DIRECTORY_SPRITE);
          }
          else
          {
            /* ...or unselected. In that case it is either open or closed. */

            if (list->flags & HOTLIST_D_IS_OPEN)
            {
              icon.data.is.sprite             = OPEN_DIRECTORY_SPRITE;
              icon.data.is.sprite_name_length = strlen(OPEN_DIRECTORY_SPRITE);
            }
            else
            {
              icon.data.is.sprite             = CLOSED_DIRECTORY_SPRITE;
              icon.data.is.sprite_name_length = strlen(CLOSED_DIRECTORY_SPRITE);
            }
          }
        }
        break;

        default:
        {
          temp_width = 0; /* Should never happen... */
        }
        break;
      }

      /* Set the item bounding box appropriately */

      icon.bbox.xmin = indent;
      icon.bbox.xmax = indent + temp_width;

      icon.bbox.ymin = -item_height * (*curr_item) - item_height;
      icon.bbox.ymax = -item_height * (*curr_item);

      /* Plot the item */

      RetError(wimp_plot_icon(&icon));

      /* We now need to plot the item text. First, get the width, */
      /* taking the opportunity to point the icon that we'll plot */
      /* to the appropriate text, too.                            */

      if (list->type == hl_url && hl_show_urls)
      {
        /* If this is a URL item and we are to show URLs, then find */
        /* the width of the URL rather than the description.        */

        RetError(utils_text_width(list->data.url, &text_width, 0));

        /* Point to the URL text */

        icon.data.it.buffer      = list->data.url;
        icon.data.it.buffer_size = strlen(list->data.url);
        icon.data.it.validation  = NULL;
      }
      else
      {
        /* Otherwise (any other item, or a URL item when we're showing */
        /* descriptions) just find the name's width.                   */

        RetError(utils_text_width(list->name, &text_width, 0));

        /* Point to the description */

        icon.data.it.buffer      = list->name;
        icon.data.it.buffer_size = strlen(list->name);
        icon.data.it.validation  = NULL;
      }

      /* Set the bounding box for the text. The hard coded */
      /* constants are nothing critical - just aesthetics. */

      icon.bbox.xmin = indent + temp_width + 2;
      icon.bbox.xmax = indent + temp_width + 2 + text_width + 12;

      icon.bbox.ymin = -item_height * (*curr_item) - item_height + 2;
      icon.bbox.ymax = -item_height * (*curr_item) - 2;

      /* Set the flags accordingly if the text is selected or unselected */

      if (((list->flags & HOTLIST_G_IS_SELECTED) ? 1 : 0) ^
          ((list->flags & HOTLIST_G_DRAG_SELECTED) ? 1 : 0)) icon.flags = HOTLIST_TEXT_ICON_FLAGS_SELECTED;
      else                                                   icon.flags = HOTLIST_TEXT_ICON_FLAGS_UNSELECTED;

      /* Finally, plot the item. */

      RetError(wimp_plot_icon(&icon));
    }

    /* Increment the item number */

    *curr_item += 1;

    /* Recursive redraw for directories */

    if (list->type == hl_directory && list->flags & HOTLIST_D_IS_OPEN)
    {
      RetError(hotlist_draw_r(list->data.directory_content,
                              first_item,
                              last_item,
                              curr_item,
                              indent + item_dir_width,
                              item_height,
                              item_dir_width,
                              item_url_width));
    }

    /* Move on down the list */

    list = list->next;
  }

  return NULL;
}

/*************************************************/
/* hotlist_get_max_width_r                       */
/*                                               */
/* Recursive back-end to hotlist_get_max_width.  */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start at;                      */
/*                                               */
/*             An indent in OS units, as for     */
/*             redrawing the hotlist;            */
/*                                               */
/*             Pointer to an int, in which the   */
/*             width of the widest item so far   */
/*             is accumulated;                   */
/*                                               */
/*             Height of a hotlist item in OS    */
/*             units;                            */
/*                                               */
/*             Width of a hotlist directory      */
/*             sprite in OS units;               */
/*                                               */
/*             Width of a hotlist URL sprite in  */
/*             OS units.                         */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static _kernel_oserror * hotlist_get_max_width_r(hotlist_item *list, unsigned int indent, int * max_width,
                                                 unsigned int item_height, unsigned int item_dir_width, unsigned int item_url_width)
{
  _kernel_oserror * e;
  unsigned int      item_width;
  int               text_width;

  while (list)
  {
    /* Get width of the icon */

    switch (list->type)
    {
      case hl_directory: item_width = item_dir_width; break;
      case hl_url:       item_width = item_url_width; break;
      default:           item_width = 0;              break;
    }

    /* Work out width of the text */

    if (list->type == hl_url && hl_show_urls) e = utils_text_width(list->data.url, &text_width, 0);
    else                                      e = utils_text_width(list->name,     &text_width, 0);

    if (e) return e;

    /* Account for the indent and spacers (aesthetics) */

    item_width += indent + 2 + text_width + 12;

    /* If this is wider than so far recorded, store the new value */

    if (item_width > *max_width) *max_width = item_width;

    /* Recursive call for open directories */

    if (list->type == hl_directory && list->flags & HOTLIST_D_IS_OPEN)
    {
      RetError(hotlist_get_max_width_r(list->data.directory_content,
                                       indent + item_dir_width,
                                       max_width,
                                       item_height,
                                       item_dir_width,
                                       item_url_width));
    }

    /* Move on down the list */

    list = list->next;
  }

  return NULL;
}

/*************************************************/
/* hotlist_get_max_width()                       */
/*                                               */
/* This function returns the maximum width of    */
/* of the displayed hotlist entries.             */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start at. This is assumed to   */
/*             be at zero indent from the left   */
/*             hand side.                        */
/*                                               */
/* Returns:    Of all visible entries, the width */
/*             of the widest, in OS units.       */
/*************************************************/

static unsigned int hotlist_get_max_width(hotlist_item * list)
{
  unsigned int item_height, item_dir_width, item_url_width;
  int          widest = 0;

  /* Find basic item size information */

  if (hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width)) return 0;

  /* Scan the directory and all open directories with in it for */
  /* the widest item                                            */

  if (hotlist_get_max_width_r(list,
                              0,
                              &widest,
                              item_height,
                              item_dir_width,
                              item_url_width)) return 0;

  /* Return the result */

  return widest;
}

/*************************************************/
/* hotlist_redraw_now()                          */
/*                                               */
/* This function redraws all visible items with  */
/* the HOTLIST_G_REDRAW_NOW bit set.             */
/*************************************************/

static void hotlist_redraw_now(void)
{
  int curr_item = 0;

  hotlist_redraw_now_r(hotlist_root->data.directory_content, &curr_item);
}

/*************************************************/
/* hotlist_redraw_now_r()                        */
/*                                               */
/* Recursive back-end to hotlist_redraw_now.     */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             to start on;                      */
/*                                               */
/*             Pointer to an int, in which the   */
/*             current item number being redrawn */
/*             is accumulated.                   */
/*                                               */
/* Assumes:    The pointer to the int may *not*  */
/*             be NULL.                          */
/*************************************************/

static void hotlist_redraw_now_r(hotlist_item * list, int * curr_item)
{
  while (list)
  {
    if (list->flags & HOTLIST_G_REDRAW_NOW)
    {
      /* Redraw just the one item */

      hotlist_redraw_items(*curr_item, *curr_item);

      /* Clear the flag */

      list->flags &= ~HOTLIST_G_REDRAW_NOW;
    }

    *curr_item += 1;

    if (list->type == hl_directory && list->flags & HOTLIST_D_IS_OPEN)
    {
      /* Recursive call for open directories */

      hotlist_redraw_now_r(list->data.directory_content, curr_item);
    }

    list = list->next;
  }
}

/*************************************************/
/* hotlist_add()                                 */
/*                                               */
/* Add a new URL to the hotlist.                 */
/*                                               */
/* Parameters: Description of the URL (e.g. from */
/*             the page title);                  */
/*                                               */
/*             Pointer to the URL itself;        */
/*                                               */
/*             0 to add to the top, 1 to add to  */
/*             the bottom.                       */
/*************************************************/

_kernel_oserror * hotlist_add(char * description, char * url, int at_bottom)
{
  _kernel_oserror * e = NULL;
  int               position;

  position = at_bottom ? HOTLIST_POSITION_END : HOTLIST_POSITION_BEGINNING;

  /* Add the item and ensure the window extent etc. is correct */

  e = hotlist_new_url(hotlist_root,
                      position,
                      description,
                      url);

  if (!e) hotlist_preopen();

  /* Optimise the redraw to do as little as possible depending */
  /* upon where in the list the item was added.                */

  switch (position)
  {
    default:
    case HOTLIST_POSITION_END:
    {
      hotlist_redraw_now();
    }
    break;

    case HOTLIST_POSITION_BEGINNING:
    {
      hotlist_redraw_items(0,
                           hotlist_count_displayed_items(hotlist_root->data.directory_content));
    }
    break;
  }

  if (!e) return hotlist_modified(HL_MODIFIED_ADD);

  return e;
}

/*************************************************/
/* hotlist_get_shape()                           */
/*                                               */
/* This function calculates the xmin and xmax    */
/* of the passed hotlist_item                    */
/*                                               */
/* Parameters: *xmin                             */
/*             *xmax                             */
/*             item                              */
/*************************************************/

static void hotlist_get_shape(unsigned int *xmin,
                       unsigned int *xmax,
                       hotlist_item *item)
{
  unsigned int item_height, item_dir_width, item_url_width;
  int count = 0, temp_width, text_width;
  hotlist_item *tempitem;
  _kernel_oserror *e;

  e = hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  if (e)
  {
    show_error_ret(e);
    *xmin = 0;
    *xmax = 0;
    return;
  }

  tempitem = item;
  while(tempitem)
  {
    tempitem = tempitem->parent;
    count++;
  }

  count -= 2;

  if (item->type == hl_url && hl_show_urls) utils_text_width(item->data.url, &text_width, 0);
  else                                      utils_text_width(item->name,     &text_width, 0);

  switch(item->type)
  {
    case hl_directory:
    temp_width = item_dir_width;
    break;

    case hl_url:
    temp_width = item_url_width;
    break;

    default:
    temp_width = 0;
    break;
  }

  *xmin = count * item_dir_width;
  *xmax = count * item_dir_width + temp_width + 2 + text_width + 12;
}

/*************************************************/
/* hotlist_directory_open_close()                */
/*                                               */
/* This function opens or closes a directory     */
/* along with all required redrawing             */
/*                                               */
/* Parameters: hotlist_item                      */
/*             item number on screen             */
/*************************************************/

static void hotlist_directory_open_close(hotlist_item *item, unsigned int itemno)
{
  unsigned int item_height, item_dir_width, item_url_width;
  int top, window_handle;
  BBox bbox;
  _kernel_oserror *e;

  e = hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  if (e)
  {
    show_error_ret(e);
    return;
  }

  item->flags ^= HOTLIST_D_IS_OPEN;

  hotlist_clear_flags(item->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);

  hotlist_preopen();
  show_error(window_get_wimp_handle(0, hotlist_windowid, &window_handle));

  window_get_extent(0, hotlist_windowid, &bbox);

  top = -itemno * item_height;				/* Window relative coordinate */

  if (item->data.directory_content)
  {
    show_error(wimp_force_redraw(window_handle,
                                 bbox.xmin,
                                 bbox.ymin,
                                 bbox.xmax,
                                 top));
  }
  else
  {
    show_error(wimp_force_redraw(window_handle,
                                 bbox.xmin,
                                 top-item_height,
                                 bbox.xmax,
                                 top));
  }
}

/*************************************************/
/* hotlist_redraw_items()                        */
/*                                               */
/* This function forces the redraw of a set of   */
/* items                                         */
/*                                               */
/* Parameters: itemno                            */
/*************************************************/

static void hotlist_redraw_items(unsigned int firstitem, unsigned int lastitem)
{
  unsigned int item_height, item_dir_width, item_url_width;
  BBox bbox;
  int window_handle;
  _kernel_oserror *e;

  e = hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  if (e)
  {
    show_error_ret(e);
    return;
  }

  show_error(window_get_wimp_handle(0, hotlist_windowid, &window_handle));
  window_get_extent(0, hotlist_windowid, &bbox);

  wimp_force_redraw(window_handle,
                    bbox.xmin,
                    - (lastitem + 1) * item_height,
                    bbox.xmax,
                    - firstitem * item_height);
}

/*************************************************/
/* hotlist_clear_selection()                     */
/*                                               */
/* This function unselects all items and redraws */
/* appropriate items                             */
/*************************************************/

static void hotlist_clear_selection(void)
{
  hotlist_clear_flags(hotlist_root->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
  hotlist_redraw_now();
}

/*************************************************/
/* hotlist_launch_url()                          */
/*                                               */
/* This function launches the url in the passed  */
/* item                                          */
/*                                               */
/* Parameters: hotlist_item                      */
/*************************************************/

static void hotlist_launch_url(hotlist_item * item)
{
  #ifdef TRACE
    if (tl & (1u<<25)) hotlist_display_item(item);
  #endif

  windows_create_browser(item->data.url, NULL, NULL, NULL, 0);
}

/*************************************************/
/* hotlist_process_click_on_item()               */
/*                                               */
/* This function handles mouse clicks on hotlist */
/* items                                         */
/*************************************************/

static _kernel_oserror * hotlist_process_click_on_item(unsigned int itemno,
                                                hotlist_item *item,
                                                int buttons,
                                                int x,
                                                int y)
{
  switch(buttons)
  {
    case Wimp_MouseButtonSelect:
    if (last_selected_item == itemno)
    {
      switch(item->type)
      {
        case hl_directory:
        hotlist_directory_open_close(item, itemno);
        break;

        case hl_url:
        hotlist_launch_url(item);
        break;
      }
    item->flags &= ~HOTLIST_G_IS_SELECTED;
    hotlist_redraw_items(itemno, itemno);
    }
    break; /* Double click select */

    case Wimp_MouseButtonAdjust:
    if (last_selected_item == itemno)
    {
      switch(item->type)
      {
        case hl_directory:
        hotlist_directory_open_close(item, itemno);
        break;

        case hl_url:
        hotlist_launch_url(item);
        toolbox_hide_object(0, hotlist_windowid);
        break;
      }
      item->flags &= ~HOTLIST_G_IS_SELECTED;
      hotlist_redraw_items(itemno, itemno);
    }
    break; /* Double click adjust */

    case 64:                     /* Drag select */
    hotlist_start_drag();
     break;

    case 16:                     /* Drag adjust */
    if (!(item->flags & HOTLIST_G_IS_SELECTED))
    {
      item->flags |= HOTLIST_G_IS_SELECTED;

      last_selected_item = itemno;
    }
    hotlist_start_drag();
    hotlist_redraw_items(itemno, itemno);
    break;

    case 1024:                   /* Click select */
    if (item->flags & HOTLIST_G_IS_SELECTED)
    {
      /* Do nothing when selected */
    }
    else
    {
      hotlist_clear_selection();
      item->flags |= HOTLIST_G_IS_SELECTED | HOTLIST_G_REDRAW_NOW;
      if (item->type == hl_directory)
      {
        hotlist_set_flags(item->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
      }
      hotlist_redraw_now();
    }
    last_selected_item = itemno;
    break;

    case 256:                   /* Click adjust */
    item->flags ^= HOTLIST_G_IS_SELECTED;
    item->flags |= HOTLIST_G_REDRAW_NOW;
    last_selected_item = itemno;
    if (item->type == hl_directory)
    {
      if (item->flags & HOTLIST_G_IS_SELECTED)
      {
        hotlist_set_flags(item->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
      }
      else
      {
        hotlist_clear_flags(item->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
      }
    }
    hotlist_redraw_now();
    break;
  }

  return NULL;
}

/*************************************************/
/* hotlist_process_click()                       */
/*                                               */
/* This function handles mouse clicks on the     */
/* hotlist window                                */
/*                                               */
/* Parameters: x position                        */
/*             y position                        */
/*             button state                      */
/*************************************************/

static _kernel_oserror * hotlist_process_click(int x, int y, int buttons)
{
  unsigned int item_height, item_dir_width, item_url_width;
  hotlist_item *item;
  unsigned int xmin, xmax, itemno;
  _kernel_oserror *e;

  RetError(hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width));

  itemno = -y / item_height;
  item = hotlist_find_item(hotlist_root->data.directory_content, itemno);
  if (item) hotlist_get_shape(&xmin, &xmax, item);

  if (item && x >= xmin && x <= xmax)
  {
    return hotlist_process_click_on_item(itemno, item, buttons, x, y);
  }
  else
  {
    switch(buttons)
    {
      case Wimp_MouseButtonSelect:
      break; /* Double click select */

      case Wimp_MouseButtonAdjust:
      break; /* Double click adjust */

      case 64:                     /* Drag select */
      hotlist_clear_selection();
      hotlist_selection_box_start();
      break;

      case 16:                     /* Drag adjust */
      hotlist_selection_box_start();
      break;

      case 1024:
      hotlist_clear_selection();
      last_selected_item = 0xffffffff;
      break;                   /* Click select */

      case 256:
      last_selected_item = 0xffffffff;
      break;                   /* Click adjust */
    }
  }
  return NULL;
}

/*************************************************/
/* hotlist_preopen()                             */
/*                                               */
/* This function should be called before opening */
/* the hotlist window. If the window is already  */
/* showing, then the extent may be altered but   */
/* it will never shrink the visible area of the  */
/* window. If the window is currently closed, it */
/* will set the extent to the minimum possible   */
/* value, which may well drag the visible area   */
/* down too.                                     */
/*                                               */
/* Returns:    1 if window was already open, 0   */
/*             or 0 if it was closed.            */
/*************************************************/

static int hotlist_preopen(void)
{
  _kernel_oserror         * e;
  WimpGetWindowStateBlock   state;
  BBox                      bbox;
  ObjectId                  parent_id;
  ComponentId               parent_component;
  unsigned int              item_height, item_dir_width, item_url_width;
  unsigned int              number, maxlen;
  unsigned int              objectstate;
  int                       height, width;

  e = toolbox_get_object_state(0, hotlist_windowid, &objectstate);

  if (e)
  {
    show_error_ret(e);
    return 0;
  }

  e = hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  if (e)
  {
    show_error_ret(e);
    return 0;
  }

  number = hotlist_count_displayed_items(hotlist_root->data.directory_content);
  maxlen = hotlist_get_max_width(hotlist_root->data.directory_content) + 4;

  e = window_get_wimp_handle(0, hotlist_windowid, &state.window_handle);

  if (e)
  {
    show_error_ret(e);
    return 0;
  }

  e = wimp_get_window_state(&state);
  if (e)
  {
    show_error_ret(e);
    return 0;
  }

  if (number < HOTLIST_WINDOW_MIN_HEIGHT) number = HOTLIST_WINDOW_MIN_HEIGHT;
  if (maxlen < HOTLIST_WINDOW_MIN_WIDTH)  maxlen = HOTLIST_WINDOW_MIN_WIDTH;

  if (objectstate & Toolbox_GetObjectState_Showing)
  {
    e = window_get_extent(0, hotlist_windowid, &bbox);
    if (e)
    {
      show_error_ret(e);
      return 0;
    }
  }

  bbox.ymin = -number * item_height;
  bbox.xmax = maxlen;

  if (objectstate & Toolbox_GetObjectState_Showing)
  {
    width  = state.visible_area.xmax - state.visible_area.xmin;
    height = state.visible_area.ymax - state.visible_area.ymin + hotlist_bbar_size;

    if (bbox.ymin > -height) bbox.ymin = -height;
    if (bbox.xmax < width) bbox.xmax = width;
  }

  bbox.ymax = hotlist_bbar_size;
  bbox.xmin = 0;

  e = window_set_extent(0, hotlist_windowid, &bbox);
  if (e)
  {
    show_error_ret(e);
    return 0;
  }

  if (objectstate & Toolbox_GetObjectState_Showing)
  {
    e = window_get_wimp_handle(0, hotlist_windowid, &state.window_handle);
    if (!e) e = wimp_get_window_state(&state);
    if (!e) e = toolbox_get_parent(0, hotlist_windowid, &parent_id, &parent_component);
    if (!e) e = toolbox_show_object(0, hotlist_windowid, Toolbox_ShowObject_FullSpec, &(state.visible_area), parent_id, parent_component);

    if (e)
    {
      show_error_ret(e);
      return 0;
    }
  }

  return !!(objectstate & Toolbox_GetObjectState_Showing);
}

/*************************************************/
/* hotlist_redraw_handler()                      */
/*                                               */
/* This function redraws the hotlist window      */
/*************************************************/

static int hotlist_redraw_handler(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle)
{
  unsigned int item_height, item_dir_width, item_url_width;
  unsigned int first_item, last_item;
  int more;
  WimpRedrawWindowBlock block;

  hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  block.window_handle = event->redraw_window_request.window_handle;

  wimp_redraw_window(&block, &more);
  while(more)
  {
    first_item = -(block.redraw_area.ymax - (block.visible_area.ymax - block.yscroll)) / item_height;
    last_item  = (-(block.redraw_area.ymin - (block.visible_area.ymax - block.yscroll)) / item_height) + 1;

    hotlist_draw(hotlist_root->data.directory_content,
                 first_item,
                 last_item);
    wimp_get_rectangle(&block, &more);
  }
  return 0;
}

/*************************************************/
/* hotlist_mouse_click_handler()                 */
/*                                               */
/* This function handles mouse clicks in the     */
/* hotlist window                                */
/*************************************************/

static int hotlist_mouse_click_handler(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle)
{
  WimpGetWindowStateBlock state;

  state.window_handle = event->mouse_click.window_handle;
  wimp_get_window_state(&state);

  wimp_set_caret_position(state.window_handle, -1, 0, 0, -1, -1);

  hotlist_process_click(event->mouse_click.mouse_x + (state.xscroll - state.visible_area.xmin),
                        event->mouse_click.mouse_y + (state.yscroll - state.visible_area.ymax),
                        event->mouse_click.buttons);
  return 0;
}

/*************************************************/
/* hotlist_menuclose_handler()                   */
/*                                               */
/* This function handles closed menu events      */
/*************************************************/

static int hotlist_menuclose_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  ObjectId submenu_id;
  if (menu_select)
  {
    hotlist_clear_selection();
    menu_select = 0;
  }
  show_error(menu_get_sub_menu_show(0, id_block->self_id, HOTLIST_URL_MENUITEM, &submenu_id));
  if (submenu_id) show_error(toolbox_delete_object(0, submenu_id));
  menu_set_sub_menu_show(0, id_block->self_id, HOTLIST_URL_MENUITEM, 0);
  return 0;
}



void hotlist_set_menu_details(ObjectId menuid)
{
  hotlist_item * item;
  char entrytext[32];
  ObjectId submenu_id;

  switch(hotlist_count_selected_items())
  {
    case 0:
    menu_set_entry_text(0, menuid, HOTLIST_URL_MENUITEM, "URL ''");
    menu_set_fade(0, menuid, HOTLIST_URL_MENUITEM, 1);
    menu_set_fade(0, menuid, HOTLIST_CLEARSELECTION_MENUITEM, 1); /* Fade clear selection */
    break;

    case -1:
    case 1:
    item = hotlist_find_selected_item();             /* One item selected */

    switch(item->type)
    {
      case hl_directory:
      strcpy(entrytext, "Dir. '");
      toolbox_create_object(0, "HLDirmenu", &submenu_id);
      menu_set_sub_menu_show(0, menuid, HOTLIST_URL_MENUITEM, submenu_id);
      break;

      case hl_url:
      strcpy(entrytext, "URL '");
      toolbox_create_object(0, "HLURLmenu", &submenu_id);
      menu_set_sub_menu_show(0, menuid, HOTLIST_URL_MENUITEM, submenu_id);
      break;

    }

    strncat(entrytext, item->name, 10);
    if (strlen(item->name) > 10) strcat(entrytext, "...");
    strcat(entrytext, "'");
    menu_set_entry_text(0, menuid, HOTLIST_URL_MENUITEM, entrytext);
    menu_set_fade(0, menuid, HOTLIST_URL_MENUITEM, 0);                    /* unfade URL '' */
    menu_set_fade(0, menuid, HOTLIST_CLEARSELECTION_MENUITEM, 0);         /* unfade clear selection */
    break;

    default:
    menu_set_entry_text(0, menuid, HOTLIST_URL_MENUITEM, "Selection");    /* Multiple items selected */
    menu_set_fade(0, menuid, HOTLIST_URL_MENUITEM, 0);                    /* unfade URL '' */
    menu_set_fade(0, menuid, HOTLIST_CLEARSELECTION_MENUITEM, 0);         /* unfade clear selection */
    toolbox_create_object(0, "HLSlctmenu", &submenu_id);
    menu_set_sub_menu_show(0, menuid, HOTLIST_URL_MENUITEM, submenu_id);
    break;

  }

  menu_get_sub_menu_show(0, menuid, HOTLIST_DISPLAY_MENUITEM, &submenu_id);

  if (hl_show_urls)
  {
    menu_set_tick(0, submenu_id, HOTLIST_MENU_SHOWDESCRIPTIONS, 0);
    menu_set_tick(0, submenu_id, HOTLIST_MENU_SHOWURLS, 1);
  }
  else
  {
    menu_set_tick(0, submenu_id, HOTLIST_MENU_SHOWDESCRIPTIONS, 1);
    menu_set_tick(0, submenu_id, HOTLIST_MENU_SHOWURLS, 0);
  }
}

/*************************************************/
/* hotlist_menuopen_handler()                    */
/*                                               */
/* This function handles opened menu events      */
/*************************************************/

static int hotlist_menuopen_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  WimpGetWindowStateBlock   state;
  WimpGetPointerInfoBlock   pointerblock;
  hotlist_item            * item;
  unsigned int              item_height, item_dir_width, item_url_width;
  unsigned int              xmin, xmax;
  int                       window_handle;
  ObjectId                  sub_menu;

  hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  if (event_code == HotlistMenuOpened)
  {
    window_get_wimp_handle(0, hotlist_windowid, &window_handle);
    state.window_handle = window_handle;
    wimp_get_window_state(&state);

    wimp_get_pointer_info(&pointerblock);
    pointerblock.x = pointerblock.x + (state.xscroll - state.visible_area.xmin);
    pointerblock.y = pointerblock.y + (state.yscroll - state.visible_area.ymax);

    menu_itemno = -pointerblock.y / item_height;
  }

  menu_get_sub_menu_show(0, id_block->self_id, HOTLIST_DISPLAY_MENUITEM, &sub_menu);

  if (hotlist_count_selected_items() == 0)
  {
    item = hotlist_find_item(hotlist_root->data.directory_content, menu_itemno);

    if (item)
    {
      hotlist_get_shape(&xmin, &xmax, item);
    }

    if (item && pointerblock.x >= xmin && pointerblock.x <= xmax)
    {
      item->flags |= HOTLIST_G_IS_SELECTED | HOTLIST_G_REDRAW_NOW;
      if (item->type == hl_directory) hotlist_set_flags(item->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
      menu_select = 1;
      hotlist_redraw_now();
    }
  }

  hotlist_set_menu_details(id_block->self_id);

  return 0;
}
/*************************************************/
/* hotlist_save_entries()                        */
/*                                               */
/* This function recurses through the hotlist    */
/* directory structure saving all directories    */
/* and entries as it goes.                       */
/*                                               */
/* Parameters: Pointer to a FILE struct for the  */
/*             file to write to;                 */
/*                                               */
/*             Pointer to a hotlist_item struct  */
/*             representing the first item in    */
/*             the directory to save (which may  */
/*             itself be a directory).           */
/*                                               */
/*             0 - save all of hotlist           */
/*             1 - only save selection portions  */
/*                                               */
/* Assumes:    The FILE pointer must not be NULL */
/*             however the hotlist_item pointer  */
/*             can be (e.g. an empty directory). */
/*************************************************/

static _kernel_oserror * hotlist_save_entries(FILE * fileptr, hotlist_item * list, int type)
{
  int               written;
  _kernel_oserror * e;

  /* Follow the directory list */

  while (list)
  {
    switch (list->type)
    {
      /* Write a link for URLs */

      case hl_url:
      {
        if (type == 0 || (type == 1 && list->flags & HOTLIST_G_IS_SELECTED))
        {
          HotlistWrite(fprintf(fileptr, "<li><a href=\"%s\">%s</a>\n", list->data.url, list->name));
        }
      }
      break;

      /* Write a heading for directories */

      case hl_directory:
      {
        if (type == 0 || !hotlist_no_contents_selected(list->data.directory_content))
        {
          if (type == 0 || (type == 1 && list->flags & HOTLIST_G_IS_SELECTED))
          {
            HotlistWrite(fprintf(fileptr, "<h4>%s</h4>\n", list->name));
            HotlistWrite(fprintf(fileptr, "<ul>\n"));
          }
          /* Recursive call for the directory contents First, */
          /* write the entry header.                          */

          /* Do the contents */

          RetError(hotlist_save_entries(fileptr, list->data.directory_content, type));
          if (type == 0 || (type == 1 && list->flags & HOTLIST_G_IS_SELECTED))
          {
            HotlistWrite(fprintf(fileptr, "</ul>\n"));
          }
        }
      }
      break;
    }

    /* Continue down the list */

    list = list->next;
  }

  return NULL;
}

/*************************************************/
/* hotlist_save_hotlist()                        */
/*                                               */
/* This function saves the hotlist as an HTML    */
/* file.                                         */
/*                                               */
/* Parameters: Pointer to the filename to save   */
/*             to (null terminated);             */
/*                                               */
/*             0 to save all of hotlist, 1 to    */
/*             save only the selected portions.  */
/*************************************************/

_kernel_oserror * hotlist_save_hotlist(char * filename, int type)
{
  FILE            * fileptr;
  _kernel_oserror * e;
  int               written;

  /* Open the file for writng */

  fileptr = fopen(filename, "wb");

  /* Complain if it fails */

  if (fileptr == NULL) return _kernel_last_oserror();

  /* Write the file header */

  HotlistWrite(fprintf(fileptr, "<html>\n"));
  HotlistWrite(fprintf(fileptr, "<head><title>Hotlist</title></head>\n"));
  HotlistWrite(fprintf(fileptr, "<body>\n"));
  HotlistWrite(fprintf(fileptr, "<ul>\n"));

  /* Fill in the body */

  e = hotlist_save_entries(fileptr, hotlist_root->data.directory_content, type);

  if (e)
  {
    fclose(fileptr);

    return e;
  }

  /* Write the footer and close the file */

  HotlistWrite(fprintf(fileptr, "</ul>\n"));
  HotlistWrite(fprintf(fileptr, "</body>\n"));
  HotlistWrite(fprintf(fileptr, "</html>\n"));

  fclose(fileptr);

  /* Set the filetype to HTML (0xfaf) */

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

               18,
               filename,
               FileType_HTML);
}

/*************************************************/
/* hotlist_save()                                */
/*                                               */
/* Veneer onto hotlist_save_hotlist - saves all  */
/* of the hotlist, created to preserve API.      */
/*                                               */
/* Parameters: Pointer to the filename to save   */
/*             under (null terminated).          */
/*************************************************/

_kernel_oserror * hotlist_save(char * filename)
{
  return hotlist_save_hotlist(filename, 0); /* Save entire hotlist */
}

/*************************************************/
/* hotlist_lower_tags()                          */
/*                                               */
/* This function processes the passed string     */
/* turning all characters within a tag to lower  */
/* case. It will detect characters within quotes */
/* and leave their case the same; this will      */
/* preserve the case of URLs.                    */
/*                                               */
/* Obviously, this assumes that the HTML file    */
/* being fed in is not broken; tags and quoted   */
/* text must always be correctly closed, and     */
/* in both cases must not span multiple lines.   */
/*                                               */
/* Parameters: Pointer to the string to process. */
/*************************************************/

static void hotlist_lower_tags(char *string)
{
  int intag = 0, inquotes = 0;

  while (*string)
  {
    if (intag)
    {
      if (inquotes)
      {
        if (*string == '"') inquotes = 0;
      }
      else
      {
        if (*string == '"') inquotes = 1;
        if (*string == '>') intag --;

        *string = tolower(*string);
      }
    }
    else
    {
      if (*string == '<') intag++;
    }

    string++;
  }
}

/*************************************************/
/* hotlist_load_directory()                      */
/*                                               */
/* This function loads the directory contents of */
/* a hotlist HTML file previously saved by       */
/* hotlist_save or a compatible source. For      */
/* example, at the time of creation this can     */
/* correctly load and understand hotlist files   */
/* from at least one other popular browser.      */
/*                                               */
/* Parameters: Pointer to a FILE struct through  */
/*             which data will be read;          */
/*                                               */
/*             Pointer to a hotlist_item; new    */
/*             data structures generated from    */
/*             the file contents are added to    */
/*             the linked list that this struct  */
/*             lies in.                          */
/*************************************************/

static _kernel_oserror * hotlist_load_directory(FILE * fileptr, hotlist_item * target)
{
  _kernel_oserror * e = NULL;

  static char     * next_directory_name = NULL;
  static char     * string_buffer       = NULL;
  static char     * str_ptr;

  char            * url;
  hotlist_item    * new_dir;
  unsigned int      unfollowed_uls = 0;
  long int          file_position;
  int               character, count;

  /* Go through the file in chunks */

  while (!feof(fileptr)) /* In theory the code below means you'll never get this; but just to be safe... */
  {
    file_position = ftell(fileptr);

    /* Scan ahead to find the end of line - marked by any */
    /* control character, in this case; need to include   */
    /* as many consecutive control chars as are present   */
    /* in the file, in the count.                         */

    do
    {
      /* First, get to either a control char or EOF */

      character = fgetc(fileptr);
    }
    while (character > 31 && character != EOF);

    while (character < 32 && character != EOF)
    {
      /* If we're not on EOF, continue until we're no longer */
      /* on a control char or hit EOF.                       */

      character = fgetc(fileptr);
    }

    /* Work out how many bytes we've read */

    count = (int) (ftell(fileptr) - file_position);

    /* If count is zero, we're at the end of the file */

    if (!count) break;

    /* If we're not on EOF and don't have a control char, then */
    /* we overshot by one; so we would want to subtract one    */
    /* from count. However, to ensure string manipulation      */
    /* works OK, we'll need one char to null terminate the     */
    /* string. So in that case, we need to *add* one to count  */
    /* if the above condition isn't true.                      */

    if (!(character > 31 && character != EOF)) count ++;

    /* Right, after all that messing around rewind to the stored */
    /* file position and allocate a buffer for this string.      */

    if (fseek(fileptr, file_position, SEEK_SET))
    {
      e = _kernel_last_oserror();
      goto hotlist_load_directory_exit; /* (See near end of function) */
    }

    /* Note that string_buffer is a static, as this way only one */
    /* of these buffers ever exists, even for recursive calls.   */

    if (string_buffer) free(string_buffer);

    string_buffer = malloc(count);

    /* Complain if the allocation fails */

    if (!string_buffer)
    {
      e = make_no_memory_error(2);

      goto hotlist_load_directory_exit; /* (See near end of function) */
    }

    /* Read the data and force a terminator at the end of the buffer, */
    /* just to be safe.                                               */

    if (fread(string_buffer, sizeof(char), count - 1, fileptr) != count - 1)
    {
      e = _kernel_last_oserror();
      goto hotlist_load_directory_exit; /* (See near end of function) */
    }

    string_buffer[count - 1] = 0;

    /* Convert tags to lower case */

    hotlist_lower_tags(string_buffer);

    /* Treat any opening '<h...>' tag (header) as the title to a directory */

    str_ptr = strstr(string_buffer, "<h");

    if (
         str_ptr           &&
         str_ptr[2] >= '1' &&
         str_ptr[2] <= '6' &&
         str_ptr[3] == '>'
       )
    {
      /* Read the directory name (up to the closing '</h...>') */

      str_ptr = strtok(str_ptr + 4, "<");

      /* Allocate space for it */

      if (next_directory_name) free(next_directory_name);

      next_directory_name = malloc(strlen(str_ptr) + 1);

      /* Complain if the allocation fails */

      if (!next_directory_name)
      {
        e = make_no_memory_error(3);

        goto hotlist_load_directory_exit; /* (See near end of function) */
      }

      /* Otherwise, copy the name in */

      strcpy(next_directory_name, str_ptr);
    }

    /* Treat any '<a href=...>' attribute contents (link) as a URL for the hotlist */

    else if ((str_ptr = strstr(string_buffer, "<a href=\"")) != NULL) /* Using '!= NULL' stops a compiler warning... */
    {
      /* First extract the URL */

      str_ptr += 9; /* Derived from strlen("<a href=\"") */

      str_ptr = strtok(str_ptr, "\"");

      /* Because we're about to use strtok() to extract the title, it'll */
      /* put a convenient terminator into the string_buffer block at the */
      /* end of the URL. So all we need to do is record the current      */
      /* pointer in the block, str_ptr, for use when we finally add the  */
      /* item to the hotlist.                                            */

      url = str_ptr;

      /* Extract the title - between the closing '>' of the '<a href=...' */
      /* and the opening '<' of the '</a>'.                               */

      str_ptr = strtok(NULL, "><");

      /* Add this item */

      e = hotlist_new_url(target, HOTLIST_POSITION_END, str_ptr, url);
      if (e) goto hotlist_load_directory_exit; /* (See near end of function) */
    }

    /* Treat any '<ul>' tags as the start of a new directory. The name comes */
    /* from a preceeding '<h...>' tag (see above).                           */

    else if ((str_ptr = strstr(string_buffer, "<ul>")) != NULL) /* New directory */
    {
      if (!next_directory_name)
      {
        /* If we don't have a directory name for this one, apparently, flag it */
        /* by incrementing the unmatched '<ul>' counter.                       */

        unfollowed_uls ++;
      }
      else
      {
        /* Otherwise, add the directory */

        e = hotlist_new_directory(target, next_directory_name, HOTLIST_POSITION_END, &new_dir);
        if (e) goto hotlist_load_directory_exit;

        free(next_directory_name);
        next_directory_name = NULL;

        /* If the directory was added, recursively load the contents */

        if (new_dir)
        {
          /* Note this will invalidate string_buffer. The contents must */
          /* not be used after the call!                                */

          e = hotlist_load_directory(fileptr, new_dir);
          if (e) goto hotlist_load_directory_exit; /* (See near end of function) */
        }
        else
        {
          /* If the directory was not added, flag it */

          unfollowed_uls ++;
        }
      }
    }

    /* Treat any '</ul>' tags as the end of a directory. */

    else if ((str_ptr = strstr(string_buffer, "</ul>")) != NULL) /* Close directory */
    {
      /* If we have unfollowed '<ul>' tags, decrement the counter; */
      /* otherwise, exit quietly.                                  */

      if (unfollowed_uls) unfollowed_uls--;
      else goto hotlist_load_directory_exit; /* (See near end of function) */
    }
  }

  /* This section is not necessarily an error exit condition, so */
  /* the code falls through to it in normal running. If 'e' is   */
  /* NULL there's still no error returned in the end. Note       */
  /* though how the various buffers are freed, and thus          */
  /* invalidated, at this point; so during recursive routines,   */
  /* they must not be accessed after the recursive call has      */
  /* been made (unless, of course, they are reallocated).        */

hotlist_load_directory_exit:

  /* Free up the temporary buffers */

  if (string_buffer)
  {
    free(string_buffer);
    string_buffer = NULL;
  }

  if (next_directory_name)
  {
    free(next_directory_name);
    next_directory_name = NULL;
  }

  // We could at this point give a warning if unfollowed_uls is non-zero.

  return e;
}

/*************************************************/
/* hotlist_load()                                */
/*                                               */
/* This function loads an HTML file previously   */
/* saved by hotlist_save as the new hotlist.     */
/*                                               */
/* Parameters: Pointer to the filename to load   */
/*             (null terminated).                */
/*************************************************/

_kernel_oserror * hotlist_load(char * filename)
{
  _kernel_oserror * e;
  FILE            * fileptr;

  /* First delete all the existing hotlist items */

  while (hotlist_root->data.directory_content)
  {
    hotlist_delete_item(hotlist_root->data.directory_content);
  }

  /* Open the file */

  fileptr = fopen(filename, "r");

  if (fileptr == NULL)
  {
    erb.errnum = Utils_Error_Custom_Normal;
    StrNCpy0(erb.errmess,
             lookup_token("HlCantLoad:Hotlist file could not be loaded",
                          0,
                          0));
    return &erb;
  }

  /* Load it (any errors are returned right at the end) */

  e = hotlist_load_directory(fileptr, hotlist_root);

  fclose(fileptr);

  /* Clear all of the various flags for redraw, */
  /* selection etc. now that we have a new      */
  /* hotlist.                                   */

  hotlist_clear_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);

  #ifdef TRACE

    /* Show the tree now the file is loaded */

    if (tl & (1u<<25)) hotlist_display_tree(hotlist_root, 0);

  #endif

  /* Finished; redraw issues are left to the caller */

  if (!e) return hotlist_modified(HL_MODIFIED_LOAD);

  return e;
}

/*************************************************/
/* hotlist_initialise()                          */
/*                                               */
/* This function initialises the hotlist library */
/* routines, and must be called before any other */
/* hotlist functions.                            */
/*************************************************/

_kernel_oserror * hotlist_initialise(void)
{
  _kernel_oserror * e;
  ObjectId          toolbar;
  ObjectId          menu_id;
  BBox              bbox;
  unsigned int      item_height, item_dir_width, item_url_width;

  /* Create root directory item */

  RetError(hotlist_new_directory(NULL, "Root", 0, &hotlist_root));

  /* Create the hotlist window */

  RetError(toolbox_create_object(0, "HotlistWind", &hotlist_windowid));

  /* Is there a toolbar? */

  RetError(window_get_tool_bars(InternalTopLeft,
                                hotlist_windowid,
                                NULL,
                                &toolbar,
                                NULL,
                                NULL));

  /* If so, read the size */

  if (toolbar != 0)
  {
    RetError(window_get_extent(0, toolbar, &bbox));

    hotlist_bbar_size = bbox.ymax - bbox.ymin;
  }
  else hotlist_bbar_size = 0;

  /* Read sprite sizes to see if the sprites actually exist */

  RetError(hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width));

  /* Register event handlers for redraw, clicks and drags in the */
  /* main hotlist window.                                        */

  RetError(event_register_wimp_handler(hotlist_windowid,
                                       Wimp_ERedrawWindow,
                                       hotlist_redraw_handler,
                                       NULL));

  RetError(event_register_wimp_handler(hotlist_windowid,
                                       Wimp_EMouseClick,
                                       hotlist_mouse_click_handler,
                                       NULL));

  RetError(event_register_wimp_handler(-1,
                                       Wimp_EUserDrag,
                                       hotlist_drag_completed_handler,
                                       NULL));

  /* Menu handlers */

  RetError(window_get_menu(0, hotlist_windowid, &menu_id));

  RetError(event_register_toolbox_handler(menu_id,
                                          HotlistMenuOpened,
                                          hotlist_menuopen_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(menu_id,
                                          HotlistMenuClosed,
                                          hotlist_menuclose_handler,
                                          NULL));

  /* Main menu items */

  RetError(event_register_toolbox_handler(-1,
                                          HotlistSelectAll,
                                          hotlist_menu_selectall_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistClearSelect,
                                          hotlist_menu_clearselect_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistOpenAll,
                                          hotlist_menu_openall_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistCloseAll,
                                          hotlist_menu_closeall_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistDelete,
                                          hotlist_menu_delete_handler,
                                          NULL));

  /* Submenu warning events */

  RetError(event_register_toolbox_handler(-1,
                                          HotlistShowEditURL,
                                          hotlist_show_editurl_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistShowRenameDirectory,
                                          hotlist_show_rendirectory_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistShowNewURL,
                                          hotlist_show_newurl_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistShowNewDirectory,
                                          hotlist_show_newdirectory_handler,
                                          NULL));

  /* Hotlist related dialogue events */

  RetError(event_register_toolbox_handler(-1,
                                          HotlistNewEditURLOk,
                                          hotlist_newedit_url_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistNewRenameDirectoryOk,
                                          hotlist_newren_directory_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistNewEditURLCancel,
                                          hotlist_reset_url_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistNewRenameDirectoryCancel,
                                          hotlist_reset_directory_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistShowDesc,
                                          hotlist_show_descriptions_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistShowURLs,
                                          hotlist_show_urls_handler,
                                          NULL));

  RetError(event_register_message_handler(Wimp_MDataSaveAck,
                                          hotlist_data_save_ack_handler,
                                          NULL));

  RetError(event_register_message_handler(Wimp_MRAMFetch,
                                          hotlist_ram_fetch_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          HotlistEscape,
                                          hotlist_drag_stop_handler,
                                          NULL));

  return NULL;
}

/*************************************************/
/* hotlist_open()                                */
/*                                               */
/* Opens the hotlist window. If already open,    */
/* all this does is bring it to the front - it   */
/* doesn't move it. The display type is changed, */
/* if required, in any case.                     */
/*                                               */
/* Parameters: Show type (as for a call to       */
/*             Toolbox_ShowObject);              */
/*                                               */
/*             Show block (as for a call to      */
/*             Toolbox_ShowObject);              */
/*                                               */
/*             0 to open showing descriptions,   */
/*             or 1 to open showing URLs.        */
/*************************************************/

_kernel_oserror * hotlist_open(int show_type, void * type, int show_urls)
{
  int               open, old_show_urls;
  _kernel_oserror * e;

  old_show_urls = hl_show_urls;

  /* Sets show descriptions / show URLs */

  hl_show_urls = show_urls;

  /* Set the size of the window etc. */

  open = hotlist_preopen();

  /* Show the hotlist. We have to do this twice; if we try to centre */
  /* it just the once, then that call seems to read the visible area */
  /* as it was *before* hotlist_preopen changed the extent.          */
  /*                                                                 */
  /* This Is Bad. In any case, for opening centred, we want to open  */
  /* full size, if not already open.                                 */

  if (open)
  {
    /* Open window once to let the wimp/toolbox see what shape it really is,  */
    /* but don't fiddle with the visible area as the window was already open. */

    e = toolbox_show_object(0,
                            hotlist_windowid,
                            Toolbox_ShowObject_Default,
                            0,
                            0,
                            -1);
  }
  else
  {
    BBox                  extent;
    WindowShowObjectBlock show;

    RetError(window_get_extent(0, hotlist_windowid, &extent));

    show.visible_area = extent;
    show.behind       = -3;

    e = toolbox_show_object(0,
                            hotlist_windowid,
                            Toolbox_ShowObject_FullSpec,
                            &show,
                            0,
                            -1);

    /* Open it once again in the place the window is really wanted */

    e = toolbox_show_object(0,
                            hotlist_windowid,
                            show_type,
                            type,
                            0,
                            -1);
  }

  /* Redraw the window if the display type has changed */

  if (!e && open && hl_show_urls != old_show_urls)
  {
    hotlist_redraw_items(0, hotlist_count_displayed_items(hotlist_root->data.directory_content));
  }

  return e;
}

/*************************************************/
/* hotlist_close()                               */
/*                                               */
/* Closes the hotlist window.                    */
/*************************************************/

_kernel_oserror * hotlist_close(void)
{
  return toolbox_hide_object(0, hotlist_windowid);
}

/*************************************************/
/* hotlist_menu_selectall_handler()              */
/*                                               */
/* This function handles the 'select all' menu   */
/* item.                                         */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int hotlist_menu_selectall_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  hotlist_item * item;
  ObjectId       sub_menu;

  if (!hotlist_root->data.directory_content) return 0;

  item = hotlist_find_item(hotlist_root->data.directory_content, menu_itemno);

  if (item && item->parent)
  {
    item = item->parent->data.directory_content;

    while (item)
    {
      item->flags |= HOTLIST_G_IS_SELECTED | HOTLIST_G_REDRAW_NOW;
      if (item->type == hl_directory) hotlist_set_flags(item->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);

      item = item->next;
    }

    hotlist_redraw_now();

    menu_get_sub_menu_show(0, id_block->self_id, HOTLIST_URL_MENUITEM, &sub_menu);
    menu_set_sub_menu_show(0, id_block->self_id, HOTLIST_URL_MENUITEM, 0);

    if (sub_menu) toolbox_delete_object(0, sub_menu);

    hotlist_set_menu_details(id_block->self_id);
  }

  menu_select = 0;

  return 1;
}

/*************************************************/
/* hotlist_menu_clearselection_handler()         */
/*                                               */
/* This function handles the clear selection     */
/* menu item                                     */
/*************************************************/

static int hotlist_menu_clearselect_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_clear_selection();
  menu_set_entry_text(0, id_block->self_id, 0x05, "URL ''");
  menu_set_fade(0, id_block->self_id, 0x05, 1); /* Fade URL'' selection */
  menu_set_fade(0, id_block->self_id, 0x01, 1); /* Fade clear selection */
  return 0;
}

/*************************************************/
/* hotlist_menu_openall_handler()                */
/*                                               */
/* This function handles the open all menu item  */
/*************************************************/

static int hotlist_menu_openall_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  if (hotlist_root->data.directory_content)
  {
    menu_itemno = 0;
    hotlist_set_flags(hotlist_root->data.directory_content, hl_directory, HOTLIST_D_IS_OPEN);
    hotlist_clear_flags(hotlist_root->data.directory_content, hl_directory, HOTLIST_G_REDRAW_NOW);
    hotlist_redraw_items(0, hotlist_count_displayed_items(hotlist_root->data.directory_content));
    hotlist_preopen();
  }
  return 0;
}

/*************************************************/
/* hotlist_menu_closeall_handler()               */
/*                                               */
/* This function handles the close all menu      */
/* item                                          */
/*************************************************/

static int hotlist_menu_closeall_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  unsigned int noitems;
  if (hotlist_root->data.directory_content)
  {
    menu_itemno = 0;
    noitems = hotlist_count_displayed_items(hotlist_root->data.directory_content);

    hotlist_clear_flags(hotlist_root->data.directory_content,
                        hl_directory,
                        HOTLIST_D_IS_OPEN | HOTLIST_G_REDRAW_NOW);
    hotlist_clear_selection();
    hotlist_redraw_items(0, noitems);
    menu_set_entry_text(0, id_block->self_id, HOTLIST_URL_MENUITEM, "URL ''");
    menu_set_fade(0, id_block->self_id, HOTLIST_URL_MENUITEM, 1); /* Fade URL'' selection */
    menu_set_fade(0, id_block->self_id, HOTLIST_CLEARSELECTION_MENUITEM, 1); /* Fade clear selection */
    hotlist_preopen();
  }
  return 0;
}

/*************************************************/
/* hotlist_menu_delete_handler()                 */
/*                                               */
/* This function handles the delete menu item    */
/*************************************************/

static int hotlist_menu_delete_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_item *item;
  unsigned int noitems;

  noitems = hotlist_count_displayed_items(hotlist_root->data.directory_content);

  if (!hotlist_count_selected_items()) return 0;

  while((item = hotlist_find_selected_item())!=NULL)
  {
    hotlist_delete_item(item);
  }
  hotlist_redraw_items(0, noitems);
  hotlist_preopen();
  toolbox_hide_object(0, id_block->ancestor_id);

  hotlist_modified(HL_MODIFIED_DELETE);

  return 0;
}

/*************************************************/
/* hotlist_show_editurl_handler()                */
/*                                               */
/* This function fills in the description and    */
/* url fields of the edit url dialogue box       */
/*************************************************/

static int hotlist_show_editurl_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_item *item;
  ObjectId dboxid;

#ifdef TRACE
  Printf("hotlist_show_editurl_handler\n");
#endif

  item = hotlist_find_selected_item();
  if (!item) return 0;
  menu_get_sub_menu_show(0, id_block->self_id, id_block->self_component, &dboxid);
  alter_new = HOTLIST_MENUSECTION_ALTER;
  window_set_title(0, dboxid, "Edit URL");
  writablefield_set_value(0, dboxid, 0x01, item->name);
  writablefield_set_value(0, dboxid, 0x05, item->data.url);
  actionbutton_set_text(0, dboxid, 0x02, "Alter");
  return 0;
}

/*************************************************/
/* hotlist_show_rendirectory_handler()           */
/*                                               */
/* This function fills in the name field of the  */
/* rename directory dialogue box                 */
/*************************************************/

static int hotlist_show_rendirectory_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_item *item;
  ObjectId dboxid;

#ifdef TRACE
  Printf("hotlist_show_rendirectory_handler\n");
#endif

  item = hotlist_find_selected_item();
  if (!item) return 0;

  menu_get_sub_menu_show(0, id_block->self_id, id_block->self_component, &dboxid);

  alter_new = HOTLIST_MENUSECTION_ALTER;
  window_set_title(0, dboxid, "Rename Directory");
  writablefield_set_value(0, dboxid, 0x01, item->name);
  actionbutton_set_text(0, dboxid, 0x02, "Rename");
  return 0;
}

/*************************************************/
/* hotlist_show_newurl_handler()                 */
/*                                               */
/* This function emptys the description and url  */
/* fields of the new url dialogue box            */
/*************************************************/

static int hotlist_show_newurl_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  ObjectId dboxid;
#ifdef TRACE
  Printf("hotlist_show_newurl_handler\n");
#endif

  menu_get_sub_menu_show(0, id_block->self_id, id_block->self_component, &dboxid);

  alter_new = HOTLIST_MENUSECTION_NEW;
  writablefield_set_value(0, dboxid, 0x01, "");
  writablefield_set_value(0, dboxid, 0x05, "");
  window_set_title(0, dboxid, "Create new URL");
  actionbutton_set_text(0, dboxid, 0x02, "Create");
  return 0;
}

/*************************************************/
/* hotlist_show_newdirectory_handler()           */
/*                                               */
/* This function emptys the name field of the    */
/* new directory dialogue box                    */
/*************************************************/

static int hotlist_show_newdirectory_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  ObjectId dboxid;
#ifdef TRACE
  Printf("hotlist_show_newdirectory_handler\n");
#endif

  menu_get_sub_menu_show(0, id_block->self_id, id_block->self_component, &dboxid);

  alter_new = HOTLIST_MENUSECTION_NEW;
  writablefield_set_value(0, dboxid, 0x01, "");
  window_set_title(0, dboxid, "Create new Directory");
  actionbutton_set_text(0, dboxid, 0x02, "Create");
  return 0;
}

/*************************************************/
/* hotlist_newedit_url_handler()                 */
/*                                               */
/* This function either alters the selected urls */
/* name and url fields or creates a new url in   */
/* the directory the pointer was in when menu    */
/* was clicked in the hotlist window.            */
/*************************************************/

static int hotlist_newedit_url_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_item *item, *tempitem;
  char *tempdesc, *tempurl;
  int size;

  item = hotlist_find_selected_item();
  writablefield_get_value(0,
                          id_block->self_id,
                          HOTLIST_NEWURL_NAME,
                          NULL,
                          0,
                          &size);
  tempdesc = malloc(size);
  if (!tempdesc) return 0; /* ERROR can't rename */
  writablefield_get_value(0,
                          id_block->self_id,
                          HOTLIST_NEWURL_NAME,
                          tempdesc,
                          size,
                          &size);

  writablefield_get_value(0,
                          id_block->self_id,
                          HOTLIST_NEWURL_URL,
                          NULL,
                          0,
                          &size);
  tempurl = malloc(size);
  if (!tempurl)
  {
    free(tempdesc);
    return 0; /* ERROR can't rename */
  }
  writablefield_get_value(0,
                          id_block->self_id,
                          HOTLIST_NEWURL_URL,
                          tempurl,
                          size,
                          &size);

  switch(alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    tempitem = hotlist_find_item(hotlist_root->data.directory_content, menu_itemno);
    if (!tempitem) tempitem = hotlist_root;
    else
    {
      tempitem = tempitem->parent;
    }
    show_error(hotlist_new_url(tempitem,
                               HOTLIST_POSITION_END,
                               tempdesc,
                               tempurl));
    hotlist_preopen();
    hotlist_redraw_items(menu_itemno,
                         hotlist_count_displayed_items(hotlist_root->data.directory_content));
    hotlist_clear_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);
    hotlist_modified(HL_MODIFIED_ADD);
    free(tempdesc);
    free(tempurl);
    break;

    case HOTLIST_MENUSECTION_ALTER:
    free(item->name);
    free(item->data.url);
    item->name = tempdesc;
    item->data.url = tempurl;
    item->flags |= HOTLIST_G_REDRAW_NOW;
    hotlist_preopen();
    hotlist_redraw_now();
    hotlist_modified(HL_MODIFIED_ALTER);
    break;

  }
  return 0;
}

/*************************************************/
/* hotlist_newren_directory_handler()            */
/*                                               */
/* This function either alters the selected      */
/* directorys name or creates a new directory in */
/* the directory the pointer was in when menu    */
/* was clicked in the hotlist window.            */
/*************************************************/

static int hotlist_newren_directory_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  int size;
  hotlist_item *item, *tempitem;
  char *tempname;

  item = hotlist_find_selected_item();
  writablefield_get_value(0,
                          id_block->self_id,
                          HOTLIST_NEWDIRECTORY_NAME,
                          NULL,
                          0,
                          &size);
  tempname = malloc(size);
  if (!tempname) return 0; /* ERROR can't rename */
  writablefield_get_value(0,
                          id_block->self_id,
                          HOTLIST_NEWDIRECTORY_NAME,
                          tempname,
                          size,
                          &size);

  switch(alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    tempitem = hotlist_find_item(hotlist_root->data.directory_content, menu_itemno);
    if (!tempitem) tempitem = hotlist_root;
    else
    tempitem = tempitem->parent;
    hotlist_new_directory(tempitem,
                          tempname,
                          HOTLIST_POSITION_END,
                          &tempitem);
    hotlist_preopen();
    hotlist_redraw_items(menu_itemno,
                         hotlist_count_displayed_items(hotlist_root->data.directory_content));
    hotlist_clear_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);
    hotlist_modified(HL_MODIFIED_ADD);
    free(tempname);
    break;

    case HOTLIST_MENUSECTION_ALTER:
    free(item->name);
    item->name = tempname;
    item->flags |= HOTLIST_G_REDRAW_NOW;
    hotlist_preopen();
    hotlist_redraw_now();
    hotlist_modified(HL_MODIFIED_ALTER);
    //toolbox_hide_object(0, id_block->ancestor_id);
    break;

  }
  return 0;
}

/*************************************************/
/* hotlist_newren_directory_handler()            */
/*                                               */
/* This function is called when Cancel is        */
/* clicked on in the new or edit url�dialogue    */
/* box.  It resets the contents to their         */
/* previous state.                               */
/*************************************************/

static int hotlist_reset_url_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_item *item;
  switch(alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    writablefield_set_value(0, id_block->self_id, 0x01, "");
    writablefield_set_value(0, id_block->self_id, 0x05, "");
    break;

    case HOTLIST_MENUSECTION_ALTER:
    item = hotlist_find_selected_item();
    writablefield_set_value(0, id_block->self_id, 0x01, item->name);
    writablefield_set_value(0, id_block->self_id, 0x05, item->data.url);
    break;

  }
  return 0;
}

/*************************************************/
/* hotlist_reset_directory_handler()             */
/*                                               */
/* This function is called when Cancel is        */
/* clicked on in the new or rename directory     */
/* dialogue box.  It resets the contents to      */
/* their previous state.                         */
/*************************************************/

static int hotlist_reset_directory_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hotlist_item *item;
  switch(alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    writablefield_set_value(0, id_block->self_id, 0x01, "");
    break;

    case HOTLIST_MENUSECTION_ALTER:
    item = hotlist_find_selected_item();
    writablefield_set_value(0, id_block->self_id, 0x01, item->name);
    break;

  }
  return 0;
}

/*************************************************/
/* hotlist_drag_renderer()                       */
/*                                               */
/* renders a single hotlist item at 0,0 for use  */
/* with draganobject                             */
/*                                               */
/* Parameters: pointer to the item to render     */
/*************************************************/

static void hotlist_drag_renderer(hotlist_item *item, unsigned int item_height, unsigned int item_dir_width, unsigned int item_url_width)
{
  /* DONT PUT Printf's in here, doesn't work (on pipefs anyway) */

  WimpIconBlock hotlist_iconblock;
  int           temp_width, text_width;

  hotlist_iconblock.flags = HOTLIST_SPRITE_ICON_FLAGS;

  hotlist_iconblock.data.is.sprite_area = (void*)sprite_block;

  switch(item->type)                                                /* Set appropriate sprite and width of sprite */
  {
    case hl_url:
    hotlist_iconblock.data.is.sprite = URL_SPRITE;
    hotlist_iconblock.data.is.sprite_name_length = strlen(URL_SPRITE);
    temp_width = item_url_width;
    break;

    case hl_directory:
    temp_width = item_dir_width;
    hotlist_iconblock.data.is.sprite = CLOSED_DIRECTORY_SPRITE;
    hotlist_iconblock.data.is.sprite_name_length = strlen(CLOSED_DIRECTORY_SPRITE);
    break;

    default:
    temp_width = 0; /* Prevent warnings */
    break;
  }

  hotlist_iconblock.bbox.xmin = 2;
  hotlist_iconblock.bbox.xmax = 2 + temp_width;
  hotlist_iconblock.bbox.ymax = 2 + item_height;
  hotlist_iconblock.bbox.ymin = 2;

  if (wimp_plot_icon(&hotlist_iconblock))                               /* Plot sprite icon */
  {
    /* Error has happened, don't know what to do about it */
  }

  if (item->type == hl_url && hl_show_urls) utils_text_width(item->data.url, &text_width, 0);
  else                                      utils_text_width(item->name,     &text_width, 0);

  hotlist_iconblock.flags               = HOTLIST_TEXT_ICON_FLAGS_DRAG;
  hotlist_iconblock.bbox.xmin           = temp_width + 4;
  hotlist_iconblock.bbox.xmax           = temp_width + text_width + 16;
  hotlist_iconblock.bbox.ymax           = item_height;
  hotlist_iconblock.bbox.ymin           = 4;
  if (item->type == hl_url && hl_show_urls)
  {
    hotlist_iconblock.data.it.buffer      = item->data.url;
    hotlist_iconblock.data.it.buffer_size = strlen(item->data.url);
    hotlist_iconblock.data.it.validation  = NULL;
  }
  else
  {
    hotlist_iconblock.data.it.buffer      = item->name;
    hotlist_iconblock.data.it.buffer_size = strlen(item->name);
    hotlist_iconblock.data.it.validation  = NULL;
  }

  if (wimp_plot_icon(&hotlist_iconblock))
  {
    /* Error has happened, don't know what to do about it */
  }
}

/*************************************************/
/* hotlist_start_drag()                          */
/*                                               */
/* This function is called to start a drag       */
/* operation from the hotlist window.  A drag    */
/* box will be initiated bounding all selected   */
/* items.                                        */
/*************************************************/

static _kernel_oserror *_hotlist_start_drag(void)
{
  _kernel_swi_regs regs;
  _kernel_oserror *err;
  unsigned int item_height, item_dir_width, item_url_width;
  hotlist_item *item;
  WimpDragBox box;
  WimpGetPointerInfoBlock pointerblock;
  unsigned int xmin, xmax, xlow, xhigh;
  int top, bottom, item_no, xorigin, yorigin, screenwidth, screenheight, tempint;
  WimpGetWindowStateBlock state;
  int redraw_params[4];
  int width, height;

  wimp_get_pointer_info(&pointerblock);                          /* Read pointer position */

  hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  item = hotlist_find_selected_item();
  item_no = hotlist_find_no_from_item(item);
  top = -item_no * item_height;
  bottom = -(item_no + 1) * item_height;
  if (item_no < 0) return NULL;                                  /* Selected item not found, this should never happen */

  hotlist_get_shape(&xmin, &xmax, item);
  item = item->next;
  if (hotlist_count_selected_items() > 1)
  {
    while(item)
    {
      item_no++;
      hotlist_get_shape(&xlow, &xhigh, item);
      if (item->flags & HOTLIST_G_IS_SELECTED)
      {
        if (xlow < xmin) xmin = xlow;                              /* Should never happen, left hand side should not be ragged */
        if (xhigh > xmax) xmax = xhigh;
        bottom = -(item_no + 1) * item_height;
      }
      item = item->next;
    }
  }

  show_error(window_get_wimp_handle(0, hotlist_windowid, &state.window_handle));
  wimp_get_window_state(&state);

  xorigin = state.xscroll - state.visible_area.xmin;
  yorigin = state.yscroll - state.visible_area.ymax;

  _swix(OS_ReadModeVariable, _INR(0,1) | _OUT(2), -1, 11, &screenwidth);
  _swix(OS_ReadModeVariable, _INR(0,1) | _OUT(2), -1, 12, &screenheight);
  _swix(OS_ReadModeVariable, _INR(0,1) | _OUT(2), -1, 4, &tempint);
  screenwidth <<= tempint;
  _swix(OS_ReadModeVariable, _INR(0,1) | _OUT(2), -1, 5, &tempint);
  screenheight <<= tempint;

  if (_kernel_osbyte(161, 28, 0) & (1<<(8+1))) /* solid drag */
  {
    item = hotlist_find_selected_item();

    if (
         (hotlist_count_selected_items() == 1 && item->type == hl_url) ||
         (
           hotlist_count_selected_items() == 1 &&
           item->type == hl_directory &&
           (
             !(item->flags & HOTLIST_D_IS_OPEN) ||
             hotlist_no_contents_selected(item->data.directory_content)   ||
             item->data.directory_content == NULL
           )
         )
       )
    {
      box.dragging_box.xmin = xmin-xorigin;                          /* box shape */
      box.dragging_box.ymin = bottom-yorigin;
      box.dragging_box.xmax = xmax-xorigin;
      box.dragging_box.ymax = top-yorigin;

      redraw_params[0] = (int)hotlist_find_selected_item();
      redraw_params[1] = (int)item_height;
      redraw_params[2] = (int)item_dir_width;
      redraw_params[3] = (int)item_url_width;

      regs.r[0] = (2<<0) | (2<<2) | (1<<6) | (1<<7) | (1<<16);
      regs.r[1] = (int)hotlist_drag_renderer;
      regs.r[2] = (int)redraw_params;
      regs.r[3] = (int)&(box.dragging_box);

      if (!_kernel_swi(DragAnObject_Start, &regs, &regs))
      {
        hotlist_dragging = HOTLIST_SOLID_DRAG_OBJECT;                /* Set global variable saying we are currently dragging */
                                                                     /* so we know whether to process a user_drag_box event  */
        return NULL;
      }
    }
    else
    {
      if (!read_sprite_size(SELECTION_SPRITE, &width, &height))
      {
        box.dragging_box.xmin = pointerblock.x - (width/2 + 10);                          /* box shape */
        box.dragging_box.ymin = pointerblock.y - (height/2 + 10);
        box.dragging_box.xmax = pointerblock.x + (width/2 + 10);
        box.dragging_box.ymax = pointerblock.y + (height/2 + 10);

        regs.r[0] = (1<<0) | (1<<2) | (1<<6) | (1<<7);
        regs.r[1] = (int)sprite_block; /* browser sprite area */
        regs.r[2] = (int)"package";
        regs.r[3] = (int)&(box.dragging_box);

        if (!_kernel_swi(DragASprite_Start, &regs, &regs))
        {
          hotlist_dragging = HOTLIST_SOLID_DRAG_SPRITE;
          return NULL;
        }
      }
    }
  }
  box.drag_type         = Wimp_DragBox_DragFixedDash;
  box.dragging_box.xmin = xmin-xorigin;                          /* box shape */
  box.dragging_box.xmax = xmax-xorigin;
  box.dragging_box.ymin = bottom-yorigin;
  box.dragging_box.ymax = top-yorigin;
  box.parent_box.xmin   = -(pointerblock.x - box.dragging_box.xmin);  /* Bounding box for dragged box */
  box.parent_box.xmax   = screenwidth+(box.dragging_box.xmax - pointerblock.x);
  box.parent_box.ymin   = -(pointerblock.y - box.dragging_box.ymin);
  box.parent_box.ymax   = screenheight+(box.dragging_box.ymax - pointerblock.y);
  err = wimp_drag_box(&box);                                           /* start drag box */
  if (err) return err;
  hotlist_dragging = HOTLIST_BOX_DRAG;                           /* Set global variable saying we are currently dragging */
                                                                 /* so we know whether to process a user_drag_box event */
  return NULL;
}

void hotlist_start_drag(void)
{
  _kernel_oserror *err;
  err = _hotlist_start_drag();
  if (err)
  {
    show_error_ret(err);
    return;
  }
  /* Register NULL handler */
  show_error_ret(hotlist_autoscroll(0)); /* Reset autoscroll */
  register_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);
}

/*************************************************/
/* hotlist_is_inside()                           */
/*                                               */
/* Checks if one hotlist_item is held inside     */
/* the directory structure of another            */
/* hotlist_item                                  */
/*                                               */
/* Parameters: pointer to possibly inside item   */
/*             pointer to outside item           */
/*                                               */
/* Returns:    1 if item is inside, 0 otherwise  */
/*************************************************/

/* This routine is no longer used but the code will be left incase it is needed */

//  static int hotlist_is_inside(hotlist_item *inside, hotlist_item *outside)
//  {
//    while(inside)
//    {
//      if (inside == outside) return 1;
//      inside = inside->parent;
//    }
//    return 0;
//  }

/*************************************************/
/* hotlist_drag_stop_handler()                   */
/*                                               */
/* Terminates any hotlist drag in progress       */
/*************************************************/

static int hotlist_drag_stop_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  if (hotlist_current_highlighted)
  {
    hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;
    hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
    hotlist_current_highlighted = NULL;
  }

  switch(hotlist_dragging)
  {
    default:
    return 0;
    break;

    case HOTLIST_SOLID_DRAG_OBJECT:
    /* Stop drag an object */
    _swix(DragAnObject_Stop, 0);
    deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);
    hotlist_dragging = 0;
    break;

    case HOTLIST_SOLID_DRAG_SPRITE:
    /* Stop drag a sprite */
    _swix(DragASprite_Stop, 0);
    deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);
    hotlist_dragging = 0;
    break;

    case HOTLIST_BOX_DRAG_SELECTION:
    /* Stop drag box */
    wimp_drag_box(NULL);
    deregister_null_claimant(Wimp_ENull, hotlist_null_drag_select_handler, NULL);
    hotlist_clear_flags(hotlist_root->data.directory_content, hl_ALL, HOTLIST_G_DRAG_SELECTED);
    hotlist_redraw_now();
    hotlist_dragging = 0;
    break;
  }
  return 1;
}

/*************************************************/
/* hotlist_drag_completed_handler()              */
/*                                               */
/* This function is called when a user_drag      */
/* completes.  If the drag is one started by the */
/* hotlist section it is processed.  Dropping    */
/* the drag in the hotlist window will move or   */
/* copy the items being dragged.                 */
/* Dropping the drag in any other window is      */
/* currently ignored                             */
/*************************************************/

static int hotlist_drag_completed_handler(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle)
{
  unsigned int item_height, item_dir_width, item_url_width;
  ObjectId window_handle;
  ComponentId comp;
  WimpGetPointerInfoBlock pointerblock;
  WimpGetWindowStateBlock state;
  int winx, winy, shift;
  unsigned int top, bottom, bottom2, tempint, position, xmin, xmax;
  hotlist_item *targetitem, *sourceitem;

  _swix(OS_Byte, _INR(0,1) | _OUT(1), 121, 128, &shift); /* Check if SHIFT is pressed */

  if (!hotlist_dragging) return 0;

  show_error_ret(hotlist_autoscroll(0)); /* Reset autoscrolling (To restore pointer if necessary) */

  switch(hotlist_dragging)
  {
    case HOTLIST_SOLID_DRAG_OBJECT:
    _swix(DragAnObject_Stop, 0);
    break;

    case HOTLIST_SOLID_DRAG_SPRITE:
    _swix(DragASprite_Stop, 0);
    break;

    case HOTLIST_BOX_DRAG_SELECTION:
    deregister_null_claimant(Wimp_ENull, hotlist_null_drag_select_handler, NULL);
    hotlist_convert_drag_selection(hotlist_root->data.directory_content);

    return 0;
    break;

    default:
    break;
  }

  if (hotlist_current_highlighted)
  {
    hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;
    hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
    hotlist_current_highlighted = NULL;
  }

  deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);

  hotlist_dragging = 0;
  wimp_get_pointer_info(&pointerblock);

  show_error(window_wimp_to_toolbox(0,
                                    pointerblock.window_handle,
                                    pointerblock.icon_handle,
                                    &window_handle,
                                    &comp));

  if (window_handle == hotlist_windowid)
  {
    /* Drag was dropped in hotlist window */
    if (pointerblock.icon_handle != -1) return 0; /* Only understand drops on workspace */

    hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);
    state.window_handle = pointerblock.window_handle;
    wimp_get_window_state(&state);
    winx = pointerblock.x + (state.xscroll - state.visible_area.xmin);
    winy = pointerblock.y + (state.yscroll - state.visible_area.ymax);
    targetitem = hotlist_find_item(hotlist_root->data.directory_content, -winy/item_height);
    top = -winy/item_height;

    /* Decide where to put the items */
    if (targetitem)
    {
      hotlist_get_shape(&xmin, &xmax, targetitem);
      if (targetitem->type == hl_directory && winx >= xmin && winx <= xmin+item_dir_width)
      {
        position = HOTLIST_POSITION_BEGINNING; /* Put in directory */
      }
      else
      {
        if ((-winy % item_height) > item_height / 2)
          position = HOTLIST_POSITION_AFTER;          /* Put items after target item */
        else
          position = HOTLIST_POSITION_BEFORE;         /* Put items before target item */
      }
    }
    else
    {
      targetitem = hotlist_root;
      position = HOTLIST_POSITION_END;
    }

    tempint = hotlist_find_no_from_item(hotlist_find_selected_item());
    if (tempint < top) top = tempint;
    bottom = hotlist_count_displayed_items(hotlist_root->data.directory_content);

    if (targetitem->flags & HOTLIST_G_IS_SELECTED) return 0; /* do nothing if dropped on selected items */

    sourceitem = hotlist_find_selected_item();

    sourceitem->flags &= ~HOTLIST_G_IS_SELECTED;

    if (!shift)
    {
      /* Move selected items */

      hotlist_move_item(sourceitem, targetitem, position);                      /* Move first item to specified position */
                                                                                /* before/after/in target                */

      if (sourceitem->type == hl_directory) hotlist_clear_flags(sourceitem->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);

      targetitem = sourceitem;

      while((sourceitem = hotlist_find_selected_item()) != NULL)
      {
        sourceitem->flags &= ~HOTLIST_G_IS_SELECTED;
        hotlist_move_item(sourceitem, targetitem, HOTLIST_POSITION_AFTER);      /* Move all subsequent items to follow   */
                                                                                /* first moved item                      */
        if (sourceitem->type == hl_directory) hotlist_clear_flags(sourceitem->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
        targetitem = sourceitem;
      }
      hotlist_modified(HL_MODIFIED_MOVE);
    }
    else
    {
      /* Copy selected items */


      hotlist_copy_item(sourceitem, targetitem, position, &targetitem);         /* Copy first item to specified position */
                                                                                /* before/after/in target                */
      if (sourceitem->type == hl_directory) hotlist_clear_flags(sourceitem->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);

      while((sourceitem = hotlist_find_selected_item()) != NULL)
      {
        sourceitem->flags &= ~HOTLIST_G_IS_SELECTED;
        hotlist_copy_item(sourceitem,                                           /* Copy all subsequent items to follow   */
                          targetitem,                                           /* first copied item                     */
                          HOTLIST_POSITION_AFTER,
                          &targetitem);
        if (sourceitem->type == hl_directory) hotlist_clear_flags(sourceitem->data.directory_content, hl_ALL, HOTLIST_G_IS_SELECTED);
      }
      hotlist_modified(HL_MODIFIED_COPY);
    }

    bottom2 = hotlist_count_displayed_items(hotlist_root->data.directory_content);
    if (bottom2 > bottom) bottom = bottom2;
    hotlist_redraw_items(top, bottom);
    hotlist_preopen();
  }
  else
  {
    if (hotlist_count_selected_items() == 1 && (sourceitem = hotlist_find_selected_item())->type == hl_url)
    {
      /* Dropped in non hotlist window - save as a URI file */
      hotlist_initiate_uri_save(sourceitem);
    }
    else
    {
      /* Can't save URI file when saving more than one URL so save HTML file instead*/
      hotlist_initiate_html_save("Hotlist");
    }
    /* Drag was dropped in non-hotlist window */
  }

  return 0;
}

/*************************************************/
/* hotlist_autoscroll()                          */
/*                                               */
/* Auto scrolls a window                         */
/*                                               */
/* Parameters: handle of toolbox window to       */
/*             scroll or 0 to reset autoscroll   */
/*************************************************/

_kernel_oserror *hotlist_autoscroll(int window)
{
  ObjectId parent;
  ComponentId component;
  int scroll_changed;
  WimpGetWindowStateBlock state;
  BBox extent;
  int x, y;
  int position;
  int autoscroll_newtime;
  _kernel_oserror *e;
  ObjectId over_window;

  static unsigned int scrolling, autoscroll_oldtime, mouse_shape = Mouse_Shape_Normal;

  if (!window)
  {
    #ifdef TRACE
      Printf("hotlist_autoscroll: resetting\n");
    #endif
    scrolling = 0;
    mouse_set_pointer_shape(Mouse_Shape_Normal);
    mouse_shape = Mouse_Shape_Normal;

    return _swix(OS_ReadMonotonicTime, _OUTR(0, 0), &autoscroll_oldtime);
  }

  /* What window is the pointer over? */
  RetError(window_get_pointer_info(0, &x, &y, NULL, &over_window, NULL));

  RetError(window_get_wimp_handle(0, window, &state.window_handle));
  RetError(wimp_get_window_state(&state));
  RetError(window_get_extent(0, window, &extent));

  scroll_changed = 0;

  if ((x < state.visible_area.xmin) ||
      (x > state.visible_area.xmax) ||
      (y < state.visible_area.ymin) ||
      (y > state.visible_area.ymax))
  {
    position = 0; /* Outside visible area */
  }
  else
  if ((x > state.visible_area.xmin + HOTLIST_SCROLL_BOUNDARY_SIZE) &&
      (x < state.visible_area.xmax - HOTLIST_SCROLL_BOUNDARY_SIZE) &&
      (y > state.visible_area.ymin + HOTLIST_SCROLL_BOUNDARY_SIZE) &&
      (y < state.visible_area.ymax - HOTLIST_SCROLL_BOUNDARY_SIZE))
  {
    position = 1; /* Inside non scrolling region */
  }
  else
  {
    position = 2; /* Inside scrolling region */
  }

  #ifdef TRACE
  Printf("Pointer is ");
  switch(position)
  {
    case 0:
    Printf("Outside visible area");
    break;
    case 1:
    Printf("inside non scrolling region");
    break;
    case 2:
    Printf("Inside scrolling region");
    break;
  }
  Printf(" of scrolling window\n");

  Printf("scrolling = %d\n", scrolling);
  #endif

  if (!scrolling)
  {
    if (window == over_window)
    {
      if (position == 2)
      {
        /* Check timer, see if it's time to start scrolling */
        RetError(_swix(OS_ReadMonotonicTime, _OUTR(0, 0), &autoscroll_newtime));
        if ((autoscroll_newtime - autoscroll_oldtime) > AUTOSCROLL_DELAY)
        {
          if (mouse_shape != Mouse_Shape_Scrolling)
          {
            mouse_set_pointer_shape(Mouse_Shape_Scrolling);
            mouse_shape = Mouse_Shape_Scrolling;
          }
          scrolling = 1;
        }
        else
        {
          if (mouse_shape != Mouse_Shape_ToScroll)
          {
            mouse_shape = Mouse_Shape_ToScroll;
            mouse_set_pointer_shape(Mouse_Shape_ToScroll);
          }
        }
      }
      else
      {
        /* Set scroll timer to zero */
        RetError(_swix(OS_ReadMonotonicTime, _OUTR(0, 0), &autoscroll_oldtime));
        if (mouse_shape != Mouse_Shape_Normal)
        {
          mouse_set_pointer_shape(Mouse_Shape_Normal);
          mouse_shape = Mouse_Shape_Normal;
        }
      }
    }
    else
    {
      RetError(_swix(OS_ReadMonotonicTime, _OUTR(0, 0), &autoscroll_oldtime));
      if (mouse_shape != Mouse_Shape_Normal)
      {
        mouse_set_pointer_shape(Mouse_Shape_Normal);
        mouse_shape = Mouse_Shape_Normal;
      }
    }
  }
  else
  {
    if (position == 1)
    {
      /* Stop scrolling */
      scrolling = 0;
      RetError(_swix(OS_ReadMonotonicTime, _OUTR(0, 0), &autoscroll_oldtime));
      if (mouse_shape != Mouse_Shape_Normal)
      {
        mouse_set_pointer_shape(Mouse_Shape_Normal);
        mouse_shape = Mouse_Shape_Normal;
      }
    }
    else
    {
      /* Scroll a bit */

      if (y > state.visible_area.ymax - HOTLIST_SCROLL_BOUNDARY_SIZE)
      {
        /* Top */
        if (state.yscroll < extent.ymax)
        {
          scroll_changed = 1;
          state.yscroll += (y - (state.visible_area.ymax - HOTLIST_SCROLL_BOUNDARY_SIZE));
        }
      }
      else
      {
        if (y < state.visible_area.ymin + HOTLIST_SCROLL_BOUNDARY_SIZE)
        {
          /* Bottom */

          if (state.yscroll > extent.ymin + (state.visible_area.ymax - state.visible_area.ymin))
          {
            scroll_changed = 1;
            state.yscroll -= ((state.visible_area.ymin + HOTLIST_SCROLL_BOUNDARY_SIZE) - y);
          }
        }
      }

      if (x > state.visible_area.xmax - HOTLIST_SCROLL_BOUNDARY_SIZE)
      {
        /* Right */
        if (state.xscroll < extent.xmax - (state.visible_area.xmax - state.visible_area.xmin))
        {
          scroll_changed = 1;
          state.xscroll += (x - (state.visible_area.xmax - HOTLIST_SCROLL_BOUNDARY_SIZE));
        }
      }
      else
      {
        if (x < state.visible_area.xmin + HOTLIST_SCROLL_BOUNDARY_SIZE)
        {
          /* Left */
          if (state.xscroll > extent.xmin)
          {
            scroll_changed = 1;
            state.xscroll -= ((state.visible_area.xmin + HOTLIST_SCROLL_BOUNDARY_SIZE) - x);
          }
        }
      }
    }
  }

  if (scroll_changed)
  {
    RetError(toolbox_get_parent(0, window, &parent, &component));
    RetError(toolbox_show_object(0, window, Toolbox_ShowObject_FullSpec, &(state.visible_area), parent, component));
  }

  return NULL;
}

/*************************************************/
/* hotlist_null_handler()                        */
/*                                               */
/* Called every null event while drag is in      */
/* operation                                     */
/*************************************************/

static int hotlist_null_handler(int event_code, WimpPollBlock *event, IdBlock *id_block, void *handle)
{
  int x, y, buttons;
  unsigned int itemno, xmin, xmax;
  unsigned int item_height, item_dir_width, item_url_width;
  int new_time;
  hotlist_item *item;
  ObjectId window;
  ComponentId component;
  WimpGetWindowStateBlock state;

  window_get_pointer_info(0, &x, &y, &buttons, &window, &component);

  show_error_ret(hotlist_autoscroll(hotlist_windowid));

  if (window == hotlist_windowid)
  {
    window_get_wimp_handle(0, window, &state.window_handle);

    /* Display arrow pointing into directory to say that dropping */
    /* here will put in directory rather than next to it          */
    wimp_get_window_state(&state);
    hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

    x += (state.xscroll - state.visible_area.xmin);
    y += (state.yscroll - state.visible_area.ymax);

    itemno = -y / item_height;
    item = hotlist_find_item(hotlist_root->data.directory_content, itemno);

    if (item && item->type == hl_directory && !(item->flags & HOTLIST_G_IS_SELECTED))
    {
      hotlist_get_shape(&xmin, &xmax, item);
      if (x >= xmin && x <= xmin + item_dir_width)
      {
        if (hotlist_current_highlighted != item)
        {
          if (hotlist_current_highlighted)
          {
            hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;
            hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
          }
          item->flags |= HOTLIST_D_IS_HIGHLIGHTED;
          hotlist_current_highlighted = item;
          highlighted_itemno = itemno;
          hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
          _swix(OS_ReadMonotonicTime, _OUTR(0, 0), &autoopen_oldtime);
        }
        else
        {
          _swix(OS_ReadMonotonicTime, _OUTR(0, 0), &new_time);
          /* Auto open directories */
          if (choices.autoopen_delay &&
              !(item->flags & HOTLIST_D_IS_OPEN) &&
              (new_time - autoopen_oldtime) > choices.autoopen_delay)
          {
            item->flags |= HOTLIST_D_IS_OPEN;
            hotlist_preopen();
            hotlist_redraw_items(highlighted_itemno, hotlist_count_displayed_items(hotlist_root->data.directory_content));
          }
        }
      }
      else
      {
        if (hotlist_current_highlighted)
        {
          hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;
          hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
          hotlist_current_highlighted = NULL;
        }
      }
    }
    else
    {
      if (hotlist_current_highlighted)
      {
        hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;
        hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
        hotlist_current_highlighted = NULL;
      }
    }
  }
  else
  {
    /* non hotlist window */
    if (hotlist_current_highlighted)
    {
      hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;
      hotlist_redraw_items(highlighted_itemno, highlighted_itemno);
      hotlist_current_highlighted = NULL;
    }
  }
  return 0;
}

/* Simple non-scrolling selection box, to be improved later */

/* Workarea relative corner of selection box may not be needed later */
///////////////////////////////////////////////////////////////////////
static int selection_x, selection_y;
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

/*************************************************/
/* hotlist_start_drag()                          */
/*                                               */
/* This function is called to start a selection  */
/* box operation in the hotlist window.          */
/*************************************************/

static _kernel_oserror *hotlist_selection_box_start(void)
{
  _kernel_oserror *err;
  WimpDragBox box;
  WimpGetPointerInfoBlock pointerblock;
  WimpGetWindowStateBlock state;

  wimp_get_pointer_info(&pointerblock);                          /* Read pointer position */

  show_error(window_get_wimp_handle(0, hotlist_windowid, &state.window_handle));
  wimp_get_window_state(&state);

  selection_x = pointerblock.x + (state.xscroll - state.visible_area.xmin);
  selection_y = pointerblock.y + (state.yscroll - state.visible_area.ymax);

  box.drag_type         = Wimp_DragBox_DragRubberDash;
  box.dragging_box.xmin = pointerblock.x;                          /* box shape */
  box.dragging_box.xmax = pointerblock.x;
  box.dragging_box.ymin = pointerblock.y;
  box.dragging_box.ymax = pointerblock.y;
  box.parent_box.xmin   = state.visible_area.xmin;  /* Bounding box for dragged box */
  box.parent_box.xmax   = state.visible_area.xmax;
  box.parent_box.ymin   = state.visible_area.ymin;
  box.parent_box.ymax   = state.visible_area.ymax;
  err = wimp_drag_box(&box);                                           /* start drag box */
  if (err) return err;
  hotlist_dragging = HOTLIST_BOX_DRAG_SELECTION;                 /* Set global variable saying we are currently dragging */
                                                                 /* so we know whether to process a user_drag_box event  */

  register_null_claimant(Wimp_ENull, hotlist_null_drag_select_handler, NULL);
  return NULL;
}

/*************************************************/
/* hotlist_null_drag_select_handler()            */
/*                                               */
/* This function is called as a null handler;    */
/* it is responsible for selecting and           */
/* deselecting items within and outside the      */
/* rubber drag box started by                    */
/* hotlist_selection_box_start.                  */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

static int hotlist_null_drag_select_handler(int event_code, WimpPollBlock * event, IdBlock * id_block, void * handle)
{
  WimpGetPointerInfoBlock pointerblock;
  unsigned int item_height, item_dir_width, item_url_width;
  int workx, worky;
  hotlist_item *item;
  WimpGetWindowStateBlock state;
  unsigned int item_min, item_max, itemno, itemxmin, itemxmax;
  int minx, maxx;
  int last_item;

  wimp_get_pointer_info(&pointerblock);                          /* Read pointer position */

  show_error(window_get_wimp_handle(0, hotlist_windowid, &state.window_handle));
  wimp_get_window_state(&state);

  hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);

  workx = pointerblock.x + (state.xscroll - state.visible_area.xmin);
  worky = pointerblock.y + (state.yscroll - state.visible_area.ymax);

  item_min = -worky / item_height;
  if (-selection_y / item_height < item_min) item_min = -selection_y / item_height;
  item_max = (-worky / item_height);
  if ((-selection_y / item_height) > item_max) item_max = (-selection_y / item_height);

  if (selection_x < workx)
  {
    minx = selection_x;
    maxx = workx;
  }
  else
  {
    minx = workx;
    maxx = selection_x;
  }

  last_item = hotlist_count_displayed_items(hotlist_root->data.directory_content);

  for(itemno = 0; itemno <= last_item; itemno++)
  {
    item = hotlist_find_item(hotlist_root->data.directory_content, itemno);
    if (item)
    {
      if (itemno >= item_min && itemno <= item_max)
      {
        hotlist_get_shape(&itemxmin, &itemxmax, item);
        if ((!(maxx < itemxmin || minx > itemxmax)))
        {
          if (!(item->flags & HOTLIST_G_DRAG_SELECTED))
          {
            item->flags |= HOTLIST_G_DRAG_SELECTED;
            hotlist_redraw_items(itemno, itemno);
            if (item->type == hl_directory && !(item->flags & HOTLIST_D_IS_OPEN)) /* If directory is closed select everything in it */
            {
              hotlist_set_flags(item->data.directory_content, hl_ALL, HOTLIST_G_DRAG_SELECTED);
              hotlist_clear_flags(item->data.directory_content, hl_ALL, HOTLIST_G_REDRAW_NOW);
            }
          }
        }
        else
        {
          if (item->flags & HOTLIST_G_DRAG_SELECTED)
          {
            item->flags &= ~HOTLIST_G_DRAG_SELECTED;
            hotlist_redraw_items(itemno, itemno);
            if (item->type == hl_directory && !(item->flags & HOTLIST_D_IS_OPEN)) /* If directory is closed unselect everything in it */
            {
              hotlist_clear_flags(item->data.directory_content, hl_ALL, HOTLIST_G_DRAG_SELECTED | HOTLIST_G_REDRAW_NOW);
            }
          }
        }
      }
      else
      {
        if (item->flags & HOTLIST_G_DRAG_SELECTED)
        {
          item->flags &= ~HOTLIST_G_DRAG_SELECTED;
          hotlist_redraw_items(itemno, itemno);
          if (item->type == hl_directory && !(item->flags & HOTLIST_D_IS_OPEN)) /* If directory is closed unselect everything in it */
          {
            hotlist_clear_flags(item->data.directory_content, hl_ALL, HOTLIST_G_DRAG_SELECTED | HOTLIST_G_REDRAW_NOW);
          }
        }
      }
    }
  }

  return 0;
}

/*************************************************/
/* hotlist_contents_selected()                   */
/*                                               */
/* Checks if all items and subdirectories are    */
/* selected.                                     */
/*                                               */
/* Parameters: Pointer to the first hotlist_item */
/*             to check.                         */
/*                                               */
/* Returns:    1 if all items are selected, else */
/*             0 (there are unselected items).   */
/*************************************************/

static unsigned int hotlist_contents_selected(hotlist_item * item)
{
  while(item)
  {
    /* Keep trying to find an unselected item and */
    /* immediately return 0 if found.             */

    if (!item->flags & HOTLIST_G_IS_SELECTED) return 0;

    if (item->type == hl_directory)
    {
      if (!hotlist_contents_selected(item->data.directory_content)) return 0;
    }

    item = item->next;
  }
  return 1;
}

/*************************************************/
/* hotlist_no_contents_selected()                */
/*                                               */
/* Checks if all items and subdirectories are    */
/* unselected.                                   */
/*                                               */
/* Parameters: Pointer to the first hotlist_item */
/*             to check.                         */
/*                                               */
/* Returns:    1 if all items are unselected,    */
/*             or 0 (there are selected items).  */
/*************************************************/

static unsigned int hotlist_no_contents_selected(hotlist_item * item)
{
  while (item)
  {
    /* Keep trying to find a selected item and */
    /* immediately return 0 if found.          */

    if (item->flags & HOTLIST_G_IS_SELECTED) return 0;

    if (item->type == hl_directory)
    {
      if (!hotlist_no_contents_selected(item->data.directory_content)) return 0;
    }

    item = item->next;
  }

  return 1;
}

/*************************************************/
/* hotlist_show_descriptions_handler()           */
/*                                               */
/* Selects show descriptions and redraws hotlist */
/* window and menu to reflect this               */
/*************************************************/

static int hotlist_show_descriptions_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hl_show_urls = 0;
  hotlist_set_menu_details(id_block->parent_id);
  hotlist_preopen();
  hotlist_redraw_items(0,
                       hotlist_count_displayed_items(hotlist_root->data.directory_content));
  return 1;
}

/*************************************************/
/* hotlist_show_urls_handler()                   */
/*                                               */
/* Selects show urls and redraws hotlist  window */
/* and menu to reflect this                      */
/*************************************************/

static int hotlist_show_urls_handler(int event_code, ToolboxEvent *event, IdBlock *id_block, void *handle)
{
  hl_show_urls = 1;
  hotlist_set_menu_details(id_block->parent_id);
  hotlist_preopen();
  hotlist_redraw_items(0,
                       hotlist_count_displayed_items(hotlist_root->data.directory_content));
  return 1;
}

/*************************************************/
/* hotlist_convert_drag_selection()              */
/*                                               */
/* Recurses through the hotlist_item structure   */
/* altering all 'HOTLIST_G_DRAG_SELECTED' items  */
/* to have 'HOTLIST_G_IS_SELECTED' set or unset  */
/* as is appropriate. ie. (effectivly eor'd)     */
/* dragselected and selected = unselected        */
/* dragselected              = selected          */
/* selected                  = selected          */
/* neither                   = unselected        */
/*                                               */
/* Parameters: Pointer to hotlist_item           */
/*************************************************/

static void hotlist_convert_drag_selection(hotlist_item *item)
{
  while(item)
  {
    if (item->flags & HOTLIST_G_DRAG_SELECTED)
    {
      item->flags &= ~HOTLIST_G_DRAG_SELECTED;
      if (item->flags & HOTLIST_G_IS_SELECTED) item->flags &= ~HOTLIST_G_IS_SELECTED;
      else                                     item->flags |= HOTLIST_G_IS_SELECTED;
    }

    if (item->type == hl_directory)
    {
      hotlist_convert_drag_selection(item->data.directory_content);
    }

    item = item->next;
  }
}

/*************************************************/
/* hotlist_modified()                            */
/*                                               */
/* Called whenever the hotlist is modified.      */
/*                                               */
/* Parameters: The operaton that modified the    */
/*             hotlist, as a number; see the     */
/*             HL_MODIFIED_... definitions in    */
/*             Hotlist.h.                        */
/*************************************************/

static _kernel_oserror * hotlist_modified(unsigned int type)
{
  if (choices.save_hotlist == 2) return hotlist_save(lookup_choice("HotlistPath:Browse:User.Hotlist",0,0));

  return NULL;
}

/* Data transfer routines follow */


/*************************************************/
/* hotlist_initiate_uri_save()                   */
/*************************************************/

static _kernel_oserror *hotlist_initiate_uri_save(hotlist_item *item)
{
  WimpGetPointerInfoBlock block;
  WimpMessage message;
  int new_task_handle;
  _kernel_oserror *e;

  RetError(wimp_get_pointer_info(&block));

  message.hdr.size        = sizeof(WimpMessage);
  message.hdr.your_ref    = 0;
  message.hdr.action_code = Wimp_MDataSave;

  message.data.data_save.destination_window = block.window_handle;
  message.data.data_save.destination_icon   = block.icon_handle;
  message.data.data_save.destination_x      = block.x;
  message.data.data_save.destination_y      = block.y;
  message.data.data_save.estimated_size     = strlen(item->data.url)+1;
  message.data.data_save.file_type          = 0xf91; /* URI file - to be replaced with a defined value */
  urlutils_leafname_from_url(item->data.url, message.data.data_save.leaf_name, 212);

  RetError(wimp_send_message(Wimp_EUserMessageRecorded,
                             &message,
                             block.window_handle,
                             block.icon_handle,
                             &new_task_handle));

  hotlist_save_item = item;
  hotlist_ram_transfer_sent = 0;
  hotlist_save_type = HL_SAVE_URI;

  return NULL;
}

/*************************************************/
/* hotlist_initiate_html_save()                  */
/*************************************************/

static _kernel_oserror *hotlist_initiate_html_save(char *filename)
{
  WimpGetPointerInfoBlock block;
  WimpMessage message;
  int new_task_handle;
  _kernel_oserror *e;

  RetError(wimp_get_pointer_info(&block));

  message.hdr.size        = sizeof(WimpMessage);
  message.hdr.your_ref    = 0;
  message.hdr.action_code = Wimp_MDataSave;

  message.data.data_save.destination_window = block.window_handle;
  message.data.data_save.destination_icon   = block.icon_handle;
  message.data.data_save.destination_x      = block.x;
  message.data.data_save.destination_y      = block.y;
  message.data.data_save.estimated_size     = -1; /* Dunno how big it's going to be */
  message.data.data_save.file_type          = FileType_HTML;

  strncpy(message.data.data_save.leaf_name, filename, 212);

  RetError(wimp_send_message(Wimp_EUserMessageRecorded,
                             &message,
                             block.window_handle,
                             block.icon_handle,
                             &new_task_handle));

  hotlist_ram_transfer_sent = 0;
  hotlist_save_type = HL_SAVE_HTML;

  return NULL;
}

/*************************************************/
/* hotlist_data_save_ack_handler()               */
/*************************************************/

static int hotlist_data_save_ack_handler(WimpMessage *message, void *handle)
{
  FILE *fileptr;
  int new_task_handle;
  _kernel_oserror *e;

  switch(hotlist_save_type)
  {
    case HL_SAVE_URI:
    if (hotlist_save_item)
    {
      fileptr = fopen(message->data.data_save_ack.leaf_name, "w");
      if (fileptr)
      {
        fprintf(fileptr, hotlist_save_item->data.url);

        if (fclose(fileptr))
        {
          show_error_ret(_kernel_last_oserror());
          hotlist_save_item = NULL;
          hotlist_save_type = HL_SAVE_NONE;
          return 1;
        }

        e = _swix(OS_File,
                  _INR(0,2),
                  18,
                  message->data.data_save_ack.leaf_name,
                  0xf91);

        if (e)
        {
          show_error_ret(e);
          hotlist_save_item = NULL;
          hotlist_save_type = HL_SAVE_NONE;
          return 1;
        }

        message->hdr.action_code = Wimp_MDataLoad;
        message->hdr.your_ref    = message->hdr.my_ref;

        e = wimp_send_message(Wimp_EUserMessageRecorded,
                              message,
                              message->hdr.sender,
                              0,
                              &new_task_handle);

        if (e)
        {
          show_error_ret(e);
          if (remove(message->data.data_save_ack.leaf_name))
          {
            show_error_ret(_kernel_last_oserror());
          }
          hotlist_save_item = NULL;
          hotlist_save_type = HL_SAVE_NONE;
          return 1;
        }
      }
      else
      {
        show_error_ret(_kernel_last_oserror());
      }

      hotlist_save_item = NULL;
      hotlist_save_type = HL_SAVE_NONE;
      return 1;
    }
    break;

    case HL_SAVE_HTML:

    e = hotlist_save_hotlist(message->data.data_save_ack.leaf_name, 1);

    hotlist_clear_selection();

    if (e)
    {
      show_error_ret(e);
      hotlist_save_type = HL_SAVE_NONE;
    }

    message->hdr.action_code = Wimp_MDataLoad;
    message->hdr.your_ref    = message->hdr.my_ref;

    e = wimp_send_message(Wimp_EUserMessageRecorded,
                          message,
                          message->hdr.sender,
                          0,
                          &new_task_handle);

    if (e)
    {
      show_error_ret(e);
      if (remove(message->data.data_save_ack.leaf_name))
      {
        show_error_ret(_kernel_last_oserror());
      }
      hotlist_save_type = HL_SAVE_NONE;
    }

    return 1;

    break;

    default:
    break;
  }

  return 0;
}

/*************************************************/
/* hotlist_ram_fetch_handler()                   */
/*************************************************/

static int hotlist_ram_fetch_handler(WimpMessage *message, void *handle)
{
  int left, towrite, new_task_handle;
  _kernel_oserror *e;

  if (hotlist_save_type == HL_SAVE_URI && hotlist_save_item)
  {
    /* Calculate the number of bytes left to send */
    /* Don't include the terminating null as it is not required for URI files*/
    left = (strlen(hotlist_save_item->data.url)) - hotlist_ram_transfer_sent;
    /* Use the smaller of the buffer size and the number of bytes to write */
    towrite = message->data.ram_fetch.buffer_size > left ? left : message->data.ram_fetch.buffer_size;

    e = wimp_transfer_block(task_handle,
                            (char*)(hotlist_save_item->data.url) + hotlist_ram_transfer_sent,
                            message->hdr.sender,
                            message->data.ram_fetch.buffer,
                            towrite);

    if (e)
    {
      hotlist_save_item = NULL;
      show_error_ret(e);
      return 1;
    }

    message->hdr.your_ref             = message->hdr.my_ref;
    message->hdr.action_code          = Wimp_MRAMTransmit;
    message->data.ram_transmit.nbytes = towrite;

    e = wimp_send_message(Wimp_EUserMessage,
                          message,
                          message->hdr.sender,
                          0,
                          &new_task_handle);

    if (e)
    {
      hotlist_save_item = NULL;
      show_error_ret(e);
      return 1;
    }

    /* If no bytes were transmitted this is the last part of the ram transfer */
    if (!left)
    {
      hotlist_save_item = NULL;
      hotlist_ram_transfer_sent = 0;
      return 1;
    }

    /* Increase the total number of bytes send by the number just send */
    hotlist_ram_transfer_sent += towrite;

    return 1;
  }
  return 0;
}

/*************************************************/
/* hotlist_return_window_id()                    */
/*                                               */
/* Returns:    The toolbox object ID of the      */
/*             hotlist window.                   */
/*************************************************/

ObjectId hotlist_return_window_id(void)
{
  return hotlist_windowid;
}

/*************************************************/
/* hotlist_add_position()                        */
/*                                               */
/* Add a new URL to the hotlist at a specified   */
/* position.�                                    */
/*                                               */
/* Parameters: screen relative x position        */
/*                                               */
/*             screen relative y position        */
/*                                               */
/*             Description of the URL (e.g. from */
/*             the page title);                  */
/*                                               */
/*             Pointer to the URL itself;        */
/*************************************************/

_kernel_oserror * hotlist_add_position(int x, int y, char *description, char *url)
{
  _kernel_oserror *e;
  WimpGetWindowStateBlock state;
  int winx, winy, top, position;
  hotlist_item * targetitem;
  unsigned int item_height, item_dir_width, item_url_width;

  /* Calculate window relative coordinates */

  RetError(window_get_wimp_handle(0,
                                  hotlist_windowid,
                                  &state.window_handle));

  RetError(wimp_get_window_state(&state));

  winx = coords_x_toworkarea(x, (WimpRedrawWindowBlock *) &state);
  winy = coords_y_toworkarea(y, (WimpRedrawWindowBlock *) &state);

  /* Calculate which item add is over */
  hotlist_get_entry_sizes(&item_height, &item_dir_width, &item_url_width);
  targetitem = hotlist_find_item(hotlist_root->data.directory_content, -winy/item_height);
  top = -winy/item_height;

  /* Decide where to put the new item */
  if (targetitem)
  {
    if ((-winy % item_height) > item_height / 2)
    {
      /* Put item after target item */
      position = HOTLIST_POSITION_AFTER;
    }
    else
    {
      /* Put item before target item */
      position = HOTLIST_POSITION_BEFORE;
    }
  }
  else
  {
    /* Put item at end of root directory */
    position = HOTLIST_POSITION_END;
    targetitem = hotlist_root;
  }

  /* Create new item in appropriate place */
  e = hotlist_new_url(targetitem,
                      position,
                      description,
                      url);

  if (!e) hotlist_preopen();

  /* Optimise the redraw to do as little as possible depending */
  /* upon where in the list the item was added.                */

  switch (position)
  {
    case HOTLIST_POSITION_END:
    {
      hotlist_redraw_now();
    }
    break;

    default:
    {
      hotlist_clear_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);
      hotlist_redraw_items(-winy/item_height - 1,
                           hotlist_count_displayed_items(hotlist_root->data.directory_content));
    }
    break;
  }

  if (!e) return hotlist_modified(HL_MODIFIED_ADD);

  return e;
}