/* 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 "ChoiceDefs.h"
#include "FetchPage.h"
#include "Filetypes.h"
#include "Menus.h"
#include "Mouse.h"
#include "Multiuser.h"
#include "Protocols.h"
#include "Save.h"
#include "Toolbars.h"
#include "URLUtils.h"
#include "Windows.h"

#include "Hotlist.h"

/* Internationalisation support */

#ifdef UNIFONT
  #define CHARSET_SPECIFIER "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n"
#else
  #define CHARSET_SPECIFIER "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n"
#endif

/* Local definitions */

#define HotlistWrite(fn) {written = (fn); if (written < 0) goto hotlist_save_error;}

/* 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            = NULL_ObjectId; /* 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 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 */
/* Local structures */

typedef struct hl_dragging_details
{
  char         drag_type;       /* One of HOTIST_NOT_DRAGGING, HOTLIST_BOX_DRAG, HOTLIST_SOLID_DRAG_OBJECT, */
                                /* HOTLIST_SOLID_DRAG_SPRITE, or HOTLIST_BOX_DRAG_SELECTION - See Hotlist.h */
  unsigned int using_adjust :1;
}
hl_dragging_details;

static hl_dragging_details hotlist_dragging;

/* 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_select_all_handler;
static ToolboxEventHandler   hotlist_clear_selection_handler;
static ToolboxEventHandler   hotlist_menu_openall_handler;
static ToolboxEventHandler   hotlist_menu_closeall_handler;
static ToolboxEventHandler   hotlist_menu_delete_handler;
static ToolboxEventHandler   hotlist_save_to_server_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 ToolboxEventHandler   hotlist_close_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_desc, 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_r    (hotlist_item * list);

/* Counting items */

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 _kernel_oserror * hotlist_redraw_now              (void);
static _kernel_oserror * hotlist_redraw_now_r            (hotlist_item * list, int * curr_item);
static _kernel_oserror * hotlist_get_shape               (int * xmin, int * xmax, hotlist_item * item);
static _kernel_oserror * hotlist_directory_open_close    (hotlist_item * item, unsigned int itemno);
static _kernel_oserror * hotlist_redraw_items            (unsigned int firstitem, unsigned int lastitem);
static _kernel_oserror * 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 _kernel_oserror * hotlist_set_menu_details        (ObjectId menuid);
static _kernel_oserror * hotlist_save_entries            (FILE * fileptr, hotlist_item * list, int type, int save_read_only);
static void              hotlist_lower_tags              (char * string);
static void              hotlist_drag_renderer           (hotlist_item * item, unsigned int item_height, unsigned int item_dir_width, unsigned int item_url_width);
static _kernel_oserror * hotlist_start_drag              (void);
static _kernel_oserror * hotlist_start_drag_backend      (void);
static _kernel_oserror * hotlist_modified                (unsigned int type);
static void              hotlist_convert_drag_selection  (hotlist_item * item);
static _kernel_oserror * hotlist_autoscroll              (int window);
static _kernel_oserror * hotlist_load_directory          (FILE * fileptr, hotlist_item * target);

static int               hotlist_get_selected_shape      (BBox * box);
static _kernel_oserror * hotlist_get_selected_shape_r    (hotlist_item * list, BBox * box, unsigned int * itemno, int * found, unsigned int item_height);

static _kernel_oserror * hotlist_select_box              (unsigned int first_item, unsigned int last_item, int minx, int maxx);
static _kernel_oserror * hotlist_select_box_r            (unsigned int first_item, unsigned int last_item, hotlist_item *item, unsigned int *itemno, int minx, int maxx);

static void              hotlist_find_match_r            (char * buffer, int buffer_size, hotlist_item * item, hotlist_item ** lowest_item, int * lowest_offset, int * lowest_diff);

/* 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_is_inside()                           */
// /*                                               */
// /* Checks if one hotlist_item is held inside     */
// /* the directory structure of another            */
// /* hotlist_item                                  */
// /*                                               */
// /* Parameters: Pointer to the hotlist_item that  */
// /*             may be inside the parent;         */
// /*                                               */
// /*             Pointer to the parent             */
// /*             hotlist_item.                     */
// /*                                               */
// /* Returns:    1 if item is inside, else 0.      */
// /*************************************************/
//
// static int hotlist_is_inside(hotlist_item * inside, hotlist_item * outside)
// {
//   while (inside)
//   {
//     if (inside == outside) return 1;
//     inside = inside->parent;
//   }
//
//   return 0;
// }

/*************************************************/
/* 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;
  int                is_untitled = 0;

  if (!directory_name) is_untitled = 1;
  else
  {
    while (*directory_name && *directory_name <= ' ') directory_name ++;

    if (!*directory_name) is_untitled = 1;
    else
    {
      int len = strlen(directory_name);

      while (len && directory_name[len - 1] <= ' ') len--;

      if (!len) is_untitled = 1;
      else directory_name[len] = '\0';
    }
  }

  if (is_untitled) directory_name = lookup_token("HotlistUntitled:(Untitled)",0,0);

  /* 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_desc, char * url)
{
  char            * perm_url_desc;
  char            * perm_url;
  hotlist_item    * item;
  _kernel_oserror * e;

  /* Do we have a URL string? */

  if (!url) return NULL;

  while (*url && *url <= ' ') url++;

  if (!*url) return NULL;

  /* Do we have a useful title string? */

  if (!url_desc) url_desc = url;
  else
  {
    while (*url_desc && *url_desc <= ' ') url_desc ++;

    if (!*url_desc) url_desc = url;
    else
    {
      int len = strlen(url_desc) - 1;

      while (len && url_desc[len] <= ' ') len--;

      if (!len) url_desc = url;
      else url_desc[len + 1] = '\0';
    }
  }

  /* Allocate a new hotlist_item structure */

  if ((item = malloc(sizeof(hotlist_item))) == NULL)
  {
    #ifdef TRACE
        if (tl & (1<<25)) Printf("hotlist_new_url: 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_desc) + 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_desc);
  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)
{
  hotlist_item * newdir, * tempptr;

  /* If the item is read only, don't move it; copy instead. */

  if (source->flags & HOTLIST_G_IS_READ_ONLY)
  {
    return hotlist_copy_item(source, target, position, NULL);
  }

  /* Otherwise, moving it is OK */

  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)
{
  hotlist_item * newdir;

  newdir = NULL;

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

    case hl_url:
    {
      RetError(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)
{
  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.              */
/*************************************************/

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

static 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.               */
/*                                               */
/* If you want to know if all items are selected */
/* use hotlist_contents_selected. If you want to */
/* know if no items are selected, it is faster   */
/* to use hotlist_no_contents_selected than      */
/* compare the return value of this function     */
/* against zero (this function *must* scan all   */
/* hotlist items, whereas the other can exit as  */
/* soon as a selected item is found).            */
/*                                               */
/* Returns:    The number of selected items.     */
/*************************************************/

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 (count  */
/*             the visible items from the top of */
/*             the window downwards starting at  */
/*             an item number of zero);          */
/*                                               */
/*             Last item number to draw.         */
/*************************************************/

static _kernel_oserror * hotlist_draw(hotlist_item * list, unsigned int first_item, unsigned int last_item)
{
  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 (count  */
/*             the visible items from the top of */
/*             the window downwards starting at  */
/*             an item number of zero);          */
/*                                               */
/*             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)
{
  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             = !(list->flags & HOTLIST_G_IS_READ_ONLY) ? URL_SPRITE : RESOURCES_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 = !(list->flags & HOTLIST_G_IS_READ_ONLY) ? OPEN_DIRECTORY_SPRITE : OPEN_RESOURCES_DIRECTORY_SPRITE;
            }
            else
            {
              icon.data.is.sprite = !(list->flags & HOTLIST_G_IS_READ_ONLY) ? CLOSED_DIRECTORY_SPRITE : CLOSED_RESOURCES_DIRECTORY_SPRITE;
            }

            icon.data.is.sprite_name_length = strlen(icon.data.is.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 _kernel_oserror * hotlist_redraw_now(void)
{
  int curr_item = 0;

  return 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 _kernel_oserror * hotlist_redraw_now_r(hotlist_item * list, int * curr_item)
{
  while (list)
  {
    if (list->flags & HOTLIST_G_REDRAW_NOW)
    {
      /* Redraw just the one item */

      RetError(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 */

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

    list = list->next;
  }

  return NULL;
}

/*************************************************/
/* 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;
  hotlist_item    * target;
  int               type;

  target = hotlist_find_selected_item();

  if (!target)
  {
    position = at_bottom ? HOTLIST_POSITION_END : HOTLIST_POSITION_BEGINNING;
    target   = hotlist_root;
    type     = 1;

    /* If adding to the beginning of the hotlist, skip any */
    /* read-only items at the top first.                   */

    if (position == HOTLIST_POSITION_BEGINNING)
    {
      hotlist_item * check = target->data.directory_content;

      /* Look for something not read-only */

      while (check && check->next)
      {
        if (check->next->flags & HOTLIST_G_IS_READ_ONLY) check = check->next;
        else break;
      }

      if (check && (check->flags & HOTLIST_G_IS_READ_ONLY))
      {
        position = HOTLIST_POSITION_AFTER;
        target   = check;
      }
    }
  }
  else
  {
    if (target->type == hl_directory)
    {
      position = HOTLIST_POSITION_BEGINNING;
    }
    else
    {
      position = HOTLIST_POSITION_AFTER;
    }

    type = 2;
  }

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

  e = hotlist_new_url(target,
                      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:
    {
      if (type == 1)
      {
        hotlist_redraw_items(0,
                             hotlist_count_displayed_items(hotlist_root->data.directory_content));
      }
      else
      {
        if (target->flags & HOTLIST_D_IS_OPEN)
        {
          hotlist_redraw_items(hotlist_find_no_from_item(target),
                               hotlist_count_displayed_items(hotlist_root->data.directory_content));
        }
      }
    }
    break;

    case HOTLIST_POSITION_BEFORE:
    case HOTLIST_POSITION_AFTER:
    {
      hotlist_redraw_items(hotlist_find_no_from_item(hotlist_newitem),
                           hotlist_count_displayed_items(hotlist_root->data.directory_content));
    }
  }

  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: Pointer to an int, in which the   */
/*             xmin offset (in OS units) from    */
/*             the left hand edge is returned;   */
/*                                               */
/*             Similarly, a pointer to an int to */
/*             take the xmax offset;             */
/*                                               */
/*             Pointer to the hotlist_item to    */
/*             examine.                          */
/*                                               */
/* Assumes:    Any pointer may be NULL.          */
/*************************************************/

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

  if (xmin) *xmin = 0;
  if (xmax) *xmax = 0;

  if (!item) return NULL;

  /* Find entry sizes */

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

  /* Count how many parent items we have. This allows */
  /* the left hand indent to be calculated.           */

  tempitem = item;

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

  count -= 2;

  /* Find the text width */

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

  /* Add in the sprite width */

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

    case hl_url:       icon_width = item_url_width;
    break;

    default:           icon_width = 0;
    break;
  }

  /* Add up the total and exit */

  if (xmin) *xmin = count * item_dir_width;
  if (xmax) *xmax = count * item_dir_width + icon_width + 2 + text_width + 12;

  return NULL;
}

/*************************************************/
/* hotlist_directory_open_close()                */
/*                                               */
/* This function opens or closes a directory,    */
/* dealing with all required redrawing.          */
/*                                               */
/* Parameters: Pointer to the hotlist_item       */
/*             struct representing the directory */
/*             to open or close;                 */
/*                                               */
/*             Number of that item (counting the */
/*             visible hotlist items from the    */
/*             top of the window downwards,      */
/*             starting at zero).                */
/*************************************************/

static _kernel_oserror * 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;

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

  /* Swap the flag saying whether the directory is open or not */

  item->flags ^= HOTLIST_D_IS_OPEN;

  /* Clear all selected items within the directory */

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

  /* Do the appropriate redrawing */

  hotlist_preopen();

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

  top = -itemno * item_height;

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

  return NULL;
}

/*************************************************/
/* hotlist_redraw_items()                        */
/*                                               */
/* This function forces the redraw of a set of   */
/* items.                                        */
/*                                               */
/* Parameters: Item number of the first item to  */
/*             redraw (counting the visible      */
/*             items from the top of the window  */
/*             downwards, starting at zero) -    */
/*             this is inclusive;                */
/*                                               */
/*             Item number of the last item to   */
/*             redraw (also inclusive).          */
/*************************************************/

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

  /* Get the entry sizes */

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

  /* Find the window extent */

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

  /* Force a redraw for the whole extent width, over a vertical */
  /* span determined by the given item numbers.                 */

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

/*************************************************/
/* hotlist_clear_selection()                     */
/*                                               */
/* This function unselects all items, redrawing  */
/* as required.                                  */
/*************************************************/

_kernel_oserror * hotlist_clear_selection(void)
{
  if (
       hotlist_clear_flags(hotlist_root->data.directory_content,
                           hl_ALL,
                           HOTLIST_G_IS_SELECTED)
     )
     return hotlist_redraw_now();

  return NULL;
}

/*************************************************/
/* hotlist_launch_url()                          */
/*                                               */
/* This function launches the url in the passed  */
/* item.                                         */
/*                                               */
/* Parameters: Pointer to a hotlist_item struct  */
/*             holding the URL to launch.        */
/*************************************************/

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

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

/*************************************************/
/* hotlist_process_click_on_item()               */
/*                                               */
/* Deal with clicks on hotlist items.            */
/*                                               */
/* Parameters: Item number that was clicked upon */
/*             (counting visible items from the  */
/*             top of the window downwards,      */
/*             starting with item number 0);     */
/*                                               */
/*             Pointer to a hotlist_item struct  */
/*             representing the item clicked on; */
/*                                               */
/*             State of the mouse buttons, as    */
/*             Wimp_GetPointerInfo would return  */
/*             for clicks on a window of button  */
/*             type 10 (Double/Click/Drag);      */
/*                                               */
/*             Screen x coordinate of the click; */
/*                                               */
/*             Screen y coordinate of the click. */
/*************************************************/

static _kernel_oserror * hotlist_process_click_on_item(unsigned int itemno, hotlist_item * item, int buttons, int x, int y)
{
  _kernel_oserror * e = NULL;

  switch (buttons)
  {
    /* The window button type is Double/Click/Drag, so */
    /* this represents a double click with Select.     */

    case Wimp_MouseButtonSelect:
    {
      /* Open or close directories, open URLs in a new window */

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

          case hl_url:       e = hotlist_launch_url(item);
          break;
        }

        if (e) show_error_ret(e);

        /* Deselect the item that was double-clicked upon */

        item->flags &= ~HOTLIST_G_IS_SELECTED;
        e = hotlist_redraw_items(itemno, itemno);
      }
    }
    break;

    /* Double-click with Adjust */

    case Wimp_MouseButtonAdjust:
    {
      /* Open or close directories, but for URLs, open them in */
      /* a new window and close the hotlist.                   */

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

          case hl_url:
          {
            e = hotlist_launch_url(item);

            if (!e) toolbox_hide_object(0, hotlist_windowid);
          }
          break;
        }

        if (e) show_error_ret(e);

        /* Deselect the item that was double-clicked upon */

        item->flags &= ~HOTLIST_G_IS_SELECTED;
        e = hotlist_redraw_items(itemno, itemno);
      }
    }
    break;

    /* Drag with Select */

    case 64:
    {
      e = hotlist_start_drag();
    }
    break;

    /* Drag with Adjust */

    case 16:
    {
      /* If the item clicked upon wasn't selected, select it */

      if (!(item->flags & HOTLIST_G_IS_SELECTED))
      {
        item->flags |= HOTLIST_G_IS_SELECTED;

        last_selected_item = itemno;
      }

      /* Start the drag and redraw the item clicked upon */

      e = hotlist_start_drag();

      if (!e) e = hotlist_redraw_items(itemno, itemno);
    }
    break;

    /* Click with Select */

    case 1024:
    {
      if (!(item->flags & HOTLIST_G_IS_SELECTED)) /* (Do nothing when selected) */
      {
        /* Clear the selected flags of everything else */

        e = hotlist_clear_selection();

        /* Select this item and if a directory, all items within it */

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

        /* Redraw to reflect the selection */

        if (!e) e = hotlist_redraw_now();
      }

      last_selected_item = itemno;
    }
    break;

    /* Click with Adjust */

    case 256:
    {
      /* Swap the selected state of the item */

      item->flags ^= HOTLIST_G_IS_SELECTED;
      item->flags |= HOTLIST_G_REDRAW_NOW;

      last_selected_item = itemno;

      /* If a directory, select or deselect the contents */

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

      /* Redraw to reflect the selection */

      e = hotlist_redraw_now();
    }
    break;
  }

  /* Return any errors that may have been generated */

  return e;
}

/*************************************************/
/* hotlist_process_click()                       */
/*                                               */
/* This function deals with mouse clicks on the  */
/* hotlist window.                               */
/*                                               */
/* Parameters: X position of click, in window    */
/*             coords;                           */
/*                                               */
/*             Y position of click, in window    */
/*             coords;                           */
/*                                               */
/*             Button state (as returned by      */
/*             Wimp_GetPointerInfo).             */
/*************************************************/

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

  /* Get information on what was clicked upon */

  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) RetError(hotlist_get_shape(&xmin, &xmax, item));

  if (item && x >= xmin && x <= xmax)
  {
    /* If we have a specific item, process the click on it */

    return hotlist_process_click_on_item(itemno, item, buttons, x, y);
  }
  else
  {
    /* Otherwise, actions depend on the button type */

    switch(buttons)
    {
      /* Drag with Select */

      case 64:
      {
        RetError(hotlist_clear_selection());

        e = hotlist_selection_box_start();
      }
      break;

      /* Drag with Adjust */

      case 16:
      {
        e = hotlist_selection_box_start();
      }
      break;

      /* Click with Select */

      case 1024:
      {
        e = hotlist_clear_selection();

        last_selected_item = 0xffffffff;
      }
      break;

      /* Click with Adjust */

      case 256:
      {
        last_selected_item = 0xffffffff;
      }
      break;
    }
  }

  return e;
}

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

  #define HotlistPreopen_ShowError(e) {if(e){show_error_ret(e);return 0;}}

  /* Is the window open or closed? */

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

  /* Get entry sizes */

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

  /* General information */

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

  /* Find out window details */

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

  e = wimp_get_window_state(&state);
  HotlistPreopen_ShowError(e);

  /* Sanity check... */

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

  /* If the window is open, get its extent */

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

  /* Work out the y extent and x extent required for showing */
  /* as much of the visible items in the hotlist as possible */

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

  /* If the window was already open, don't let the visible */
  /* area change - otherwise go for a best fit to the      */
  /* items, as worked out above.                           */

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

  /* Account for a button bar, if one were present. */

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

  /* Set the extent */

  e = window_set_extent(0, hotlist_windowid, &bbox);
  HotlistPreopen_ShowError(e);

  /* (Re)open the window */

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

    HotlistPreopen_ShowError(e);
  }

  return !!(objectstate & Toolbox_GetObjectState_Showing);
}

/*************************************************/
/* hotlist_redraw_handler()                      */
/*                                               */
/* This handles redraw events from the Wimp, for */
/* the hotlist window.                           */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

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

  /* Get entry sizes */

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

  block.window_handle = event->redraw_window_request.window_handle;

  /* Start the redraw loop */

  wimp_redraw_window(&block, &more);

  while (more && !e)
  {
    /* Work out which bits to redraw... */

    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;

    /* ...and redraw them */

    e = hotlist_draw(hotlist_root->data.directory_content,
                     first_item,
                     last_item);

    /* Get the next redraw rectangle */

    if (!e) e = wimp_get_rectangle(&block, &more);
  }

  return 1;
}

/*************************************************/
/* hotlist_mouse_click_handler()                 */
/*                                               */
/* Event handler to deal with mouse clicks in    */
/* the hotlist window; converts coordinates from */
/* screen to window, and runs the result through */
/* hotlist_process_click.                        */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

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

  ChkError(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 1;
}

/*************************************************/
/* hotlist_menuclose_handler()                   */
/*                                               */
/* Called when menus close.                      */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int hotlist_menuclose_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  ObjectId submenu_id;

  /* Deselect any items selected by the menu */

  if (menu_select)
  {
    hotlist_clear_selection();
    menu_select = 0;
  }

  /* Get rid of any submenus */

  ChkError(menu_get_sub_menu_show(0, id_block->self_id, HOTLIST_URL_MENUITEM, &submenu_id));

  if (submenu_id) ChkError(toolbox_delete_object(0, submenu_id));

  ChkError(menu_set_sub_menu_show(0, id_block->self_id, HOTLIST_URL_MENUITEM, 0));

  return 1;
}

/*************************************************/
/* hotlist_set_menu_details()                    */
/*                                               */
/* When a menu is to be opened or the hotlist    */
/* state changes in a way that can affect open   */
/* menus, this can be used to update the menu    */
/* contents.                                     */
/*                                               */
/* Parameters: Object ID of the hotlist root     */
/*             menu.                             */
/*************************************************/

static _kernel_oserror * hotlist_set_menu_details(ObjectId menuid)
{
  hotlist_item * item;
  char           entrytext[Limits_Hotlist_ItemName];
  ObjectId       submenu_id;

  switch (hotlist_count_selected_items())
  {
    /* No selected items - set the URL/Directory name string to */
    /* "URL ''" and grey it out, along with the Clear Selection */
    /* entry.                                                   */

    case 0:
    {
      RetError(menu_set_entry_text(0, menuid, HOTLIST_URL_MENUITEM, "URL ''"));

      RetError(menu_set_fade(0, menuid, HOTLIST_URL_MENUITEM, 1));
      RetError(menu_set_fade(0, menuid, HOTLIST_CLEARSELECTION_MENUITEM, 1));
    }
    break;

    /* Only 1 item selected */

    case 1:
    {
      int name_len;

      item = hotlist_find_selected_item();

      switch(item->type)
      {
        /* One directory selected - set the URL/Directory name */
        /* string to a relevant value, and create and attach   */
        /* the relevant submenu.                               */

        case hl_directory:
        {
          StrNCpy0(entrytext, "Dir. '");

          RetError(toolbox_create_object(0, "HLDirmenu", &submenu_id));
          RetError(menu_set_sub_menu_show(0, menuid, HOTLIST_URL_MENUITEM, submenu_id));

          /* If the item is read-only, grey out the 'delete' entry */

          if (item->flags & HOTLIST_G_IS_READ_ONLY) menu_set_fade(0, submenu_id, HOTLIST_DELETE_SUBMENUITEM, 1);
          else                                      menu_set_fade(0, submenu_id, HOTLIST_DELETE_SUBMENUITEM, 0);
        }
        break;

        /* Similarly, deal with a single URL being selected */

        case hl_url:
        {
          StrNCpy0(entrytext, "URL '");

          RetError(toolbox_create_object(0, "HLURLmenu", &submenu_id));
          RetError(menu_set_sub_menu_show(0, menuid, HOTLIST_URL_MENUITEM, submenu_id));

          if (item->flags & HOTLIST_G_IS_READ_ONLY) menu_set_fade(0, submenu_id, HOTLIST_DELETE_SUBMENUITEM, 1);
          else                                      menu_set_fade(0, submenu_id, HOTLIST_DELETE_SUBMENUITEM, 0);
        }
        break;
      }

      name_len = strlen(item->name);

      /* Add the item name in. If the whole item will not fit, put as much as */
      /* will do, followed by '...'.                                          */

      if (name_len < sizeof(entrytext) - strlen(entrytext) - 1) strcat(entrytext, item->name); /* ('<' accounts for terminator, -1 accounts for closing "'") */
      else if (sizeof(entrytext) - strlen(entrytext) > 5)
      {
        strncat(entrytext, item->name, sizeof(entrytext) - strlen(entrytext) - 5);             /* (-5 accounts for closing "'", "..." and terminator)        */
        strcat(entrytext, "...");
      }

      if (strlen(entrytext) < sizeof(entrytext) - 1) strcat(entrytext, "'");

      /* Set the text, unfade the item, and unfade the Clear Selection item */

      RetError(menu_set_entry_text(0, menuid, HOTLIST_URL_MENUITEM, entrytext));

      RetError(menu_set_fade(0, menuid, HOTLIST_URL_MENUITEM, 0));
      RetError(menu_set_fade(0, menuid, HOTLIST_CLEARSELECTION_MENUITEM, 0));
    }
    break;

    /* Many items selected */

    default:
    {
      /* Set the URL/Directory name string, ungrey that item and Clear Selection, */
      /* and create and attach an appropriate submenu.                            */

      RetError(menu_set_entry_text(0, menuid, HOTLIST_URL_MENUITEM, "Selection"));

      RetError(menu_set_fade(0, menuid, HOTLIST_URL_MENUITEM, 0));
      RetError(menu_set_fade(0, menuid, HOTLIST_CLEARSELECTION_MENUITEM, 0));

      RetError(toolbox_create_object(0, "HLSlctmenu", &submenu_id));
      RetError(menu_set_sub_menu_show(0, menuid, HOTLIST_URL_MENUITEM, submenu_id));
    }
    break;

  }

  /* If *everything* is selected, want to grey out Select All; */
  /* else ungrey it.                                           */

  if (hotlist_contents_selected(hotlist_root)) RetError(menu_set_fade(0, menuid, HOTLIST_SELECTALL_MENUITEM, 1))
  else                                         RetError(menu_set_fade(0, menuid, HOTLIST_SELECTALL_MENUITEM, 0))

  /* Set the ticks on 'Show Descriptions' or 'Show URLs' in the */
  /* Display submenu.                                           */

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

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

  /* Finished */

  return NULL;
}

/*************************************************/
/* hotlist_menuopen_handler()                    */
/*                                               */
/* Handles events raised when menus are about to */
/* be shown.                                     */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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;
  int                       xmin, xmax, window_handle;
  ObjectId                  sub_menu;

  /* Get entry sizes. */

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

  if (event_code == EHotlistToBeShown)
  {
    /* Work out which item the pointer was over. */

    ChkError(window_get_wimp_handle(0, hotlist_windowid, &window_handle));

    state.window_handle = window_handle;
    ChkError(wimp_get_window_state(&state));

    ChkError(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;
  }

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

  /* If there are no selected items, select the one the menu */
  /* was opened over.                                        */

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

    if (item) ChkError(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;

      ChkError(hotlist_redraw_now());
    }
  }

  /* Update the menus */

  ChkError(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, */
/*             but in both cases obey the next   */
/*             parameter;                        */
/*                                               */
/*             0 - don't save read-only items,   */
/*             1 - allow saving of read-only     */
/*             items.                            */
/*                                               */
/* 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 save_read_only)
{
  int written;

  /* Follow the directory list */

  while (list)
  {
    /* Don't save read-only entries unless told to */

    if (save_read_only || !(list->flags & HOTLIST_G_IS_READ_ONLY))
    {
      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));

              if (list->flags & HOTLIST_D_IS_OPEN)
              {
                HotlistWrite(fprintf(fileptr, "<ul><!--open-->\n"));
              }
              else
              {
                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, save_read_only));
            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;

  /* Error condition exit */

hotlist_save_error:

  RetLastE;
}

/*************************************************/
/* hotlist_save_hotlist()                        */
/*                                               */
/* This function saves the hotlist as an HTML    */
/* file.                                         */
/*                                               */
/* Parameters: Pointer to the filename to save   */
/*             to (null terminated);             */
/*                                               */
/*             Pointer to a buffer containing    */
/*             any extra information to put in   */
/*             the top of the file (intended for */
/*             multiuser builds);                */
/*                                               */
/*             0 to save all of hotlist, 1 to    */
/*             save only the selected portions.  */
/*************************************************/

_kernel_oserror * hotlist_save_hotlist(char * filename, char * extradata, int type)
{
  _kernel_oserror * e;
  FILE            * fileptr;
  static char     * local_path = NULL;
  int               written;

  if (!filename || !*filename) return NULL;

  /* Canonicalise the path */

  RetError(utils_canonicalise_path(filename, &local_path));

  /* Ensure it is present */

  e = utils_build_tree(local_path);

  if (e)
  {
    free(local_path);
    return e;
  }

  /* Could take a while... */

  _swix(Hourglass_On, 0);

  /* Open the file for writng */

  fileptr = fopen(local_path, "wb");

  /* Complain if it fails */

  if (fileptr == NULL)
  {
    free(local_path);
    RetLastE;
  }

  /* Write the extra data */

  if (extradata) HotlistWrite(fprintf(fileptr, "%s", extradata));

  /* Write the file header */

  HotlistWrite(fprintf(fileptr, "<html>\n"
                                "<head>\n"
                                CHARSET_SPECIFIER
                                "<title>"));

  HotlistWrite(fprintf(fileptr, "%s", lookup_token("HotlistHTMLTitle:Hotlist",0,0)));

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

  /* Fill in the body */

  e = hotlist_save_entries(fileptr,
                           hotlist_root->data.directory_content,
                           type,
                           type == 1 ? 1 : 0); /* If saving part of the hotlist, allow saving of read-only components */

  if (e)
  {
    _swix(Hourglass_Off, 0);

    fclose(fileptr);
    free(local_path);

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

  _swix(Hourglass_Off, 0);

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

  e = _swix(OS_File,
            _INR(0,2),

            18,
            local_path,
            FileType_HTML);

  free(local_path);

  return e;

  /* Error condition exit */

hotlist_save_error:

  if (fileptr)
  {
    fclose(fileptr);

    _swix(Hourglass_Off, 0);
  }

  free(local_path);

  RetLastE;
}

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

_kernel_oserror * hotlist_save(char * filename)
{
  return hotlist_save_hotlist(filename, NULL, 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;
  int               target_type;

  /* Go through the file in chunks */

  target_type = 0; /* At end of directory */

  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))
    {
      erb = *_kernel_last_oserror();
      e   = &erb;

      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)
    {
      erb = *_kernel_last_oserror();
      e   = &erb;

      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)
    {
      erb = *_kernel_last_oserror();
      e   = &erb;

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

      /* Get ready to add this item, provided that the URL string has */
      /* ':/' in - i.e. looks fully specified                        */

      if (strstr(url, ":/"))
      {
        if (target_type == 0)
        {
          e = hotlist_new_url(target, HOTLIST_POSITION_END, str_ptr, url);
          target_type = 1;
        }
        else
        {
          e = hotlist_new_url(target, HOTLIST_POSITION_AFTER, str_ptr, url);
        }
        if (e) goto hotlist_load_directory_exit; /* (See near end of function) */

        target = hotlist_newitem;
      }
    }

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

        if (target_type == 0)
        {
          e = hotlist_new_directory(target, next_directory_name, HOTLIST_POSITION_END, &new_dir);
          target_type = 1;
        }
        else
        {
          e = hotlist_new_directory(target, next_directory_name, HOTLIST_POSITION_AFTER, &new_dir);
        }
        if (e) goto hotlist_load_directory_exit;

        target = hotlist_newitem;

        if (strstr(string_buffer, "<!--open-->") != NULL)
        {
          new_dir->flags |= HOTLIST_D_IS_OPEN;
        }

        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_discard()                             */
/*                                               */
/* Empties the hotlist. The hotlist window is    */
/* not updated (expected usage is to close the   */
/* window shotly before or after discarding the  */
/* contents).                                    */
/*************************************************/

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

/*************************************************/
/* 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;
  static char     * local_path = NULL;
  FILE            * fileptr;

  if (!filename || !*filename) return NULL;

  local_path = malloc(strlen(filename) + 1);
  if (!local_path) return NULL;

  strcpy(local_path, filename);

  /* First delete all the existing hotlist items */

  hotlist_discard();

  /* Open the file */

  fileptr = fopen(local_path, "r");

  if (fileptr == NULL)
  {
    free(local_path);

    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) e = hotlist_modified(HL_MODIFIED_LOAD);

  free(local_path);

  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)
{
  ObjectId     toolbar;
  ObjectId     menu_id;
  BBox         bbox;
  unsigned int item_height, item_dir_width, item_url_width;
  char         root[] = "Root";

  /* Set some initial flags */

  hotlist_dragging.drag_type    = HOTLIST_NOT_DRAGGING;
  hotlist_dragging.using_adjust = 0;

  /* 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,
                                          EHotlistToBeShown,
                                          hotlist_menuopen_handler,
                                          NULL));

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

  /* Main menu items */

  RetError(event_register_toolbox_handler(-1,
                                          EHotlistSelectAll,
                                          hotlist_select_all_handler,
                                          NULL));

  RetError(event_register_toolbox_handler(-1,
                                          EHotlistClearSelect,
                                          hotlist_clear_selection_handler,
                                          NULL));

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

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

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

  RetError(event_register_toolbox_handler(-1,
                                          EHotlistSaveToServer,
                                          hotlist_save_to_server_handler, /* a.k.a. Save As Default */
                                          NULL));

  /* Submenu warning events */

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

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

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

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

  /* Hotlist related dialogue events */

  RetError(event_register_toolbox_handler(-1,
                                          EHotlistClose,
                                          hotlist_close_handler,
                                          NULL));

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

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

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

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

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

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

  RetError(event_register_toolbox_handler(-1,
                                          EHotlistCancelDrag,
                                          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)
{
  if (hotlist_windowid) return toolbox_hide_object(0, hotlist_windowid);
  else                  return NULL;
}

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

static int hotlist_close_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  ChkError(hotlist_close());

  return 1;
}

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

static int hotlist_select_all_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  ObjectId main_menu;

  menu_select = 0;

  /* Select everything */

  hotlist_set_flags(hotlist_root, hl_ALL, HOTLIST_G_IS_SELECTED);
  hotlist_root->flags &= ~HOTLIST_G_IS_SELECTED;

  hotlist_redraw_now();

  /* Get the main menu ID */

  ChkError(window_get_menu(0, hotlist_windowid, &main_menu));

  /* Update the main menu contents to reflect the new state */

  hotlist_set_menu_details(main_menu);

  return 1;
}

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

static int hotlist_clear_selection_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  ObjectId main_menu;

  menu_select = 0;

  /* Get the main menu ID */

  ChkError(window_get_menu(0, hotlist_windowid, &main_menu));

  /* Clear the selection */

  hotlist_clear_selection();

  /* Update the main menu */

  hotlist_set_menu_details(main_menu);

  return 1;
}

/*************************************************/
/* hotlist_menu_openall_handler()                */
/*                                               */
/* Deal with the Open All menu item.             */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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;

    /* Set all Open flags, clear all Redraw Now flags, and force a redraw of everything */

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

    ChkError(hotlist_redraw_items(0, hotlist_count_displayed_items(hotlist_root->data.directory_content)));

    hotlist_preopen();
  }

  return 1;
}

/*************************************************/
/* hotlist_menu_closeall_handler()               */
/*                                               */
/* Deal with the Close All menu item.            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int hotlist_menu_closeall_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  unsigned int noitems;
  ObjectId     main_menu;

  if (hotlist_root->data.directory_content)
  {
    menu_itemno = 0;

    /* Clear all Open and Redraw Now flags, and deselect all items */

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

    ChkError(hotlist_clear_selection());
    ChkError(hotlist_redraw_items(0, noitems));

    /* Get the main menu ID */

    ChkError(window_get_menu(0, hotlist_windowid, &main_menu));

    /* Update the menus to reflect there's nothing selected */

    ChkError(hotlist_set_menu_details(main_menu));

    hotlist_preopen();
  }

  return 1;
}

/*************************************************/
/* hotlist_menu_delete_handler()                 */
/*                                               */
/* Deal with the Delete menu item.               */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  if (!id_block->ancestor_id) hotlist_window = id_block->self_id;
  else                        hotlist_window = id_block->ancestor_id;

  /* If there are no selected items, can't do anything */

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

  if (!hotlist_count_selected_items()) return 0;

  /* Delete all selected items */

  while ((item = hotlist_find_selected_item()) != NULL)
  {
    if (item->flags & HOTLIST_G_IS_READ_ONLY)
    {
      /* Just clear selection on read only items */

      item->flags &= ~HOTLIST_G_IS_SELECTED;
    }
    else
    {
      /* Delete read/write items */

      deleted ++;

      hotlist_delete_item(item);
    }
  }

  /* Redraw */

  hotlist_redraw_items(0, noitems);

  /* We may have no more work to do if all of the selected items */
  /* were read-only; otherwise, 'deleted' will be non-zero.      */

  if (deleted)
  {
    /* Update the window extent */

    hotlist_preopen();

    /* The hotlist has been modified... */

    hotlist_modified(HL_MODIFIED_DELETE);
  }

  /* Finished */

  return 1;
}

/*************************************************/
/* hotlist_save_to_server_handler()              */
/*                                               */
/* Deal with the 'Save to server' menu item      */
/* (also known as 'Save as default').            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int hotlist_save_to_server_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  #ifdef SINGLE_USER

    show_error_ret(hotlist_save(lookup_choice("HotlistSave:Browse:User.Hotlist",0,0)));

  #else

    show_error_ret(multiuser_save_hotlist());

  #endif

  return 1;
}

/*************************************************/
/* hotlist_show_newurl_handler()                 */
/*                                               */
/* This function fills in the edit / create URL  */
/* dialogue to be a New URL object.              */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_show_newurl_handler: Called\n");
  #endif

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

  alter_new = HOTLIST_MENUSECTION_NEW;

  /* Set the title, clear the description writable and URL writable, */
  /* and set the default action button text. 'Cancel' stays as       */
  /* 'Cancel'.                                                       */

  ChkError(window_set_title(0, dboxid, lookup_token("HotlistCreateURLTitle:Create new URL",0,0)));

  ChkError(writablefield_set_value(0, dboxid, HOTLIST_NEWURL_NAME, ""));
  ChkError(writablefield_set_value(0, dboxid, HOTLIST_NEWURL_URL,  ""));

  ChkError(actionbutton_set_text  (0, dboxid, HOTLIST_NEWURL_NEW,  lookup_token("HotlistCreateURLAction:Create",0,0)));

  return 1;
}

/*************************************************/
/* hotlist_show_editurl_handler()                */
/*                                               */
/* This function fills in the edit / create URL  */
/* dialogue to be an Edit URL object.            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_show_editurl_handler: Called\n");
  #endif

  /* Must have a selected item... */

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

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

  alter_new = HOTLIST_MENUSECTION_ALTER;

  /* Set the title, description writable, URL writable, and  */
  /* default action button text. 'Cancel' stays as 'Cancel'. */

  ChkError(window_set_title(0, dboxid, lookup_token("HotlistEditURLTitle:Edit URL",0,0)));

  ChkError(writablefield_set_value(0, dboxid, HOTLIST_NEWURL_NAME, item->name));
  ChkError(writablefield_set_value(0, dboxid, HOTLIST_NEWURL_URL,  item->data.url));

  ChkError(actionbutton_set_text  (0, dboxid, HOTLIST_NEWURL_NEW,  lookup_token("HotlistEditURLAction:Alter",0,0)));

  /* If the item's flags show it is read-only, it can't be altered */

  if (item->flags & HOTLIST_G_IS_READ_ONLY) set_gadget_state(dboxid, HOTLIST_NEWURL_NEW, 1);
  else                                      set_gadget_state(dboxid, HOTLIST_NEWURL_NEW, 0);

  return 1;
}

/*************************************************/
/* hotlist_show_newdirectory_handler()           */
/*                                               */
/* This function fills in the edit / create      */
/* directory dialogue to be a New Directory      */
/* object.                                       */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_show_newdirectory_handler: Called\n");
  #endif

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

  alter_new = HOTLIST_MENUSECTION_NEW;

  /* Set the title, clear the writable contents, and set */
  /* the default action button text. 'Cancel' stays as   */
  /* 'Cancel'.                                           */

  ChkError(window_set_title(0, dboxid, lookup_token("HotlistCreateDirTitle:Create new directory",0,0)));

  ChkError(writablefield_set_value(0, dboxid, HOTLIST_NEWDIRECTORY_NAME, ""));
  ChkError(actionbutton_set_text  (0, dboxid, HOTLIST_NEWDIRECTORY_NEW,  lookup_token("HotlistCreateDirAction:Create",0,0)));

  return 1;
}

/*************************************************/
/* hotlist_show_rendirectory_handler()           */
/*                                               */
/* This function fills in the edit / create      */
/* directory dialogue to be a Rename Directory   */
/* object.                                       */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_show_rendirectory_handler: Called\n");
  #endif

  /* Must have a selected item... */

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

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

  alter_new = HOTLIST_MENUSECTION_ALTER;

  /* Set the title, writable contents, and default action */
  /* button text. 'Cancel' stays as 'Cancel'.             */

  ChkError(window_set_title(0, dboxid, lookup_token("HotlistRenameDirTitle:Rename directory",0,0)));

  ChkError(writablefield_set_value(0, dboxid, HOTLIST_NEWDIRECTORY_NAME, item->name));
  ChkError(actionbutton_set_text  (0, dboxid, HOTLIST_NEWDIRECTORY_NEW,  lookup_token("HotlistRenameDirAction:Rename",0,0)));

  /* If the item's flags show it is read-only, it can't be renamed */

  if (item->flags & HOTLIST_G_IS_READ_ONLY) set_gadget_state(dboxid, HOTLIST_NEWDIRECTORY_NEW, 1);
  else                                      set_gadget_state(dboxid, HOTLIST_NEWDIRECTORY_NEW, 0);

  return 1;
}

/*************************************************/
/* hotlist_newedit_url_handler()                 */
/*                                               */
/* This function either alters the name and URL  */
/* fields of a selected URL, or creates a new    */
/* URL item in the directory that the pointer    */
/* was over when the main menu was opened.       */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  /* Is there a selected item? */

  item = hotlist_find_selected_item();

  /* Get the length of the description (name) text */

  ChkError(writablefield_get_value(0,
                                   id_block->self_id,
                                   HOTLIST_NEWURL_NAME,
                                   NULL,
                                   0,
                                   &size));

  /* Try to allocate space for it */

  tempdesc = malloc(size + 1); /* '+1' to be safe (above call *should* return 'size of buffer required'...) */

  if (!tempdesc)
  {
    show_error_cont(make_no_memory_error(15));
    return 1;
  }

  /* Get the value, making absolutely sure it's terminated */

  ChkError(writablefield_get_value(0,
                                   id_block->self_id,
                                   HOTLIST_NEWURL_NAME,
                                   tempdesc,
                                   size,
                                   NULL));
  tempdesc[size] = 0;

  /* Do the same for the URL */

  ChkError(writablefield_get_value(0,
                                   id_block->self_id,
                                   HOTLIST_NEWURL_URL,
                                   NULL,
                                   0,
                                   &size));
  tempurl = malloc(size + 1);

  if (!tempurl)
  {
    free(tempdesc);

    show_error_cont(make_no_memory_error(15));
    return 1;
  }

  ChkError(writablefield_get_value(0,
                                   id_block->self_id,
                                   HOTLIST_NEWURL_URL,
                                   tempurl,
                                   size,
                                   NULL));
  tempurl[size] = 0;

  /* 'alter_new' is set by the menu handling functions to */
  /* reflect whether we should create or alter items.     */

  switch (alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    {
      /* Create a new item. First, find the one the menu was */
      /* opened over.                                        */

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

      /* If we can't, add to the start of the root directory */

      if (!tempitem)
      {
        menu_itemno = 0;

        e = hotlist_new_url(hotlist_root,
                            HOTLIST_POSITION_BEGINNING,
                            tempdesc,
                            tempurl);
      }

      /* Otherwise add it after the item, unless the parent is read-only */

      else
      {
        if (tempitem->parent && (tempitem->parent->flags & HOTLIST_G_IS_READ_ONLY))
        {
          erb.errnum = Utils_Error_Custom_Message;

          StrNCpy0(erb.errmess,
                   lookup_token("HLNewRO:You can't create an item here (the directory is read-only).",
                                0,0));

          e = &erb;
        }
        else
        {
          e = hotlist_new_url(tempitem,
                              HOTLIST_POSITION_AFTER,
                              tempdesc,
                              tempurl);
        }
      }

      if (!e)
      {
        /* Ensure the window extent is up to date */

        hotlist_preopen();

        /* Deal with redraw issues */

        e = 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);

        /* Call the 'hotlist has changed' function */

        hotlist_modified(HL_MODIFIED_ADD);
      }

      /* Free temporary data and report any errors */

      free(tempdesc);
      free(tempurl);

      ChkError(e);
    }
    break;

    case HOTLIST_MENUSECTION_ALTER:
    {
      /* Alter an existing item. If it is read-only, complain; */
      /* otherwise, free its URL and description text.         */

      if (item->flags & HOTLIST_G_IS_READ_ONLY)
      {
        erb.errnum = Utils_Error_Custom_Message;

        StrNCpy0(erb.errmess,
                 lookup_token("NLAlterRO:You can't alter this item (it is read-only).",
                              0,0));

        e = &erb;

        free(tempdesc);
        free(tempurl);
      }
      else
      {
        free(item->name);
        free(item->data.url);

        /* Point to the new URL and description */

        item->name     = tempdesc;
        item->data.url = tempurl;

        /* Deal with redraw issues */

        item->flags |= HOTLIST_G_REDRAW_NOW;

        hotlist_preopen();

        e = hotlist_redraw_now();

        /* Call the 'hotlist has changed' function */

        hotlist_modified(HL_MODIFIED_ALTER);
      }

      /* Report any errors */

      ChkError(e);
    }
    break;
  }

  return 1;
}

/*************************************************/
/* hotlist_newren_directory_handler()            */
/*                                               */
/* As hotlist_newedit_url_handler, but for       */
/* directory items.                              */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

  /* This bit is similar to the start of hotlist_newedit_url_handler */

  item = hotlist_find_selected_item();

  ChkError(writablefield_get_value(0,
                                   id_block->self_id,
                                   HOTLIST_NEWDIRECTORY_NAME,
                                   NULL,
                                   0,
                                   &size));

  tempname = malloc(size + 1);

  if (!tempname)
  {
    show_error_cont(make_no_memory_error(16));
    return 1;
  }

  ChkError(writablefield_get_value(0,
                                   id_block->self_id,
                                   HOTLIST_NEWDIRECTORY_NAME,
                                   tempname,
                                   size,
                                   NULL));

  switch (alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    {
      tempitem = hotlist_find_item(hotlist_root->data.directory_content, menu_itemno);

      /* If we can't find the item the pointer was near, at at the */
      /* start of the root; else add near the item.                */

      if (!tempitem)
      {
        menu_itemno = 0;

        e = hotlist_new_directory(hotlist_root,
                                  tempname,
                                  HOTLIST_POSITION_END,
                                  &tempitem);
      }
      else
      {
        /* The parent may be read-only */

        if (tempitem->parent && (tempitem->parent->flags & HOTLIST_G_IS_READ_ONLY))
        {
          erb.errnum = Utils_Error_Custom_Message;

          StrNCpy0(erb.errmess,
                   lookup_token("HLNewRO:You can't create an item here (the directory is read-only).",
                                0,0));

          e = &erb;
        }
        else
        {
          e = hotlist_new_directory(tempitem,
                                    tempname,
                                    HOTLIST_POSITION_AFTER,
                                    &tempitem);
        }
      }

      if (!e)
      {
        hotlist_preopen();

        e = 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 the temporary block and report any errors */

      free(tempname);

      ChkError(e);
    }
    break;

    case HOTLIST_MENUSECTION_ALTER:
    {
      /* Alter an existing directory. If it is read-only, complain. */

      if (item->flags & HOTLIST_G_IS_READ_ONLY)
      {
        erb.errnum = Utils_Error_Custom_Message;

        StrNCpy0(erb.errmess,
                 lookup_token("NLAlterRO:You can't alter this item (it is read-only).",
                              0,0));

        e = &erb;

        free(tempname);
      }
      else
      {
        /* Free the item's existing name, link to the new */
        /* name, and do the relevant redraws / hotlist    */
        /* modified calls.                                */

        free(item->name);

        item->name = tempname;
        item->flags |= HOTLIST_G_REDRAW_NOW;

        hotlist_preopen();

        e = hotlist_redraw_now();

        hotlist_modified(HL_MODIFIED_ALTER);
      }

      /* Report any errors */

      ChkError(e);
    }
    break;
  }

  return 1;
}

/*************************************************/
/* hotlist_reset_url_handler()                   */
/*                                               */
/* This function is called when Cancel is        */
/* clicked on in the New (aka Edit) URL�dialogue */
/* box. It resets the contents to their previous */
/* state.                                        */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int hotlist_reset_url_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  /* Action depends on whether we were altering, or creating an item */

  switch (alter_new)
  {
    case HOTLIST_MENUSECTION_NEW:
    {
      ChkError(writablefield_set_value(0, id_block->self_id, HOTLIST_NEWURL_NAME, ""));
      ChkError(writablefield_set_value(0, id_block->self_id, HOTLIST_NEWURL_URL,  ""));
    }
    break;

    case HOTLIST_MENUSECTION_ALTER:
    {
      hotlist_item * item = hotlist_find_selected_item();

      if (item)
      {
        ChkError(writablefield_set_value(0, id_block->self_id, HOTLIST_NEWURL_NAME, item->name));
        ChkError(writablefield_set_value(0, id_block->self_id, HOTLIST_NEWURL_URL,  item->data.url));
      }
    }
    break;
  }

  return 1;
}

/*************************************************/
/* hotlist_reset_directory_handler()             */
/*                                               */
/* Similar to hotlist_reset_url_handler, but for */
/* resetting the New (aka Rename) Directory      */
/* dialogue.                                     */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

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

    case HOTLIST_MENUSECTION_ALTER:
    {
      hotlist_item * item = hotlist_find_selected_item();

      if (item) ChkError(writablefield_set_value(0,
                                                 id_block->self_id,
                                                 HOTLIST_NEWDIRECTORY_NAME,
                                                 item->name));
    }
    break;
  }

  return 1;
}

/*************************************************/
/* hotlist_drag_renderer()                       */
/*                                               */
/* Renders a single hotlist item at 0,0 for use  */
/* with DragAnObject.                            */
/*                                               */
/* Parameters: Pointer to the item to render;    */
/*                                               */
/*             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.                         */
/*************************************************/

static void hotlist_drag_renderer(hotlist_item * item, unsigned int item_height,
                                  unsigned int item_dir_width, unsigned int item_url_width)
{
  /* DON'T put Printf's in here... Doesn't work, at least, */
  /* not with PipeFS output methods.                       */

  WimpIconBlock hotlist_iconblock;
  int           temp_width;
  int           text_width;

  /* Create an icon block for the sprite component */

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

  /* Set appropriate sprite and width of sprite */

  switch(item->type)
  {
    case hl_url:
    {
      hotlist_iconblock.data.is.sprite             = !(item->flags & HOTLIST_G_IS_READ_ONLY) ? URL_SPRITE : RESOURCES_URL_SPRITE;
      hotlist_iconblock.data.is.sprite_name_length = strlen(hotlist_iconblock.data.is.sprite);

      temp_width = item_url_width;
    }
    break;

    case hl_directory:
    {
      hotlist_iconblock.data.is.sprite             = !(item->flags & HOTLIST_G_IS_READ_ONLY) ? CLOSED_DIRECTORY_SPRITE : CLOSED_RESOURCES_DIRECTORY_SPRITE;
      hotlist_iconblock.data.is.sprite_name_length = strlen(hotlist_iconblock.data.is.sprite);

      temp_width = item_dir_width;
    }
    break;

    default:
    {
      temp_width = 0;
    }
    break;
  }

  /* Set the bounding box */

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

  /* Plot the item */

  if (wimp_plot_icon(&hotlist_iconblock)) return; /* Bail out on error! */

  /* Create an icon block for the text component */

  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;

  /* Set the bounding box */

  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;

  /* Set the text according to the item type */

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

  /* Plot the icon */

  wimp_plot_icon(&hotlist_iconblock);

  /* Finished */

  return;
}

/*************************************************/
/* hotlist_start_drag()                          */
/*                                               */
/* Start a new drag operation inside the hotlist */
/* window.                                       */
/*************************************************/

static _kernel_oserror * hotlist_start_drag(void)
{
  /* Do the hard work elsewhere... */

  RetError(hotlist_start_drag_backend());

  /* Reset autoscroll */

  RetError(hotlist_autoscroll(0));

  /* Register NULL handler */

  register_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);

  /* Finished */

  return NULL;
}

/*************************************************/
/* hotlist_start_drag_backend()                  */
/*                                               */
/* This function is called by hotlist_start_drag */
/* to do most of the work for starting a drag    */
/* operation from within the hotlist window. A   */
/* drag box is created, bounding all selected    */
/* items.                                        */
/*************************************************/

static _kernel_oserror * hotlist_start_drag_backend(void)
{
  WimpGetWindowStateBlock   state;
  WimpGetPointerInfoBlock   pointerblock;
  WimpDragBox               box;
  hotlist_item            * item;
  unsigned int              item_height, item_dir_width, item_url_width;
  BBox                      bbox;
  int                       xorigin,     yorigin;
  int                       screenwidth, screenheight;

  /* Read the pointer position and get the hotlist item sizes */

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

  /* Record whether ot not Adjust (and only Adjust) was used */

  hotlist_dragging.using_adjust = (pointerblock.button_state == Wimp_MouseButtonAdjust);

  /* Get the shape of the selected items */

  hotlist_get_selected_shape(&bbox);

  /* Now get information on the hotlist window itself, */
  /* and the current screen shape.                     */

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

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

  /* Screen size, pixels */

  screenwidth  = bbc_modevar(-1, BBC_XWindLimit);
  screenheight = bbc_modevar(-1, BBC_YWindLimit);

  /* Convert to OS units */

  screenwidth  = (screenwidth  + 1) << (bbc_modevar(-1, BBC_XEigFactor));
  screenheight = (screenheight + 1) << (bbc_modevar(-1, BBC_YEigFactor));

  /* Read CMOS - solid drags, or a dashed outline? */

  if (_kernel_osbyte(161, 28, 0) & (1 << (8 + 1)))
  {
    _kernel_swi_regs regs;
    int              redraw_params[4];

    /* Solid drags */

    item = hotlist_find_selected_item();

    /* A single item, or several? */

    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
           )
         )
       )
    {
      /* A single item. Set up the box shape */

      box.dragging_box.xmin = bbox.xmin - xorigin;
      box.dragging_box.ymin = bbox.ymin - yorigin;
      box.dragging_box.xmax = bbox.xmax - xorigin;
      box.dragging_box.ymax = bbox.ymax - yorigin;

      /* Store details of the redraw */

      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;

      /* Fill in the register block */

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

      /* Start the drag - use DragAnObject, as then we can show */
      /* the item's text, as seen in the hotlist window, during */
      /* the drag.                                              */

      RetError(_kernel_swi(DragAnObject_Start, &regs, &regs));

      /* Finished; flag that we're solid-dragging an object and exit */

      hotlist_dragging.drag_type = HOTLIST_SOLID_DRAG_OBJECT;

      return NULL;
    }
    else
    {
      int width, height;

      /* Several items. */

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

      /* Set the box shape, based around the mouse pointer coordinates  */

      box.dragging_box.xmin = pointerblock.x - (width  / 2 + 10); /* '+ 10' = aesthetics */
      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);

      /* Fill in the register block */

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

      /* Start the drag - use DragASprite as there's no text to */
      /* show with the 'package' icon.                          */

      RetError(_kernel_swi(DragASprite_Start, &regs, &regs));

      /* Finished; flag that we're solid-dragging an object and exit */

      hotlist_dragging.drag_type = HOTLIST_SOLID_DRAG_SPRITE;

      return NULL;
    }
  }

  /* CMOS said - use a dashed outline drag. */

  box.drag_type         = Wimp_DragBox_DragFixedDash;

  /* Set the box shape */

  box.dragging_box.xmin = bbox.xmin - xorigin;
  box.dragging_box.xmax = bbox.xmax - xorigin;
  box.dragging_box.ymin = bbox.ymin - yorigin;
  box.dragging_box.ymax = bbox.ymax - yorigin;

  /* Constrain the drag to the screen size */

  box.parent_box.xmin   = box.dragging_box.xmin - pointerblock.x;
  box.parent_box.xmax   = box.dragging_box.xmax - pointerblock.x + screenwidth;
  box.parent_box.ymin   = box.dragging_box.ymin - pointerblock.y;
  box.parent_box.ymax   = box.dragging_box.ymax - pointerblock.y + screenheight;

  /* Start the drag */

  RetError(wimp_drag_box(&box));

  /* Finished; flag that we're dragging an object with a dashed outline and exit */

  hotlist_dragging.drag_type = HOTLIST_BOX_DRAG;

  return NULL;
}

/*************************************************/
/* 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)
{
  _kernel_oserror * e;
  int               dragging = hotlist_dragging.drag_type;

  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_drag_stop_handler: Called\n");
  #endif

  /* If we're not dragging in the hotlist, something weird is */
  /* going on! So definitely, ignore the event...             */

  if (hotlist_dragging.drag_type == HOTLIST_NOT_DRAGGING) return 0;

  /* Make sure we clear the dragging flag as soon as possible. */
  /* If it gets left switched on, then drags in other parts of */
  /* the browser could get misinterpreted by the hotlist code. */

  dragging                   = hotlist_dragging.drag_type;
  hotlist_dragging.drag_type = HOTLIST_NOT_DRAGGING;

  if (hotlist_current_highlighted)
  {
    hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;

    ChkError(hotlist_redraw_items(highlighted_itemno, highlighted_itemno));

    hotlist_current_highlighted = NULL;
  }

  /* Deal with each dragging type */

  switch (dragging)
  {
    /* Complain if we don't understand it */

    default:
    {
      hotlist_dragging.drag_type = HOTLIST_NOT_DRAGGING;

      #ifdef TRACE

        erb.errnum = Utils_Error_Custom_Normal;
        sprintf(erb.errmess,
                "Value %d of hotlist_dragging.drag_type not understood in hotlist_drag_stop_handler",
                hotlist_dragging.drag_type);

        show_error_ret(&erb);

      #endif

      return 0;
    }
    break;

    case HOTLIST_SOLID_DRAG_OBJECT:
    {
      /* Stop drag an object */

      e = _swix(DragAnObject_Stop, 0);

      deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);

      ChkError(e);
    }
    break;

    case HOTLIST_SOLID_DRAG_SPRITE:
    {
      /* Stop drag a sprite */

      e = _swix(DragASprite_Stop, 0);

      deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);

      ChkError(e);
    }
    break;

    case HOTLIST_BOX_DRAG_SELECTION:
    {
      /* Stop drag box */

      e = wimp_drag_box(NULL);

      deregister_null_claimant(Wimp_ENull, hotlist_null_drag_select_handler, NULL);

      /* Deselect anything that is currently selected */

      hotlist_clear_flags(hotlist_root->data.directory_content, hl_ALL, HOTLIST_G_DRAG_SELECTED);
      ChkError(hotlist_redraw_now());

      ChkError(e);
    }
    break;

    case HOTLIST_BOX_DRAG:
    {
      e = wimp_drag_box(NULL);

      deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);

      ChkError(e);
    }
    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 in any      */
/* other window will try to save the relevant    */
/* datatype (URI or HTML) to that window.        */
/*************************************************/

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

  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_drag_completed_handler: Called\n");
  #endif

  /* If we're not dragging in the hotlist, the Wimp returned */
  /* this because someone else completed a drag (e.g. a save */
  /* dialogue). In that case, ignore the event.              */

  if (hotlist_dragging.drag_type == HOTLIST_NOT_DRAGGING) return 0;

  /* Make sure we clear the dragging flag as soon as possible. */
  /* If it gets left switched on, then drags in other parts of */
  /* the browser could get misinterpreted by the hotlist code. */

  dragging                   = hotlist_dragging.drag_type;
  hotlist_dragging.drag_type = HOTLIST_NOT_DRAGGING;

  /* Check if SHIFT is pressed */

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

        121,
        128,

        &shift);

  /* Reset autoscrolling (To restore pointer if necessary) */

  show_error_ret(hotlist_autoscroll(0));

  /* Deal with each dragging type */

  switch (dragging)
  {
    /* Complain if we don't understand it */

    default:
    {
      #ifdef TRACE

        erb.errnum = Utils_Error_Custom_Normal;
        sprintf(erb.errmess,
                "Value %d of hotlist_dragging.drag_type not understood in hotlist_drag_completed_handler",
                hotlist_dragging.drag_type);

        show_error_ret(&erb);

      #endif

      return 0;
    }
    break;

    /* For e.g. dragging a single URL or directory item */

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

    /* For e.g. dragging a selection under the 'package' icon */

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

    /* For e.g. dragging a single URL or selection without solid drags emables */

    case HOTLIST_BOX_DRAG:
    {
      /* No special action required in this case */
    }
    break;

    /* When dragging out a box to dynamically select hotlist items */

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

  /* If there is a currently highlighted directory (i.e. something */
  /* was to be dropped into it) unhighlight that item.             */

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

    hotlist_current_highlighted = NULL;
  }

  /* Remove the drag null handler */

  deregister_null_claimant(Wimp_ENull, hotlist_null_handler, NULL);

  /* Remember the first selected item in the hotlist */

  sourceitem = hotlist_find_selected_item();

  /* Get information on the pointer */

  wimp_get_pointer_info(&pointerblock);

  /* Was the drag ended over the hotlist window? */

  ChkError(window_get_wimp_handle(0,
                                  hotlist_windowid,
                                  &window_handle));

  if (window_handle == pointerblock.window_handle)
  {
    /* Yes, the drag ended over the hotlist window. */

    if (pointerblock.icon_handle != -1) return 0; /* (Only understand drops on workspace) */

    /* Find item sizes, the window x and y coordinate of the  */
    /* drop point, and from this, the targetted hotlist item. */

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

    state.window_handle = pointerblock.window_handle;
    ChkError(wimp_get_window_state(&state));

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

    top        = (-winy) / item_height;
    targetitem = hotlist_find_item(hotlist_root->data.directory_content, top);

    /* Decide where to put the items */

    if (targetitem)
    {
      /* If we've dropped on a selected or read-only item, do nothing */

      if (targetitem->flags & (HOTLIST_G_IS_SELECTED | HOTLIST_G_IS_READ_ONLY)) return 0;

      /* Otherwise, find the x coordinate span of the target item... */

      hotlist_get_shape(&xmin, &xmax, targetitem);

      if (targetitem->type == hl_directory && winx >= xmin && winx <= xmin + item_dir_width)
      {
        /* ...and if the drop point was within the x span of the */
        /* directory icon part of a directory, place in that     */
        /* directory (directories will only show the '+' icon,   */
        /* i.e. highlighted, when the pointer hovers over the    */
        /* sprite component).                                    */

        position = HOTLIST_POSITION_BEGINNING;
      }
      else
      {
        if ((-winy % item_height) > item_height / 2)
        {
          /* If we're over half way past the item, place after it, unless */
          /* it is an open directory. Again, the null handler dealing     */
          /* with highlighting directory sprites reflects this situation  */
          /* correctly.                                                   */

          if (
               targetitem->type == hl_directory      &&
               targetitem->flags & HOTLIST_D_IS_OPEN
             )
          {
            position = HOTLIST_POSITION_BEGINNING;
          }
          else
          {
            position = HOTLIST_POSITION_AFTER;
          }
        }
        else
        {
          /* Otherwise, place before */

          position = HOTLIST_POSITION_BEFORE;
        }
      }
    }
    else
    {
      /* If there is no target item, add to the end of the hotlist root */

      targetitem = hotlist_root;
      position   = HOTLIST_POSITION_END;
    }

    /* Find the selected item. If its number is less than the one */
    /* we estimated from the drop y coordinate, use that instead. */

    {
      int selected;

      selected = hotlist_find_no_from_item(hotlist_find_selected_item());
      if (selected < top) top = selected;
    }

    /* Count how many items are displayed to get the last hotlist item */

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

    if (!shift)
    {
      /* If shift isn't pressed, move selected items */

      do
      {
        /* Ensure the source item is deselected */

        sourceitem->flags &= ~HOTLIST_G_IS_SELECTED;

        /* Move it */

        ChkError(hotlist_move_item(sourceitem,
                                   targetitem,
                                   position));

        /* If this is a directory, deselect its contents */

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

        targetitem = sourceitem;
        sourceitem = hotlist_find_selected_item();

        /* Loop round moving all following items for as long as */
        /* anything is still selected - making sure we copy     */
        /* them all after what we just added.                   */

        position = HOTLIST_POSITION_AFTER;
      }
      while (sourceitem);

      ChkError(hotlist_modified(HL_MODIFIED_MOVE));
    }
    else
    {
      /* Shift is pressed, so copy the items. The code is very */
      /* similar in structure to the move stuff above.         */

      do
      {
        sourceitem->flags &= ~HOTLIST_G_IS_SELECTED;

        ChkError(hotlist_copy_item(sourceitem,
                                   targetitem,
                                   position,
                                   &targetitem));

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

        sourceitem = hotlist_find_selected_item();
        position   = HOTLIST_POSITION_AFTER;
      }
      while (sourceitem);

      ChkError(hotlist_modified(HL_MODIFIED_COPY));
    }

    /* The displayed item count may have changed, so take the greatest */
    /* value of this as the bottom item to redraw between.             */

    {
      int new_bottom = hotlist_count_displayed_items(hotlist_root->data.directory_content);

      if (new_bottom > bottom) bottom = new_bottom;
    }

    /* Ensure the window extent etc. is up to date */

    hotlist_preopen();

    /* Redraw the relevant regions of the window */

    ChkError(hotlist_redraw_items(top, bottom));
  }
  else
  {
    /* No - the drag did not end over the hotlist window */

    if (
         sourceitem                          &&
         hotlist_count_selected_items() == 1 &&
         sourceitem->type == hl_url
       )
    {
      /* There is a single selected item which is a URL. So, save */
      /* it as a URI file.                                        */

      hotlist_initiate_uri_save(sourceitem);
    }
    else
    {
      /* Can't save URI file when saving more than one URL, */
      /* so save an HTML file instead.                      */

      hotlist_initiate_html_save(lookup_token("HotlistLeafname:Hotlist",0,0));
    }
  }

  /* If we were using Adjust, close the window */

  if (hotlist_dragging.using_adjust)
  {
    hotlist_dragging.using_adjust = 0;

    ChkError(toolbox_hide_object(0, hotlist_windowid));
  }

  /* Finished */

  return 1;
}

/*************************************************/
/* hotlist_autoscroll()                          */
/*                                               */
/* Auto-scrolls a window.                        */
/*                                               */
/* Parameters: Object ID of the window to scroll */
/*             or 0 to reset autoscrolling.      */
/*************************************************/

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

  static unsigned int     scrolling, autoscroll_oldtime;
  static unsigned int     mouse_shape = Mouse_Shape_Normal;

  /* Reset autoscroll handling if requested */

  if (!window)
  {
    #ifdef TRACE
      if (tl & (1<<25)) Printf("hotlist_autoscroll: Resetting\n");
    #endif

    scrolling = 0;

    mouse_set_pointer_shape(Mouse_Shape_Normal);
    mouse_shape = Mouse_Shape_Normal;

    return _swix(OS_ReadMonotonicTime,
                 _OUT(0),

                 &autoscroll_oldtime);
  }

  /* What window is the pointer over? */

  RetError(window_get_pointer_info(0, &x, &y, NULL, &over_window, NULL));

  /* Get information on the window given to the function */

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

  /* Find location of pointer relative to the given window */

  if (
       x < state.visible_area.xmin ||
       x > state.visible_area.xmax ||
       y < state.visible_area.ymin ||
       y > state.visible_area.ymax
     )
  {
    /* Outside the visible area */

    position = 0;
  }
  else if (
            x > state.visible_area.xmin + choices.auto_scroll_margin &&
            x < state.visible_area.xmax - choices.auto_scroll_margin &&
            y > state.visible_area.ymin + choices.auto_scroll_margin &&
            y < state.visible_area.ymax - choices.auto_scroll_margin
          )
  {
    /* Pointer is inside the non-scrolling region of the given window */

    position = 1;
  }
  else
  {
    /* Pointer is inside the scrolling region of the given window */

    position = 2;
  }

  /* Work out what scroll settings to give, flagging if there is */
  /* a change in 'scroll_changed'.                               */

  scroll_changed = 0;

  if (position == 0 || position == 2)
  {
    /* Check position relative to the top/bottom of the window */

    if (y > state.visible_area.ymax - choices.auto_scroll_margin)
    {
      /* At or over the top of the window */

      if (state.yscroll < extent.ymax)
      {
        scroll_changed = 1;
        state.yscroll += (y - (state.visible_area.ymax - choices.auto_scroll_margin));
      }
    }
    else
    {
      if (y < state.visible_area.ymin + choices.auto_scroll_margin)
      {
        /* At or under the bottom of the window */

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

    /* Check the position relative to the left/right of the window, too */

    if (x > state.visible_area.xmax - choices.auto_scroll_margin)
    {
      /* At or to the right of the right hand edge of the window */

      if (state.xscroll < extent.xmax - (state.visible_area.xmax - state.visible_area.xmin))
      {
        scroll_changed = 1;
        state.xscroll += (x - (state.visible_area.xmax - choices.auto_scroll_margin));
      }
    }
    else
    {
      if (x < state.visible_area.xmin + choices.auto_scroll_margin)
      {
        /* At or to the left of the left hand edge of the window */

        if (state.xscroll > extent.xmin)
        {
          scroll_changed = 1;
          state.xscroll -= ((state.visible_area.xmin + choices.auto_scroll_margin) - x);
        }
      }
    }
  }

  /* Deal with flags and the mouse pointer */

  switch (position)
  {
    /* If we're outside the visible area of the window, and not scrolling, */
    /* set the pointer back to a normal shape (else leave it alone).       */

    case 0:
    {
      if (!scrolling)
      {
        RetError(_swix(OS_ReadMonotonicTime,
                       _OUT(0),

                       &autoscroll_oldtime));

        if (mouse_shape != Mouse_Shape_Normal)
        {
          mouse_set_pointer_shape(Mouse_Shape_Normal);
          mouse_shape = Mouse_Shape_Normal;
        }
      }
    }
    break;

    /* If we're in the non-scrolling region, unset the 'scrolling' flag */
    /* and set the mouse pointer back to a normal shape.                */

    case 1:
    {
      scrolling = 0;

      RetError(_swix(OS_ReadMonotonicTime,
                     _OUT(0),

                     &autoscroll_oldtime));

      if (mouse_shape != Mouse_Shape_Normal)
      {
        mouse_set_pointer_shape(Mouse_Shape_Normal);
        mouse_shape = Mouse_Shape_Normal;
      }
    }
    break;

    /* If inside the scrolling region, wait a while on the 'ToScroll' */
    /* pointer, then start scrolling.                                 */

    case 2:
    {
      if (!scrolling && scroll_changed)
      {
        RetError(_swix(OS_ReadMonotonicTime,
                       _OUT(0),

                       &autoscroll_newtime));

        /* If we exceed the autoscroll delay, set the scrolling flag */

        if (autoscroll_newtime - autoscroll_oldtime > choices.auto_scroll_delay)
        {
          scrolling = 1;
        }

        /* Otherwise, set the 'ToScroll' pointer */

        else
        {
          if (mouse_shape != Mouse_Shape_ToScroll)
          {
            mouse_shape = Mouse_Shape_ToScroll;
            mouse_set_pointer_shape(Mouse_Shape_ToScroll);
          }
        }
      }

      /* If the scroll position hasn't changed, update oldtime */

      if (!scroll_changed)
      {
        RetError(_swix(OS_ReadMonotonicTime,
                       _OUT(0),

                       &autoscroll_oldtime));
      }
    }
    break;
  }

  /* Closing actions to take if scrolling is in progress. */

  if (scrolling)
  {
    if (scroll_changed)
    {
      /* If the scroll position has changed, update the window */

      RetError(toolbox_get_parent(0,
                                  window,
                                  &parent,
                                  &component));

      RetError(toolbox_show_object(0,
                                   window,
                                   Toolbox_ShowObject_FullSpec,
                                   &state.visible_area,
                                   parent,
                                   component));

      /* Ensure the pointer shape is correct */

      if (mouse_shape != Mouse_Shape_Scrolling)
      {
        mouse_shape = Mouse_Shape_Scrolling;
        mouse_set_pointer_shape(Mouse_Shape_Scrolling);
      }
    }
    else
    {
      /* If the scroll position has not changed, set the pointer */
      /* shape to normal, and unset the scrolling flag - we've   */
      /* scrolled to the limit of the window extent.             */

      if (mouse_shape != Mouse_Shape_Normal)
      {
        mouse_shape = Mouse_Shape_Normal;
        mouse_set_pointer_shape(Mouse_Shape_Normal);

        scrolling = 0;
      }
    }
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* hotlist_null_handler()                        */
/*                                               */
/* Called every null event while a drag is in    */
/* operation.                                    */
/*                                               */
/* Parameters are as standard for a Wimp event   */
/* handler.                                      */
/*************************************************/

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

  /* Get information on the pointer position */

  ChkError(window_get_pointer_info(0, &x, &y, &buttons, &window, NULL));

  /* Deal with autoscroll */

  ChkError(hotlist_autoscroll(hotlist_windowid));

  if (window == hotlist_windowid)
  {
    /* The pointer is over the hotlist window */

    ChkError(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. First,  */
    /* get the window state.                                      */

    ChkError(wimp_get_window_state(&state));

    /* Work out the item that the pointer is over */

    ChkError(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);

    /* Is this a directory item which is already not selected? (The */
    /* selected check is so that we don't end up highlighting the   */
    /* item we're dragging if this item is a directory).            */

    if (item && item->type == hl_directory && !(item->flags & HOTLIST_G_IS_SELECTED))
    {
      ChkError(hotlist_get_shape(&xmin, &xmax, item));

      /* If we're over the directory sprite, or half way under the item height when */
      /* the directory is open.                                                     */

      if (
           (
             x >= xmin &&
             x <= xmin + item_dir_width
           )
           ||
           (
             ((-y % item_height) > item_height / 2) &&
             item->flags & HOTLIST_D_IS_OPEN
           )
         )
      {
        /* ...and the currently highlighted item isn't the one we're over, and */
        /* it isn't read only...                                               */

        if (
             hotlist_current_highlighted != item     &&
             !(item->flags & HOTLIST_G_IS_READ_ONLY)
           )
        {
          /* ...then first, clear any highlighted item */

          if (hotlist_current_highlighted)
          {
            hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;

            ChkError(hotlist_redraw_items(highlighted_itemno, highlighted_itemno));
          }

          /* Now highlight the item we're over */

          item->flags |= HOTLIST_D_IS_HIGHLIGHTED;

          hotlist_current_highlighted = item;
          highlighted_itemno          = itemno;

          ChkError(hotlist_redraw_items(highlighted_itemno, highlighted_itemno));

          /* Reset the auto-open timer */

          ChkError(_swix(OS_ReadMonotonicTime,
                         _OUT(0),

                         &autoopen_oldtime));
        }
        else if (hotlist_current_highlighted == item)
        {
          int new_time;

          /* If we've stayed over the same highlighted item, */
          /* keep a count of for how long, and if required,  */
          /* automatically open the directory.               */

          ChkError(_swix(OS_ReadMonotonicTime,
                         _OUT(0),

                         &new_time));

          if (
               choices.auto_open_delay                               &&
               !(item->flags & HOTLIST_D_IS_OPEN)                    &&
               new_time - autoopen_oldtime > choices.auto_open_delay
             )
          {
            /* Auto-open the directory */

            item->flags |= HOTLIST_D_IS_OPEN;

            hotlist_preopen();

            ChkError(hotlist_redraw_items(highlighted_itemno,
                                          hotlist_count_displayed_items(hotlist_root->data.directory_content)));
          }
        }
      }
      else
      {
        /* If we're not over a directory sprite, clear any existing */
        /* highlight.                                               */

        remove_highlight = 1;
      }
    }

    /* If we're not over a directory at all, never mind it's directory */
    /* sprite component, again, clear any existing highlight.          */

    else remove_highlight = 1;
  }

  /* The following executes if the pointer is not over the hotlist window */

  else
  {
    /* Again, clear any highlighted directory sprite. */

    remove_highlight = 1;
  }

  /* If we're not over a directory sprite, clear any existing */
  /* highlight.                                               */

  if (remove_highlight && hotlist_current_highlighted)
  {
    hotlist_current_highlighted->flags &= ~HOTLIST_D_IS_HIGHLIGHTED;

    ChkError(hotlist_redraw_items(highlighted_itemno, highlighted_itemno));

    hotlist_current_highlighted = NULL;
  }

  /* Finished */

  return 0;
}

//
// Simple non-scrolling selection box, to be improved later...
// Workarea relative corner of selection box may not be needed.
//
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)
{
  WimpGetPointerInfoBlock pointerblock;
  WimpGetWindowStateBlock state;
  WimpDragBox             box;

  /* Get the pointer position and info on the hotlist window */

  RetError(wimp_get_pointer_info(&pointerblock));

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

  /* Remember the pointer position in window coordinates */

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

  /* Set up the drag box structure */

  box.drag_type         = Wimp_DragBox_DragRubberDash;

  box.dragging_box.xmin = pointerblock.x;
  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;
  box.parent_box.xmax   = state.visible_area.xmax;
  box.parent_box.ymin   = state.visible_area.ymin;
  box.parent_box.ymax   = state.visible_area.ymax;

  /* Start the drag */

  RetError(wimp_drag_box(&box));

  /* Set global variable saying we are currently dragging */
  /* so we know whether to process a user_drag_box event  */

  hotlist_dragging.drag_type = HOTLIST_BOX_DRAG_SELECTION;

  /* Install the null handler */

  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;
  WimpGetWindowStateBlock state;
  unsigned int            item_height, item_dir_width, item_url_width;
  unsigned int            item_min,    item_max;
  int                     workx,       worky;
  int                     minx,        maxx;

  /* Find the pointer position and get information on the hotlist window */

  ChkError(wimp_get_pointer_info(&pointerblock));

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

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

  /* Get the mouse position in window coordinates */

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

  /* Work out the items spanned by the drag box */

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

  /* Select them */

  ChkError(hotlist_select_box(item_min, item_max, minx, maxx));

  return 0;
}

/*************************************************/
/* hotlist_select_box()                          */
/*                                               */
/* Sets the HOTLIST_G_DRAG_SELECTED flag for all */
/* visible items in the range first_item to      */
/* last_item where their horizontal visible area */
/* intersects with the a line draw from minx to  */
/* maxx. Clears the HOTLIST_G_DRAG_SELECTED      */
/* flag for all other items.                     */
/*                                               */
/* Parameters: Number of the first visible item; */
/*                                               */
/*             Number of the last visible item;  */
/*                                               */
/*             minx (window coords);             */
/*                                               */
/*             maxx (window coords).             */
/*************************************************/

_kernel_oserror * hotlist_select_box(unsigned int first_item, unsigned int last_item, int minx, int maxx)
{
  unsigned int itemno = 0;

  return hotlist_select_box_r(first_item,
                              last_item,
                              hotlist_root->data.directory_content,
                              &itemno,
                              minx,
                              maxx);
}

/*************************************************/
/* hotlist_select_box_r()                        */
/*                                               */
/* Recursive backend to hotlist_select_box.      */
/*                                               */
/* Parameters: Number of the first visible item; */
/*                                               */
/*             Number of the last visible item;  */
/*                                               */
/*             First item in the directory to    */
/*             scan;                             */
/*                                               */
/*             Pointer to an int, in which the   */
/*             current item number is kept (for  */
/*             internal use only);               */
/*                                               */
/*             minx (window coords);             */
/*                                               */
/*             maxx (window coords).             */
/*************************************************/

_kernel_oserror * hotlist_select_box_r(unsigned int first_item, unsigned int last_item, hotlist_item * item,
                                       unsigned int * itemno, int minx, int maxx)
{
  _kernel_oserror * e;
  int               itemxmin, itemxmax;

  while (item)
  {
    if (*itemno >= first_item && *itemno <= last_item)
    {
      /* As long as we're within the item number specified, deal with items */

      RetError(hotlist_get_shape(&itemxmin, &itemxmax, item));

      /* Select items within the horizontal range */

      if (!(maxx < itemxmin || minx > itemxmax))
      {
        if (!(item->flags & HOTLIST_G_DRAG_SELECTED))
        {
          item->flags |= HOTLIST_G_DRAG_SELECTED;

          e = hotlist_redraw_items(*itemno, *itemno);

          /* If directory is closed, select everything in it (if it is open, */
          /* we should only select items the drag box covers).               */

          if (item->type == hl_directory && !(item->flags & HOTLIST_D_IS_OPEN))
          {
            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);
          }

          if (e) return e;
        }
      }

      /* Deselect items outside of the horizontal range */

      else
      {
        if (item->flags & HOTLIST_G_DRAG_SELECTED)
        {
          item->flags &= ~HOTLIST_G_DRAG_SELECTED;

          e = hotlist_redraw_items(*itemno, *itemno);

          /* If directory is closed unselect everything in it */

          if (item->type == hl_directory && !(item->flags & HOTLIST_D_IS_OPEN))
          {
            hotlist_clear_flags(item->data.directory_content,
                                hl_ALL,
                                HOTLIST_G_DRAG_SELECTED | HOTLIST_G_REDRAW_NOW);
          }

          if (e) return e;
        }
      }
    }
    else
    {
      /* If we're not within the item numbers specified, then deselect things */

      if (item->flags & HOTLIST_G_DRAG_SELECTED)
      {
        item->flags &= ~HOTLIST_G_DRAG_SELECTED;

        e = hotlist_redraw_items(*itemno, *itemno);

        /* If directory is closed unselect everything in it */

        if (item->type == hl_directory && !(item->flags & HOTLIST_D_IS_OPEN))
        {
          hotlist_clear_flags(item->data.directory_content,
                              hl_ALL,
                              HOTLIST_G_DRAG_SELECTED | HOTLIST_G_REDRAW_NOW);
        }

        if (e) return e;
      }
    }

    /* Increment the item number character */

    *itemno += 1;

    /* Recurse for any open directory items */

    if (item->type == hl_directory && item->flags & HOTLIST_D_IS_OPEN)
    {
      RetError(hotlist_select_box_r(first_item,
                                    last_item,
                                    item->data.directory_content,
                                    itemno,
                                    minx,
                                    maxx));
    }

    /* Get the next item */

    item = item->next;
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* 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 the     */
/* hotlist window and relevant menus to reflect  */
/* this. Parameters are as standard for a        */
/* Toolbox event handler.                        */
/*************************************************/

static int hotlist_show_descriptions_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  ObjectId main_menu;

  hl_show_urls = 0;

  /* Update the menus */

  ChkError(window_get_menu(0, hotlist_windowid, &main_menu));
  hotlist_set_menu_details(main_menu);

  /* Ensure the window is up to date */

  hotlist_preopen();

  /* Redraw as required */

  ChkError(hotlist_redraw_items(0,
                                hotlist_count_displayed_items(hotlist_root->data.directory_content)));

  return 1;
}

/*************************************************/
/* hotlist_show_urls_handler()                   */
/*                                               */
/* Selects Show URLs and redraws the hotlist     */
/* window and relevant menus to reflect this.    */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int hotlist_show_urls_handler(int event_code, ToolboxEvent * event, IdBlock * id_block, void * handle)
{
  ObjectId main_menu;

  hl_show_urls = 1;

  /* Update the menus */

  ChkError(window_get_menu(0, hotlist_windowid, &main_menu));
  hotlist_set_menu_details(main_menu);

  /* Ensure the window is up to date */

  hotlist_preopen();

  /* Redraw as required */

  ChkError(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.                            */
/*                                               */
/* dragselected and selected = unselected        */
/* dragselected              = selected          */
/* selected                  = selected          */
/* neither                   = unselected        */
/*                                               */
/* Parameters: Pointer to a hotlist_item to      */
/*             start on.                         */
/*************************************************/

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

    /* Recursive call for directories */

    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)
{
  #ifdef TRACE
    if (tl & (1u<<25)) Printf("hotlist_modified: Called\n");
  #endif

  if (choices.save_hotlist == Choices_SaveHotlist_Always)
  {
    /* If we've loaded a resources file, there's no point  */
    /* saving the hotlist because the read-only parts will */
    /* not get saved anyway.                               */

    if (type != HL_MODIFIED_LOADED_RESOURCES)
    {
      #ifdef SINGLE_USER

        return hotlist_save(lookup_choice("HotlistSave:Browse:User.Hotlist",0,0));

      #else

        multiuser_save_hotlist();
        return NULL;

      #endif
    }
  }

  return NULL;
}

/*************************************************/
/* hotlist_initiate_uri_save()                   */
/*                                               */
/* Starts file transfer of a URI file to another */
/* application.                                  */
/*                                               */
/* Parameters: Pointer to the hotlist item to    */
/*             save as a URI file.               */
/*************************************************/

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

  /* Is Control being pressed? */

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

        121,
        129,

        &ctrl);

  /* Find out where the pointer is */

  RetError(wimp_get_pointer_info(&block));

  /* Get the leafname. Use a message block for the buffer because  */
  /* there's no point generating anything longer than can actually */
  /* be sent out in a message.                                     */

  urlutils_leafname_from_url(item->data.url,
                             message.data.data_save.leaf_name,
                             sizeof(message.data.data_save.leaf_name));

  /* Send Message_DataSave to start file transfer - a URI file, */
  /* or if Control is held down, a URL file.                    */

  return protocols_atats_send_data_save(NULL,
                                        item,
                                        message.data.data_save.leaf_name,
                                        save_uri_size(item->data.url, item->name, 0),
                                        ctrl ? FileType_URL : FileType_URI,
                                        protocols_saving_hotlist_entry,
                                        &block);
}

/*************************************************/
/* hotlist_initiate_html_save()                  */
/*                                               */
/* Starts file transfer of a selection in the    */
/* hotlist as an HTML file.                      */
/*                                               */
/* Parameters: Pointer to a null-terminated      */
/*             leafname to save as.              */
/*************************************************/

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

  RetError(wimp_get_pointer_info(&block));

  /* Ensure the leafname isn't too long for a message */

  StrNCpy0(message.data.data_save.leaf_name, filename);

  /* Send Message_DataSave to start file transfer */

  return protocols_atats_send_data_save(NULL,
                                        NULL,
                                        message.data.data_save.leaf_name,
                                        -1,
                                        FileType_HTML,
                                        protocols_saving_hotlist_selection,
                                        &block);
}

/*************************************************/
/* 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)
{
  WimpGetWindowStateBlock   state;
  hotlist_item            * targetitem;
  unsigned int              item_height, item_dir_width, item_url_width;
  int                       winx, winy;
  int                       top, position;

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

  top        = (-winy) / item_height;
  targetitem = hotlist_find_item(hotlist_root->data.directory_content, top);

  /* Decide where to put the new item */

  if (targetitem)
  {
    if ((-winy % item_height) > item_height / 2)
    {
      /* Put item after the target if we're over */
      /* half way past its height                */

      position = HOTLIST_POSITION_AFTER;
    }
    else
    {
      /* Otherwise, put item before the target */

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

  RetError(hotlist_new_url(targetitem,
                           position,
                           description,
                           url));

  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:
    {
      RetError(hotlist_redraw_now());
    }
    break;

    default:
    {
      hotlist_clear_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);

      RetError(hotlist_redraw_items(top - 1,
                                    hotlist_count_displayed_items(hotlist_root->data.directory_content)));
    }
    break;
  }

  /* Exit by calling the routine that we must call every */
  /* time the hotlist changes (so that it can, for       */
  /* example, be saved - should the Choices require it.  */

  return hotlist_modified(HL_MODIFIED_ADD);
}

/*************************************************/
/* hotlist_add_html_file()                       */
/*                                               */
/* Read in an HTML file, adding items starting   */
/* at the specified position.                    */
/*                                               */
/* Parameters: Screen relative x position;       */
/*                                               */
/*             Screen relative y position;       */
/*                                               */
/*             Pathname of the HTML file.        */
/*************************************************/

_kernel_oserror * hotlist_add_html_file(int x, int y, char * path)
{
  _kernel_oserror         * e;
  hotlist_item            * targetitem;
  hotlist_item            * newitem;
  FILE                    * fileptr;
  WimpGetWindowStateBlock   state;
  unsigned int              item_height, item_dir_width, item_url_width;
  int                       winx, winy;
  int                       top, position;
  char                    * dirname;

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

  top        = (-winy) / item_height;
  targetitem = hotlist_find_item(hotlist_root->data.directory_content, top);

  /* Decide where to put the new item */

  if (targetitem)
  {
    if ((-winy % item_height) > item_height / 2)
    {
      /* Put item after the target if we're over */
      /* half way past its height                */

      position = HOTLIST_POSITION_AFTER;
    }
    else
    {
      /* Otherwise, put item before the target */

      position = HOTLIST_POSITION_BEFORE;
    }
  }
  else
  {
    /* Put item at end of root directory */

    position   = HOTLIST_POSITION_END;
    targetitem = hotlist_root;
  }

  dirname = strrchr(path, '.');
  if (dirname) dirname ++;
  if (!dirname || !*dirname) dirname = lookup_token("HotlistUntitled:(Untitled)",0,0);

  RetError(hotlist_new_directory(targetitem, dirname, position, &newitem));

  /* Open the file */

  fileptr = fopen(path, "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, adding to the new directory */

  e = hotlist_load_directory(fileptr, newitem);

  fclose(fileptr);

  if (e) return e;

  /* Handle redraw */

  hotlist_preopen();

  hotlist_set_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);

  RetError(hotlist_redraw_now());

  /* Exit through the 'has modified' routine */

  return hotlist_modified(HL_MODIFIED_ADD);
}

/*************************************************/
/* hotlist_load_resources()                      */
/*                                               */
/* Read in a read-only 'Resources' hotlist, at   */
/* the top of the hotlist window. This will not  */
/* be saved when the rest of the hotlist is.     */
/*                                               */
/* Errors are raised if there is not enough      */
/* memory etc., but not if the file doesn't      */
/* exist.                                        */
/*                                               */
/* Parameters: Pathname of the HTML file.        */
/*************************************************/

_kernel_oserror * hotlist_load_resources(char * path)
{
  _kernel_oserror * e;
  hotlist_item    * newitem;
  FILE            * fileptr;

  RetError(hotlist_new_directory(hotlist_root,
                                 lookup_token("HotlistResDir:Resources",0,0),
                                 HOTLIST_POSITION_BEGINNING,
                                 &newitem));

  /* Open the file */

  fileptr = fopen(path, "r");

  /* Fail silently if there's an error opening it */

  if (fileptr == NULL) return NULL;

  /* Load it, adding to the new directory */

  e = hotlist_load_directory(fileptr, newitem);

  fclose(fileptr);

  if (e) return e;

  /* Set all contained items as read-only, and mark the top-level */
  /* Resources directory as both read-only and, if the Choices    */
  /* say so, open.                                                */

  newitem->flags |= (HOTLIST_G_IS_READ_ONLY);

  hotlist_set_flags(newitem->data.directory_content,
                    hl_ALL,
                    HOTLIST_G_IS_READ_ONLY);

  if (!strcmp(lookup_choice("OpenResources:no",0,0), "yes"))
  {
    newitem->flags |= (HOTLIST_D_IS_OPEN);
  }

  /* Handle redraw */

  hotlist_preopen();
  hotlist_set_flags(hotlist_root, hl_ALL, HOTLIST_G_REDRAW_NOW);

  RetError(hotlist_redraw_now());

  /* Exit through the 'has modified' routine */

  return hotlist_modified(HL_MODIFIED_LOADED_RESOURCES);
}

/*************************************************/
/* hotlist_get_selected_shape()                  */
/*                                               */
/* This routine gets the coordinates required to */
/* bound every selected item in the hotlist      */
/* window (window relative coordinated)          */
/*                                               */
/* Parameters: Pointer to window relative        */
/*             bounding box.                     */
/*                                               */
/* Returns:    1 if there were selected item(s), */
/*             0 if there weren't.               */
/*************************************************/

static int hotlist_get_selected_shape(BBox * box)
{
  _kernel_oserror * e;
  unsigned int      itemno, item_height;
  int               found;

  found  = 0;
  itemno = 0;

  /* Get item sizes */

  e = hotlist_get_entry_sizes(&item_height, NULL, NULL);

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

  /* Enter the recursive routine */

  e = hotlist_get_selected_shape_r(hotlist_root->data.directory_content,
                                   box,
                                   &itemno,
                                   &found,
                                   item_height);
  if (e)
  {
    show_error_ret(e);
    return 0;
  }

  return found;
}

/*************************************************/
/* hotlist_get_selected_shape_r()                */
/*                                               */
/* Recursive back-end to                         */
/* hotlist_get_selected_shape().                 */
/*                                               */
/* Parameters: Pointer to a hotlist_item;        */
/*                                               */
/*             Pointer to window relative        */
/*             bounding box;                     */
/*                                               */
/*             Pointer to the current item       */
/*             number (for internal use);        */
/*                                               */
/*             Pointer to found flag;            */
/*                                               */
/*             The height of a single entry in   */
/*             OS units.                         */
/*************************************************/

static _kernel_oserror * hotlist_get_selected_shape_r(hotlist_item * list, BBox * box, unsigned int * itemno, int * found, unsigned int item_height)
{
  int newxmin, newxmax;

  while (list)
  {
    if (list->flags & HOTLIST_G_IS_SELECTED)
    {
      RetError(hotlist_get_shape(&newxmin, &newxmax, list));

      /* If we've already found an open item, then only extend the */
      /* bounding box in appropriate directions.                   */

      if (*found)
      {
        if (newxmin < box->xmin) box->xmin = newxmin;
        if (newxmax > box->xmax) box->xmax = newxmax;

        box->ymin = -((*itemno) + 1) * item_height;
      }
      else
      {
        /* This section run for first selected item found only; */
        /* set the selected shape box to match the item's       */
        /* bounding box.                                        */

        box->xmin = newxmin;
        box->xmax = newxmax;
        box->ymax = -(*itemno)       * item_height;
        box->ymin = -((*itemno) + 1) * item_height;

        /* Flag that we've found a selected item */

        *found = 1;
      }
    }

    *itemno += 1;

    /* Recursively scan all open directories */

    if (list->type == hl_directory && (list->flags & HOTLIST_D_IS_OPEN))
    {
      RetError(hotlist_get_selected_shape_r(list->data.directory_content,
                                            box,
                                            itemno,
                                            found,
                                            item_height));
    }

    /* Follow the list */

    list = list->next;
  }

  /* Finished */

  return NULL;
}

/*************************************************/
/* hotlist_find_match()                          */
/*                                               */
/* Takes a string from the given buffer and sees */
/* if there's something in the Hotlist that      */
/* matches it in some way.                       */
/*                                               */
/* If it finds something, it writes it back to   */
/* the buffer and returns 1.                     */
/*                                               */
/* Parameters: Pointer to the buffer holding the */
/*             string to try and match;          */
/*                                               */
/*             Size of the buffer in bytes.      */
/*                                               */
/* Returns:    1 if the buffer is updated with a */
/*             match string, else 0 (buffer      */
/*             contents will be unaltered).      */
/*************************************************/

int hotlist_find_match(char * buffer, int buffer_size)
{
  hotlist_item * lowest_item   = NULL;
  int            lowest_offset = -1;
  int            lowest_diff   = 0;

  if (!buffer || !*buffer) return 0;

  hotlist_find_match_r(buffer,
                       buffer_size,
                       hotlist_root,
                       &lowest_item,
                       &lowest_offset,
                       &lowest_diff);

  if (lowest_item && lowest_offset >= 0)
  {
    strncpy(buffer, lowest_item->data.url, buffer_size);
    buffer[buffer_size - 1] = 0;

    return 1;
  }

  return 0;
}

/*************************************************/
/* hotlist_find_match_r()                        */
/*                                               */
/* Recursive back-end to hotlist_find_match.     */
/*                                               */
/* Parameters: Pointer to the buffer holding the */
/*             string to try and match;          */
/*                                               */
/*             Size of the buffer in bytes;      */
/*                                               */
/*             Pointer to a hotlist_item at the  */
/*             top of the directory to scan;     */
/*                                               */
/*             Pointer to a hotlist_item * in    */
/*             which the best match hotlist_item */
/*             is written, if any;               */
/*                                               */
/*             Pointer to an int, in which the   */
/*             lowest offset into a string so    */
/*             far is written;                   */
/*                                               */
/*             Pointer to an int, in which the   */
/*             lowest difference in lengths      */
/*             between the buffer and match      */
/*             string is written.               */
/*************************************************/

static void hotlist_find_match_r(char * buffer, int buffer_size, hotlist_item * item,
                                 hotlist_item ** lowest_item, int * lowest_offset,
                                 int * lowest_diff)
{
  while (item)
  {
    if (item->type == hl_directory)
    {
      hotlist_find_match_r(buffer,
                           buffer_size,
                           item->data.directory_content,
                           lowest_item,
                           lowest_offset,
                           lowest_diff);
    }
    else
    {
      int          this_offset = 0;
      int          got_one     = 0;
      const char * found;

      if (item->data.url)
      {
        found = strstr(item->data.url, buffer);

        if (found)
        {
          this_offset = found - item->data.url;
          got_one     = 1;
        }
      }

      if (!got_one && item->name)
      {
        found = strstr(item->name, buffer);

        if (found)
        {
          this_offset = found - item->name;
          got_one     = 1;
        }
      }

      if (got_one)
      {
        if (*lowest_offset < 0 || this_offset <= *lowest_offset)
        {
          int this_diff = strlen(item->data.url) - strlen(buffer);

          *lowest_offset = this_offset;

          if (this_offset < *lowest_offset) *lowest_diff = 0;

          if (!*lowest_diff || (this_diff && this_diff < *lowest_diff))
          {
            *lowest_diff = this_diff;
            *lowest_item = item;
          }
        }
      }
    }

    item = item->next;
  }

  return;
}

/*************************************************/
/* hotlist_empty()                               */
/*                                               */
/* See if the hotlist is empty or not.           */
/*                                               */
/* Returns:    1 if the hotlist is empty else 0. */
/*************************************************/

int hotlist_empty(void)
{
  if (
       !hotlist_root                         ||
       !hotlist_root->data.directory_content
     )
     return 1;

  return 0;
}