/* 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   : SaveFile.c                             */
/*                                                 */
/* Purpose: Handle the Save File dialogue (actual  */
/*          file saving is done in Save.c). Relies */
/*          on there being only one Save File      */
/*          dialogue open at a time (it is a       */
/*          shared object).                        */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 03-Sep-97: Created.                    */
/*                                                 */
/*          08-Dec-97: Added code for option or    */
/*                     radio buttons and auto      */
/*                     widthing of the dialogue.   */
/***************************************************/

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

#include "swis.h"

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

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

#include "toolbox.h"
#include "window.h"
#include "gadgets.h"

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

#include "Browser.h"
#include "Fetch.h"  /* (For ISLINK macro) */
#include "Filetypes.h"
#include "History.h"
#include "Hotlist.h"
#include "Images.h"
#include "Menus.h"
#include "MimeMap.h"
#include "Object.h"
#include "Protocols.h"
#include "Save.h"
#include "SaveDraw.h"
#include "SaveText.h"
#include "Toolbars.h"
#include "URLutils.h"
#include "Windows.h"

#include "SaveFile.h"

/* Local statics */

static browser_data         * savefile_browser = NULL;
static HStream              * savefile_token   = NULL;
static int                    savefile_type    = 0x000;
static savefile_open_reason   savefile_reason  = save_as_html;

static ObjectId               window_id        = 0;
static ComponentId            parent_component = -1;
static ObjectId               ancestor_id      = 0;

/* These are to remember the state of the 'alternative'  */
/* selector (option button or radio buttons) for when    */
/* the Save File dialogue is opened for a particular     */
/* parent component. The 'mhso' etc. represent the first */
/* letters of the component name from Menus.h (so in the */
/* above example, mhso = MiscHotlistSaveObject). Where   */
/* several components lead to the same section of code   */
/* in a 'switch' statement, the name of the first        */
/* as appearing in savefile_to_be_shown is used.         */

#ifndef REMOTE_HOTLIST

  static int alt_mhso = 0; /* MiscHotlistSaveObject  */

#endif

static   int alt_ead  = 0; /* ExportAsDraw           */
static   int alt_fsfl = 0; /* FrameSaveFrameLocation */
static   int alt_el   = 0; /* ExportLink             */
static   int alt_ep   = 0; /* ExportPicture          */
static   int alt_eb   = 0; /* ExportBackground       */

/* Static function prototypes */

static int               savefile_drag_ended                         (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               savefile_ok                                 (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               savefile_cancel                             (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);

static _kernel_oserror * savefile_switch_to_normal                   (ObjectId window);
static _kernel_oserror * savefile_switch_to_option                   (ObjectId window);
static _kernel_oserror * savefile_switch_to_radios                   (ObjectId window);

static _kernel_oserror * savefile_set_items                          (ObjectId window, int selected);

static int               savefile_option_changed                     (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               savefile_radio_changed                      (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               savefile_item_changed                       (IdBlock * idb);

static _kernel_oserror * savefile_text_uri_or_url                    (ObjectId window);
static _kernel_oserror * savefile_toggle_uri_or_url                  (ObjectId window);
static _kernel_oserror * savefile_text_sprite_or_original            (ObjectId window);
static _kernel_oserror * savefile_toggle_sprite_or_original          (ObjectId window, const char * src);
static _kernel_oserror * savefile_text_without_backgrounds_or_with   (ObjectId window);
static _kernel_oserror * savefile_toggle_without_backgrounds_or_with (ObjectId window);

static _kernel_oserror * savefile_set_correct_extent                 (ObjectId window, BBox * ra1);
static _kernel_oserror * savefile_auto_width                         (ObjectId window);

/*************************************************/
/* savefile_open_for()                           */
/*                                               */
/* Creates and opens a Save File dialogue for a  */
/* given browser, opening near the pointer.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the ancestor of the      */
/*             dialogue;                         */
/*                                               */
/*             A savefile_open_reason describing */
/*             why the dialogue is being opened  */
/*             (see SaveFile.h).                 */
/*************************************************/

_kernel_oserror * savefile_open_for(browser_data * b, savefile_open_reason reason)
{
  ObjectId    id;
  ComponentId pc;

  /* Create the dialogue - as this is shared object it */
  /* will not be recreated if it already exists, but   */
  /* it's more efficient to not bother even trying!    */

  if (!window_id)
  {
    RetError(toolbox_create_object(0,
                                   "SaveFile",
                                   &id));
  }
  else id = window_id;

  /* Remember the reason we're being opened here */

  savefile_reason = reason;

  /* Work out which parent object and component IDs */
  /* we want to set                                 */

  switch (savefile_reason)
  {
    default:
    case save_as_html: pc = FileSaveFrame; break;
    case save_as_draw: pc = ExportAsDraw;  break;
    case save_as_text: pc = ExportAsText;  break;
  }

  /* Show the dialogue */

  RetError(toolbox_show_object(Toolbox_ShowObject_AsMenu,
                               id,
                               Toolbox_ShowObject_AtPointer,
                               NULL,
                               b->self_id,
                               pc));
  return NULL;
}

/*************************************************/
/* savefile_to_be_shown()                        */
/*                                               */
/* Fills in the Save File dialogue prior to      */
/* being shown, on the basis of the parent       */
/* component ID and ancestor object ID in the    */
/* event.                                        */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

int savefile_to_be_shown(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  char           text[Limits_OS_Pathname];
  browser_data * b;
  int            first_time = !window_id;
  int            ok         = 0;
  ObjectId       pc         = idb->parent_component;

  /* If this is the first time we've been opened, do a few */
  /* initialisation bits and pieces                        */

  if (first_time)
  {
    /* Process the writable icon text */

    ChkError(windows_process_component_text(idb->self_id, SaveFileWrit, text, sizeof(text), 0, 1));

    /* The following ensures that we increment the usage */
    /* count for the dialogue so it is never deleted.    */
    /* Otherwise, we have to start keeping track of when */
    /* it goes and deal with event handlers etc. as      */
    /* appropriate. Creating the object like this only   */
    /* gives back the existing ID of what is already     */
    /* there, so this works out as taking up less time   */
    /* and less code than an alternative approach.       */

    ChkError(toolbox_create_object(0, "SaveFile", NULL));
  }

  /* Where did we come from? */

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

  /* Deal with each parent menu item case */

  switch (pc)
  {
    case -1:
    {
      /* If the dialogue was raised directly from a keyboard shortcut, */
      /* (hmm, preferable to go through an event...) want to save the  */
      /* current page source.                                          */

      pc = FileSaveFrame;

      /* Slight complication is that only an ancestor browser ever   */
      /* has the input focus, so we need to find the actual selected */
      /* frame that the user is going to think the dialogue is for.  */

      if (b->selected_frame) b = b->selected_frame;

      /* Can't rely on any earlier sanity checks in this case, as this will */
      /* be the first and only function called before the dialogue opens.   */

      if (!b || !b->source) return 0;

      /* No 'break' - let this drop through... */
    }

    /* Save a page's source */

    case FileSaveFrame:
    case FileSaveParent:
    case FileSaveAncestor:
    {
      browser_data * alt = b;

      /* For saving a frameset, work out the required browser_data struct */

      if (pc == FileSaveParent)
      {
        alt = utils_parent(b);
        if (!alt) alt = utils_ancestor(b);
      }

      else if (pc == FileSaveAncestor) alt = utils_ancestor(b);

      b = alt;

      ChkError(savefile_switch_to_normal(idb->self_id));

      /* Reset the transferred data counter */

      b->save_transferred = 0;

      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, browser_current_url(b)));

      /* Set the draggable sprite */

      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, b->page_is_text ? FileType_TEXT : FileType_HTML, 0));

      /* Ensure the width is correct */

      ChkError(savefile_auto_width(idb->self_id));

      /* Remember various details about the dialogue's source */

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    #ifndef REMOTE_HOTLIST

      case HotlistSaveHotlist:
      {
        ChkError(savefile_switch_to_normal(idb->self_id));
        ChkError(savefile_set_leafname(idb->self_id, SaveFileWrit, lookup_token("HotlistLeafname:Hotlist",0,0)));
        ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML, 0));
        ChkError(savefile_auto_width(idb->self_id));

        ok = 1;
      }
      break;

      /* Save an object from the hotlist - a URL, directory, */
      /* or general selection                                */

      case MiscHotlistSaveObject:
      {
        hotlist_item * source = hotlist_find_selected_item();
        unsigned int   items  = hotlist_count_selected_items();

        if (items && source)
        {
          if (items == 1 && source->type == hl_url)
          {
            ChkError(savefile_switch_to_option(idb->self_id));
            ChkError(savefile_set_items(idb->self_id, alt_mhso));
            ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, source->data.url));
            ChkError(savefile_text_uri_or_url(idb->self_id));
            ChkError(savefile_toggle_uri_or_url(idb->self_id));
            ChkError(savefile_auto_width(idb->self_id));
          }
          else
          {
            ChkError(savefile_switch_to_normal(idb->self_id));
            ChkError(savefile_set_leafname(idb->self_id, SaveFileWrit, lookup_token("HotlistLeafname:Hotlist",0,0)));
            ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML, 0));
            ChkError(savefile_auto_width(idb->self_id));
          }

          ok = 1;
        }

        #ifdef TRACE

          else
          {
            erb.errnum = Utils_Error_Custom_Normal;

            sprintf(erb.errmess,
                    "Should have more than a selected hotlist item %p and item count %d in savefile_to_be_shown",
                    source,
                    items);

            ChkError(&erb);
          }

        #endif
      }
      break;

    #endif

    case HistorySaveLocal:
    case HistorySaveGlobal:
    {
      ChkError(savefile_switch_to_normal(idb->self_id));
      ChkError(savefile_set_leafname(idb->self_id, SaveFileWrit, lookup_token("HistoryLeafname:History",0,0)));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_HTML, 0));

      savefile_browser = b;
      ok               = 1;
    }
    break;

    case ExportAsDraw:
    {
      ChkError(savefile_switch_to_option(idb->self_id));
      ChkError(savefile_set_items(idb->self_id, alt_ead));
      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, browser_current_url(b)));
      ChkError(savefile_text_without_backgrounds_or_with(idb->self_id));
      ChkError(savefile_toggle_without_backgrounds_or_with(idb->self_id));
      ChkError(savefile_auto_width(idb->self_id));

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    case ExportAsText:
    {
      ChkError(savefile_switch_to_normal(idb->self_id));
      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, browser_current_url(b)));
      ChkError(savefile_set_filetype(idb->self_id, SaveFileDrag, FileType_TEXT, 0));
      ChkError(savefile_auto_width(idb->self_id));

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    /* Save a location as a URI file */

    case FileSaveFrameLocation:
    case FileSaveParentLocation:
    case FileSaveAncestorLocation:
    {
      browser_data * alt = b;
      char         * url;

      /* For saving a frameset, work out the required browser_data struct */

      if (pc == FileSaveParentLocation)
      {
        alt = utils_parent(b);
        if (!alt) alt = utils_ancestor(b);
      }

      else if (pc == FileSaveAncestorLocation) alt = utils_ancestor(b);

      b   = alt;
      url = browser_current_url(b);

      if (!url) url = " ";

      ChkError(savefile_switch_to_option(idb->self_id));
      ChkError(savefile_set_items(idb->self_id, alt_fsfl));
      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, url));
      ChkError(savefile_text_uri_or_url(idb->self_id));
      ChkError(savefile_toggle_uri_or_url(idb->self_id));
      ChkError(savefile_auto_width(idb->self_id));

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    /* Export the link the pointer was over when the menu opened */

    case ExportLink:
    {
      HStream * link = menus_document_opened_over();

      if (!link || !ISLINK(link))
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          sprintf(erb.errmess,
                  "Menu token %p is not a link or has no anchor text in savefile_to_be_shown",
                  link);

          show_error_ret(&erb);

          return 0;

        #endif
      }

      /* Set the leafname and filetype */

      ChkError(savefile_switch_to_option(idb->self_id));
      ChkError(savefile_set_items(idb->self_id, alt_el));
      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, link->anchor));
      ChkError(savefile_text_uri_or_url(idb->self_id));
      ChkError(savefile_toggle_uri_or_url(idb->self_id));
      ChkError(savefile_auto_width(idb->self_id));

      /* Remember various details about the dialogue's source */

      savefile_browser = b;
      savefile_token   = link;

      ok = 1;
    }
    break;

    case ExportPicture:
    {
      HStream    * image = menus_document_opened_over();
      const char * src   = NULL;

      ChkError(savefile_switch_to_radios(idb->self_id));
      ChkError(savefile_set_items(idb->self_id, alt_ep));

      if (
           !image ||
           !
           (
             (image->style & IMG) ||
             (
               image->tagno == TAG_INPUT &&
               HtmlINPUTtype(image) == inputtype_IMAGE
             )
             ||
             (
               ISOBJECT(image) &&
               object_token_is_image(b, image)
             )
           )
         )
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          sprintf(erb.errmess,
                  "Menu token %p is not an image in savefile_to_be_shown",
                  image);

          show_error_ret(&erb);

          return 0;

        #endif
      }

      if (image->style & IMG)             src = image->src;
      else if (image->tagno == TAG_INPUT) src = HtmlINPUTsrc(image);
      else                                src = HtmlOBJECTdata(image);

      ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, (char *) src));
      ChkError(savefile_text_sprite_or_original(idb->self_id));
      ChkError(savefile_toggle_sprite_or_original(idb->self_id, src));
      ChkError(savefile_auto_width(idb->self_id));

      /* Remember various details about the dialogue's source */

      savefile_browser = b;
      savefile_token   = image;

      ok = 1;
    }
    break;

    case ExportBackground:
    {
      char src[Limits_URL];

      ChkError(savefile_switch_to_radios(idb->self_id));
      ChkError(savefile_set_items(idb->self_id, alt_eb));

      if (b->background_image == -1)
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          StrNCpy0(erb.errmess,
                   "There is no background image on this page in savefile_to_be_shown");

          show_error_ret(&erb);

          return 0;

        #endif
      }

      *src = 0;
      image_get_background_image_url(b, src, sizeof(src));

      if (!*src) ChkError(savefile_set_leafname         (idb->self_id, SaveFileWrit, lookup_token("BackName:Background",0,0)));
      else       ChkError(savefile_set_leafname_from_url(idb->self_id, SaveFileWrit, src));

      ChkError(savefile_text_sprite_or_original(idb->self_id));
      ChkError(savefile_toggle_sprite_or_original(idb->self_id, src));
      ChkError(savefile_auto_width(idb->self_id));

      savefile_browser = b;
      savefile_token   = NULL;

      ok = 1;
    }
    break;

    #ifdef TRACE

      default:
      {
        erb.errnum = 0;
        StrNCpy0(erb.errmess,
                 "Save dialogue origin not understood in savefile_to_be_shown");

        show_error_ret(&erb);

        return 0;
      }
      break;

    #endif
  }

  /* If we were successful, install relevant event handlers etc., assuming */
  /* this is the first time the dialogue was opened.                       */

  if (ok)
  {
    parent_component   = pc;
    ancestor_id        = b->self_id;

    if (first_time)
    {
      window_id = idb->self_id;

      ChkError(event_register_toolbox_handler(window_id, Draggable_DragEnded, savefile_drag_ended,     NULL));
      ChkError(event_register_toolbox_handler(window_id, ESaveFileOK,         savefile_ok,             NULL));
      ChkError(event_register_toolbox_handler(window_id, ESaveFileCancel,     savefile_cancel,         NULL));
      ChkError(event_register_toolbox_handler(window_id, ESaveFileOption,     savefile_option_changed, NULL));
      ChkError(event_register_toolbox_handler(window_id, ESaveFileRadio,      savefile_radio_changed,  NULL));
    }
  }

  return 1;
}

/*************************************************/
/* savefile_set_leafname()                       */
/*                                               */
/* Sets the leafname in the Save File dialogue   */
/* to a given value, preserving whatever path    */
/* component may already have been present.      */
/* Any last save path recorded by the save       */
/* routines in Save.c will take precedence over  */
/* an existing dialogue path, though.            */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Component ID of the writable      */
/*             gadget;                           */
/*                                               */
/*             Pointer to a null terminated leaf */
/*             to work with.                     */
/*************************************************/

_kernel_oserror * savefile_set_leafname(ObjectId object, ComponentId component, char * leaf)
{
  char       * dot;
  char         path[Limits_OS_Pathname];
  const char * last;

  if (!leaf) return NULL;

  /* Is there an existing last pathname we should use? */

  last = save_return_last_path();

  if (last && *last)
  {
    StrNCpy0(path, last);
  }
  else
  {
    /* See what filename is already in the dialogue */

    path[0] = 0; /* Important so that when we strcat the leafname later, */
                 /* things will work even if the writable holds no text. */

    RetError(writablefield_get_value(0,
                                     object,
                                     component,
                                     path,
                                     sizeof(path),
                                     NULL));
    path[sizeof(path) - 1] = 0;
  }

  /* If there's a leafname here, force a terminator in place of the '.' */
  /* - otherwise, want to only have the leaf, so clear the string.      */

  dot = strrchr(path, '.');

  if (dot) *(++dot) = 0;
  else *path = 0;

  /* Can we fit the leaf in? */

  if (strlen(path) + strlen(leaf) + 1 > sizeof(path))
  {
    /* No, can we just fit in the leaf? If not, leave */
    /* everything alone...                            */

    if (strlen(leaf) + 1 <= sizeof(path)) strcpy(path, leaf);
  }
  else strcat(path, leaf);

  /* Set the value */

  return writablefield_set_value(0,
                                 object,
                                 component,
                                 path);
}

/*************************************************/
/* savefile_set_leafname_from_url()              */
/*                                               */
/* As savefile_set_leafname, but works out the   */
/* leaf from a given URL.                        */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Component ID of the writable      */
/*             gadget;                           */
/*                                               */
/*             Pointer to a null terminated URL  */
/*             to work with.                     */
/*************************************************/

_kernel_oserror * savefile_set_leafname_from_url(ObjectId object, ComponentId component, char * url)
{
  char leaf[Limits_OS_Pathname];

  /* Get a leafname */

  urlutils_leafname_from_url(url, leaf, sizeof(leaf));

  /* Use it */

  return savefile_set_leafname(object, component, leaf);
}

/*************************************************/
/* savefile_set_filetype()                       */
/*                                               */
/* Sets the sprite of the draggable object in    */
/* the Save File dialogue according to the given */
/* filetype, and records that filetype in        */
/* savefile_type (unless asked not to).          */
/*                                               */
/* If the sprite cannot be found in the Wimp     */
/* sprite pool, 'file_xxx' is used instead.      */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Component ID of the draggable     */
/*             gadget;                           */
/*                                               */
/*             The filetype;                     */
/*                                               */
/*             1 to *not* set savefile_type to   */
/*             the filetype - this would be used */
/*             if (for example) this function    */
/*             was being called for a dialogue   */
/*             similar to, but not the same as   */
/*             the Save File dialogue (e.g. Save */
/*             Object, where several can exist   */
/*             at once); if for a 'genuine' Save */
/*             File dialogue, you *must* pass    */
/*             zero here.                        */
/*************************************************/

_kernel_oserror * savefile_set_filetype(ObjectId object, ComponentId component, int type, int dont_record)
{
  char sprite[Limits_OS_SpriteName];
  int  len;

  if (dont_record || savefile_type != type)
  {
    if (!dont_record) savefile_type = type;

    /* Will it fit in the buffer? */

    len = utils_len_printf("file_%03x", type);

    /* If so, build the sprite name and tell the gadget to use that sprite */

    if (len < sizeof(sprite))
    {
      sprintf(sprite, "file_%03x", type);

      if (
           !_swix(Wimp_SpriteOp,
                  _IN(0) | _IN(2),

                  0x18, /* Select sprite */
                  sprite)
         )
      {
        /* If it has been found, use this sprite */

        return draggable_set_sprite(0,
                                    object,
                                    component,
                                    sprite);
      }
      else
      {
        /* Otherwise use the generic 'file_xxx' instead */

        return draggable_set_sprite(0,
                                    object,
                                    component,
                                    "file_xxx");
      }
    }
  }

  return NULL;
}

/*************************************************/
/* savefile_drag_ended()                         */
/*                                               */
/* Handle Draggable_DragEnded events from the    */
/* Save File dialogue draggable sprite gadget.   */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_drag_ended(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  DraggableDragEndedEvent * drag   = (DraggableDragEndedEvent *) event;
  protocols_saving          saving = protocols_saving_nothing;
  int                       size   = 4096; /* More or less arbitrary */
  int                       alt    = savefile_alternative_selected();
  WimpGetPointerInfoBlock   info;
  int                       window_handle;
  char                      path[Limits_OS_Pathname];
  char                    * leaf;
  void                    * extra  = savefile_token;

  /* If the user dragged back to the save dialogue, do nothing */

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

  if (window_handle == drag->window_handle) return 1;

  /* Get the pathname from the Save File dialogue. */

  ChkError(writablefield_get_value(0,
                                   window_id,
                                   SaveFileWrit,
                                   path,
                                   sizeof(path),
                                   NULL));

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

  /* Point to the leafname component */

  leaf = strrchr(path, '.');
  if (!leaf) leaf = path;
  else       leaf ++;

  /* Work out the estimated data size */

  switch (parent_component)
  {
    /* Size of an HTML file */

    case FileSaveFrame:
    case FileSaveParent:
    case FileSaveAncestor:
    {
      saving = protocols_saving_document_source;

      if (is_known_browser(savefile_browser)) size = save_source_size(savefile_browser);
      else                                    size = 0;
    }
    break;

    /* Size of a URI file for a location */

    case FileSaveFrameLocation:
    case FileSaveParentLocation:
    case FileSaveAncestorLocation:
    {
      char * url   = browser_current_url  (savefile_browser);
      char * title = browser_current_title(savefile_browser);

      if (!url) url = " ";

      saving = protocols_saving_frame_location;
      size   = save_uri_size(url, title, alt);
    }
    break;

    /* Size of a text file */

    case ExportAsText:
    {
      saving = protocols_saving_document_as_text;

      if (is_known_browser(savefile_browser)) size = savetext_text_size(savefile_browser);
      else                                    size = 0;
    }
    break;

    /* Size of a Draw file */

    case ExportAsDraw:
    {
      saving = protocols_saving_document_as_draw;

      if (is_known_browser(savefile_browser)) size = savedraw_draw_size(savefile_browser, alt);
      else                                    size = 0;
    }
    break;

    /* Size of a URI file */

    case ExportLink:
    {
      saving = protocols_saving_link;

      if (savefile_token && savefile_token->anchor) size = save_uri_size(savefile_token->anchor, NULL, alt);
      else                                          size = 0;
    }
    break;

    /* Size of an image to export */

    case ExportPicture:
    {
      saving = protocols_saving_image_sprite;

      if (savefile_token && is_known_browser(savefile_browser))
      {
        if (!alt) size = image_sprite_size(savefile_browser, savefile_token);
      }
      else size = 0;
    }
    break;

    /* Save a background image */

    case ExportBackground:
    {
      saving = protocols_saving_image_sprite;

      if (is_known_browser(savefile_browser))
      {
        if (!alt) size = image_sprite_size(savefile_browser, NULL);
      }
      else size = 0;
    }
    break;

    #ifndef REMOTE_HOTLIST

      /* File size of the entire hotlist */

      case HotlistSaveHotlist:
      {
        saving = protocols_saving_entire_hotlist;
      }
      break;

      /* Size of an object from the hotlist - a URL, directory, */
      /* or general selection                                   */

      case MiscHotlistSaveObject:
      {
        hotlist_item * source = hotlist_find_selected_item();
        unsigned int   items  = hotlist_count_selected_items();

        if (items && source)
        {
          if (items == 1 && source->type == hl_url)
          {
            saving = protocols_saving_hotlist_entry;
            extra  = (void *) source;
            size   = save_uri_size(source->data.url, source->name, alt);
          }
          else saving = protocols_saving_hotlist_selection;
        }
      }
      break;

    #endif

    case HistorySaveLocal:
    {
      saving = protocols_saving_local_history;
      size   = 0;
    }
    break;

    case HistorySaveGlobal:
    {
      saving = protocols_saving_global_history;
      size   = 0;
    }
    break;

    /* For others, could leave size as 4096 - *but must set 'saving' appropriately* */
  }

  /* Send out the DataSave message */

  info.x             = drag->x;
  info.y             = drag->y;
  info.window_handle = drag->window_handle;
  info.icon_handle   = drag->icon_handle;

  ChkError(protocols_atats_send_data_save(savefile_browser,
                                          extra,
                                          leaf,
                                          size,
                                          savefile_type,
                                          saving,
                                          &info));
  return 1;
}

/*************************************************/
/* savefile_ok()                                 */
/*                                               */
/* Handles clicks on the 'OK' button in the      */
/* Save File dialogue.                           */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_ok(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  _kernel_oserror * e   = NULL;
  int               ok  = 0;
  int               alt = savefile_alternative_selected();
  char            * leaf;
  char              path[Limits_OS_Pathname];

  /* Get the pathname from the Save File dialogue. */

  ChkError(writablefield_get_value(0,
                                   window_id,
                                   SaveFileWrit,
                                   path,
                                   sizeof(path),
                                   NULL));

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

  /* Is this fully specified? */

  leaf = strrchr(path, '.');

  if (!leaf)
  {
    StrNCpy0(erb.errmess,
             lookup_token("GivePath:To save, drag the file icon to a directory viewer",
                          0,
                          0));

    erb.errnum = Utils_Error_Custom_Message;

    ChkError(&erb);
  }

  /* Save the file */

  switch (parent_component)
  {
    /* Save as HTML */

    case FileSaveFrame:
    case FileSaveParent:
    case FileSaveAncestor:
    {
      if (is_known_browser(savefile_browser))
      {
        e = save_save_source(path, savefile_browser);
        if (!e) ok = 1;
      }
    }
    break;

    /* Save as text */

    case ExportAsText:
    {
      if (is_known_browser(savefile_browser))
      {
        e = savetext_save_text(savefile_browser, path);
        if (!e) ok = 1;
      }
    }
    break;

    /* Save as Draw */

    case ExportAsDraw:
    {
      if (is_known_browser(savefile_browser))
      {
        e = savedraw_save_draw(savefile_browser, path, alt);
        if (!e) ok = 1;
      }
    }
    break;

    /* Save the current location or a link as a URI or URL file */

    case ExportLink:
    case FileSaveFrameLocation:
    case FileSaveParentLocation:
    case FileSaveAncestorLocation:
    {
      if (is_known_browser(savefile_browser))
      {
        if (savefile_token)
        {
          char * title = browser_current_title(savefile_browser);

          /* Save a link as a URI file */

          e = save_save_uri(path, savefile_token->anchor, title, alt);
          if (!e) ok = 1;
        }
        else
        {
          /* Save the current location as a URI file */

          char * url   = browser_current_url  (savefile_browser);
          char * title = browser_current_title(savefile_browser);

          if (!url) url = " ";

          e = save_save_uri(path, url, title, alt);
          if (!e) ok = 1;
        }
      }
    }
    break;

    /* Export a foreground image */

    case ExportPicture:
    {
      if (is_known_browser(savefile_browser))
      {
        if (!alt) e = image_export_sprite  (path, savefile_browser, savefile_token);
        else      e = image_export_original(path, savefile_browser, savefile_token);

        if (!e) ok = 1;
      }
    }
    break;

    /* Export a background image */

    case ExportBackground:
    {
      if (is_known_browser(savefile_browser))
      {
        if (!alt) e = image_export_sprite  (path, savefile_browser, NULL);
        else      e = image_export_original(path, savefile_browser, NULL);

        if (!e) ok = 1;
      }
    }
    break;

    #ifndef REMOTE_HOTLIST

      /* Save the hotlist */

      case HotlistSaveHotlist:
      {
        e = hotlist_save_hotlist(path, NULL, 0);
        if (!e) ok = 1;
      }
      break;

      /* Save an object from the hotlist - a URL, directory, */
      /* or general selection                                */

      case MiscHotlistSaveObject:
      {
        hotlist_item * source = hotlist_find_selected_item();
        unsigned int   items  = hotlist_count_selected_items();

        if (items && source)
        {
          if (items == 1 && source->type == hl_url)
          {
            e = save_save_uri(path, source->data.url, source->name, alt);

            if (!e)
            {
              ok = 1;
              e = hotlist_clear_selection();
            }
          }
          else
          {
            e = hotlist_save_hotlist(path, NULL, 1);

            if (!e)
            {
              ok = 1;
              e = hotlist_clear_selection();
            }
          }
        }
      }
      break;

    #endif

    case HistorySaveLocal:
    {
      browser_data * local_browser = savefile_browser;

      if (!is_known_browser(savefile_browser)) local_browser = NULL;

      e = history_save_as_html(path, local_browser);
      if (!e) ok = 1;
    }
    break;

    case HistorySaveGlobal:
    {
      e = history_save_as_html(path, NULL);
      if (!e) ok = 1;
    }
    break;
  }

  /* If everything is OK, close the menu */

  if (!e)
  {
    ChkError(savefile_close(0,1)); /* Make sure we've tidied up */

    _swix(Wimp_CreateMenu,
          _IN(1),

          -1);
  }

  /* Finished */

  if (e) ChkError(e);

  return 1;
}

/*************************************************/
/* savefile_cancel()                             */
/*                                               */
/* Handles clicks on the 'Cancel' button in the  */
/* Save File dialogue                            */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  /* If we were fetching, stop the fetch */

  if (savefile_browser && is_known_browser(savefile_browser))
  {
    if (savefile_browser->save_link) fetch_stop(savefile_browser, 0);
  }

  /* We don't do anything sophisticated, like */
  /* restoring previous options here, as the  */
  /* dialogue is too simple for it to be      */
  /* worthwhile. Therefore, just close it.    */

  ChkError(savefile_close(0,0));

  return 1;
}

/*************************************************/
/* savefile_close()                              */
/*                                               */
/* If the Save File dialogue is opened, this     */
/* will close it.                                */
/*                                               */
/* Parameters: An object ID, or 0. If not zero,  */
/*             the ID must match the ancestor    */
/*             recorded when the dialogue was    */
/*             opened or no action is taken.     */
/*                                               */
/*             0 to close the dialogue, 1 to do  */
/*             everything except that.           */
/*************************************************/

_kernel_oserror * savefile_close(ObjectId ancestor, int do_not_close)
{
  _kernel_oserror * e = NULL;

  if (!window_id) return NULL;
  if (ancestor && ancestor != ancestor_id) return NULL;

  /* If required, close the dialogue */

  if (!do_not_close && !e)
  {
    /* If the dialogue came from a menu tree, collapse the tree */

    if (parent_component != -1) e = _swix(Wimp_CreateMenu, _IN(1), -1);

    /* Close the dialogue */

    if (!e) e = toolbox_hide_object(0, window_id);
  }

  parent_component = -1;
  ancestor_id      = 0;

  savefile_browser = NULL;
  savefile_token   = NULL;
  savefile_type    = 0x000;

  return e;
}

/*************************************************/
/* savefile_return_dialogue_info()               */
/*                                               */
/* Returns information on the Save File dialogue */
/* and its ancestor.                             */
/*                                               */
/* Parameters: Pointer to an ObjectId, in which  */
/*             the ID of the dialogue is placed; */
/*                                               */
/*             Pointer to an ObjectId, in which  */
/*             the ID of the ancestor window is  */
/*             placed.                           */
/*                                               */
/* Returns:    See parameters list, and note     */
/*             that the returned values will be  */
/*             0 and 0 if the Save File dialogue */
/*             is closed.                        */
/*                                               */
/* Assumes:    Either pointer may be NULL.       */
/*************************************************/

void savefile_return_dialogue_info(ObjectId * window, ObjectId * ancestor)
{
  if (window)   *window   = window_id;
  if (ancestor) *ancestor = ancestor_id;
}

/*************************************************/
/* savefile_switch_to_normal()                   */
/*                                               */
/* Move the radio buttons or option buttons from */
/* view in the Save File dialogue, if either is  */
/* present.                                      */
/*                                               */
/* Parameters: Object ID of the dialogue.        */
/*************************************************/

static _kernel_oserror * savefile_switch_to_normal(ObjectId window)
{
  BBox opt, ra1, ra2;
  int  showing = 0;
  int  noopt   = 0;
  int  norad   = 0;
  int  moveby;

  /* Find the option button and/or radio gadgets */

  if (gadget_get_bbox(0, window, SaveFileOption, &opt)) noopt = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio1, &ra1)) norad = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio2, &ra2)) norad = 1;

  if      (!noopt && opt.xmin < opt.xmax - opt.xmin) showing = 1;
  else if (!norad && ra1.xmin < ra1.xmax - ra1.xmin) showing = 2;

  /* If 'showing' is zero, we're on the normal display already */

  if (!showing) goto savefile_switch_to_normal_set_extent;

  /* Otherwise, need to move things about a bit */

  if (showing == 1)
  {
    /* We're on the option button display */

    moveby = (opt.xmax - opt.xmin) * 2;

    opt.xmin += moveby;
    opt.xmax += moveby;

    RetError(gadget_move_gadget(0, window, SaveFileOption, &opt));
  }
  else
  {
    /* We're on the radio buttons display */

    moveby = (ra1.xmax - ra1.xmin) * 2;

    ra1.xmin += moveby;
    ra1.xmax += moveby;
    ra2.xmin += moveby;
    ra2.xmax += moveby;

    RetError(gadget_move_gadget(0, window, SaveFileRadio1, &ra1));
    RetError(gadget_move_gadget(0, window, SaveFileRadio2, &ra2));
  }

  /* Move the Y coordinates of the Cancel and Save buttons back */
  /* to the normal position. First, Cancel.                     */

  RetError(gadget_get_bbox(0, window, SaveFileCancel,           &ra1));
  RetError(gadget_get_bbox(0, window, SaveFileCancelMarkNormal, &ra2));

  ra1.ymin = ra2.ymin;
  ra1.ymax = ra2.ymax;

  RetError(gadget_move_gadget(0, window, SaveFileCancel, &ra1));

savefile_switch_to_normal_set_extent:

  /* Now, OK (or 'Save' or whatever) */

  RetError(gadget_get_bbox(0, window, SaveFileOK,           &ra1));
  RetError(gadget_get_bbox(0, window, SaveFileOKMarkNormal, &ra2));

  ra1.ymin = ra2.ymin;
  ra1.ymax = ra2.ymax;

  RetError(gadget_move_gadget(0, window, SaveFileOK, &ra1));

  /* Set the extent */

  RetError(savefile_set_correct_extent(window, &ra1));

  /* Finished */

  return NULL;
}

/*************************************************/
/* savefile_switch_to_option()                   */
/*                                               */
/* Move the option button into the Save File     */
/* dialogue, moving out the radio buttons if     */
/* present.                                      */
/*                                               */
/* Parameters: Object ID of the dialogue.        */
/*************************************************/

static _kernel_oserror * savefile_switch_to_option(ObjectId window)
{
  BBox opt, ra1, ra2, wri;
  int  showing = 0;
  int  noopt   = 0;
  int  norad   = 0;
  int  moveby;

  /* Find the writable gadget */

  RetError(gadget_get_bbox(0, window, SaveFileWrit, &wri));

  /* Find the option button and/or radio gadgets */

  if (gadget_get_bbox(0, window, SaveFileOption, &opt)) noopt = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio1, &ra1)) norad = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio2, &ra2)) norad = 1;

  /* If there are no option buttons, try switching to the */
  /* radios instead.                                      */

  if (noopt)
  {
    if (norad) return NULL;
    else       return savefile_switch_to_radios(window);
  }

  if      (!noopt && opt.xmin < opt.xmax - opt.xmin) showing = 1;
  else if (!norad && ra1.xmin < ra1.xmax - ra1.xmin) showing = 2;

  /* If 'showing' is 1, we're on the option display already */

  if (showing == 1) goto savefile_switch_to_option_set_extent;

  /* Move back to the normal display as a starting point */

  RetError(savefile_switch_to_normal(window));

  /* Move the option button to the same xmin as the writable */

  moveby = opt.xmin - wri.xmin;

  opt.xmin -= moveby;
  opt.xmax -= moveby;

  RetError(gadget_move_gadget(0, window, SaveFileOption, &opt));

  /* Move the Y coordinates of the Cancel and Save buttons */
  /* to the option position. First, Cancel.                */

  RetError(gadget_get_bbox(0, window, SaveFileCancel,           &ra1));
  RetError(gadget_get_bbox(0, window, SaveFileCancelMarkOption, &ra2));

  ra1.ymin = ra2.ymin;
  ra1.ymax = ra2.ymax;

  RetError(gadget_move_gadget(0, window, SaveFileCancel, &ra1));

savefile_switch_to_option_set_extent:

  /* Now, OK (or 'Save' or whatever) */

  RetError(gadget_get_bbox(0, window, SaveFileOK,           &ra1));
  RetError(gadget_get_bbox(0, window, SaveFileOKMarkOption, &ra2));

  ra1.ymin = ra2.ymin;
  ra1.ymax = ra2.ymax;

  RetError(gadget_move_gadget(0, window, SaveFileOK, &ra1));

  /* Set the extent */

  RetError(savefile_set_correct_extent(window, &ra1));

  /* Finished */

  return NULL;
}

/*************************************************/
/* savefile_switch_to_radios()                   */
/*                                               */
/* Move the radio buttons into the Save File     */
/* dialogue, moving out the option button if     */
/* present.                                      */
/*                                               */
/* Parameters: Object ID of the dialogue.        */
/*************************************************/

static _kernel_oserror * savefile_switch_to_radios(ObjectId window)
{
  BBox opt, ra1, ra2, wri;
  int  showing = 0;
  int  noopt   = 0;
  int  norad   = 0;
  int  moveby;

  /* Find the writable gadget */

  RetError(gadget_get_bbox(0, window, SaveFileWrit, &wri));

  /* Find the option button and/or radio gadgets */

  if (gadget_get_bbox(0, window, SaveFileOption, &opt)) noopt = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio1, &ra1)) norad = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio2, &ra2)) norad = 1;

  /* If there are no radio buttons, try switching to the option */
  /* button instead.                                            */

  if (norad)
  {
    if (noopt) return NULL;
    else       return savefile_switch_to_option(window);
  }

  if      (!noopt && opt.xmin < opt.xmax - opt.xmin) showing = 1;
  else if (!norad && ra1.xmin < ra1.xmax - ra1.xmin) showing = 2;

  /* If 'showing' is 2, we're on the radios display already */

  if (showing == 2) goto savefile_switch_to_radios_set_extent;

  /* Move back to the normal display as a starting point */

  RetError(savefile_switch_to_normal(window));

  /* Move the radio buttons to the same xmin as the writable */

  moveby = ra1.xmin - wri.xmin;

  ra1.xmin -= moveby;
  ra1.xmax -= moveby;

  moveby = ra2.xmin - wri.xmin;

  ra2.xmin -= moveby;
  ra2.xmax -= moveby;

  RetError(gadget_move_gadget(0, window, SaveFileRadio1, &ra1));
  RetError(gadget_move_gadget(0, window, SaveFileRadio2, &ra2));

  /* Move the Y coordinates of the Cancel and Save buttons */
  /* to the radios position. First, Cancel.                */

  RetError(gadget_get_bbox(0, window, SaveFileCancel,           &ra1));
  RetError(gadget_get_bbox(0, window, SaveFileCancelMarkRadios, &ra2));

  ra1.ymin = ra2.ymin;
  ra1.ymax = ra2.ymax;

  RetError(gadget_move_gadget(0, window, SaveFileCancel, &ra1));

savefile_switch_to_radios_set_extent:

  /* Now, OK (or 'Save' or whatever) */

  RetError(gadget_get_bbox(0, window, SaveFileOK,           &ra1));
  RetError(gadget_get_bbox(0, window, SaveFileOKMarkRadios, &ra2));

  ra1.ymin = ra2.ymin;
  ra1.ymax = ra2.ymax;

  RetError(gadget_move_gadget(0, window, SaveFileOK, &ra1));

  /* Set the extent */

  RetError(savefile_set_correct_extent(window, &ra1));

  /* Finished */

  return NULL;
}

/*************************************************/
/* savefile_set_items()                          */
/*                                               */
/* Set the radio buttons or option button in a   */
/* Save File dialogue, if present, to the given  */
/* state.                                        */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             1 to tick the option box or       */
/*             select the second of the two      */
/*             radios - 0 to untick the option   */
/*             box or select the first of the    */
/*             two radios.                       */
/*************************************************/

static _kernel_oserror * savefile_set_items(ObjectId window, int selected)
{
  optionbutton_set_state(0, window, SaveFileOption, selected);

  radiobutton_set_state (0, window, SaveFileRadio1, !selected);
  radiobutton_set_state (0, window, SaveFileRadio2, selected);

  return NULL;
}

/*************************************************/
/* savefile_option_changed()                     */
/*                                               */
/* Called when the option button in a Save File  */
/* dialogue (if any) is toggled.                 */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_option_changed(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  return savefile_item_changed(idb);
}

/*************************************************/
/* savefile_radio_changed()                      */
/*                                               */
/* Called when the radio buttons in a Save File  */
/* dialogue (if any) are toggled.                */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int savefile_radio_changed(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  return savefile_item_changed(idb);
}

/*************************************************/
/* savefile_item_changed()                       */
/*                                               */
/* Called when either one of the two radios that */
/* can appear in a Save File dialogue, or the    */
/* option button that can appear, is used.       */
/*                                               */
/* Parameters: Pointer an IdBlock from the       */
/*             Toolbox event that was raised by  */
/*             the button(s).                    */
/*************************************************/

static int savefile_item_changed(IdBlock * idb)
{
  browser_data * b;
  ComponentId    pc  = idb->parent_component;
  int            alt = savefile_alternative_selected();

  /* Where did we come from? */

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

//  /* If the parent ID is the same as a toolbar, this was */
//  /* from a toolbar button - in which case, use a parent */
//  /* ID of FileSaveParent, as that is what we actually    */
//  /* want to do.                                         */
//
//  if (
//       idb->parent_id == toolbars_get_upper(b) ||
//       idb->parent_id == toolbars_get_lower(b)
//     )
//     pc = FileSaveParent;

  /* Actions will vary according to the parent component */

  switch (pc)
  {
    case FileSaveFrame:
    case FileSaveParent:
    case FileSaveAncestor:
    case HotlistSaveHotlist:
    case HistorySaveLocal:
    case HistorySaveGlobal:
    case ExportAsText:
    {
      /* Nothing to do at the moment, shouldn't be able to access */
      /* the option button gadget anyway...                       */

#ifndef REMOTE_HOTLIST

  savefile_item_changed_gripe:

#endif

      #ifdef TRACE

        erb.errnum = Utils_Error_Custom_Normal;

        sprintf(erb.errmess,
                "Shouldn't be using the option button in SaveFile dialogue for parent component 0x%x in savefile_item_changed!",
                pc);

        show_error_ret(&erb);

      #endif

      return 1;
    }
    break;

    #ifndef REMOTE_HOTLIST

      case MiscHotlistSaveObject:
      {
        hotlist_item * source = hotlist_find_selected_item();
        unsigned int   items  = hotlist_count_selected_items();

        alt_mhso = alt;

        if (items && source)
        {
          if (items == 1 && source->type == hl_url)
          {
            /* Saving a single hotlist item, so can do this as */
            /* a URI or ANT URL file.                          */

            ChkError(savefile_toggle_uri_or_url(idb->self_id));
          }
          else
          {
            /* Saving multiple items as HTML - no defined action */
            /* for the option button.                            */

            goto savefile_item_changed_gripe;
          }
        }
      }
      break;

    #endif

    case ExportAsDraw:
    {
      alt_ead = alt;

      ChkError(savefile_toggle_without_backgrounds_or_with(idb->self_id));
    }
    break;

    case FileSaveFrameLocation:
    case FileSaveParentLocation:
    case FileSaveAncestorLocation:
    case ExportLink:
    {
      if (pc == ExportLink) alt_el   = alt;
      else                  alt_fsfl = alt;

      ChkError(savefile_toggle_uri_or_url(idb->self_id));
    }
    break;

    case ExportPicture:
    {
      HStream    * image = menus_document_opened_over();
      const char * src   = NULL;

      alt_ep = alt;

      if (image->style & IMG)             src = image->src;
      else if (image->tagno == TAG_INPUT) src = HtmlINPUTsrc(image);
      else                                src = HtmlOBJECTdata(image);

      ChkError(savefile_toggle_sprite_or_original(idb->self_id, src));
    }
    break;

    case ExportBackground:
    {
      char src[Limits_URL];

      alt_eb = alt;

      if (b->background_image == -1)
      {
        #ifndef TRACE

          return 0;

        #else

          erb.errnum = Utils_Error_Custom_Normal;

          StrNCpy0(erb.errmess,
                   "There is no background image on this page in savefile_item_changed.");

          show_error_ret(&erb);

          return 0;

        #endif
      }

      *src = 0;
      image_get_background_image_url(b, src, sizeof(src));

      ChkError(savefile_toggle_sprite_or_original(idb->self_id, src));
    }
    break;

    #ifdef TRACE

      default:
      {
        erb.errnum = 0;
        StrNCpy0(erb.errmess,
                 "Save dialogue origin not understood in savefile_item_changed.");

        show_error_ret(&erb);

        return 0;
      }
      break;

    #endif
  }

  return 1;
}

/*************************************************/
/* savefile_alternative_selected()               */
/*                                               */
/* If the second of the two radios in a Save     */
/* File dialogue is selected, or if there is     */
/* an option button present instead and it is    */
/* itself selected, returns 1. Otherwise returns */
/* 0.                                            */
/*                                               */
/* Returns:    See above.                        */
/*                                               */
/* Assumes:    window_id holds the Object ID of  */
/*             a relevant Save file dialogue.    */
/*************************************************/

int savefile_alternative_selected(void)
{
  int  alt_set    = 0;
  int  try_radios = 0;
  BBox box;

  if (!window_id) return 0;

  /* Need to work out whether the option button or radio buttons */
  /* are present, and if so, which is visible at the moment and  */
  /* use that.                                                   */

  if (!gadget_get_bbox(0, window_id, SaveFileOption, &box))
  {
    /* If we can get the BBox, is it visible right now? */

    if (box.xmin >= box.xmax - box.xmin) try_radios = 1; /* No, so signal we should try the radios */
    else
    {
      /* Get the option button state */

      if (optionbutton_get_state(0, window_id, SaveFileOption, &alt_set)) alt_set = 0;
    }
  }

  /* Couldn't get the option button BBox, so try the radios */

  else try_radios = 1;

  /* If required, do similar things to the above with the radio buttons */

  if (try_radios)
  {
    if (!gadget_get_bbox(0, window_id, SaveFileRadio2, &box))
    {
      if (box.xmin < box.xmax - box.xmin)
      {
        if (radiobutton_get_state(0, window_id, SaveFileRadio2, &alt_set, NULL)) alt_set = 0;
      }
    }
  }

  return alt_set;
}

/*************************************************/
/* savefile_text_uri_or_url()                    */
/*                                               */
/* Set the text in the radio buttons or option   */
/* button of a Save File dialogue giving the     */
/* choice of saving a URL as a URI or ANT URL    */
/* file.                                         */
/*                                               */
/* Parameters: Object ID of the relevant Save    */
/*             File dialogue.                    */
/*************************************************/

static _kernel_oserror * savefile_text_uri_or_url(ObjectId window)
{
  /* These can all fail silently if the gadget isn't present */

  optionbutton_set_label(0,
                         window,
                         SaveFileOption,
                         lookup_token("SaveFileOpURL:Save URL file",0,0));

  radiobutton_set_label (0,
                         window,
                         SaveFileRadio1,
                         lookup_token("SaveFileRaURI:URI format",0,0));

  radiobutton_set_label (0,
                         window,
                         SaveFileRadio2,
                         lookup_token("SaveFileRaURL:URL format",0,0));

  return 0;
}

/*************************************************/
/* savefile_toggle_uri_or_url()                  */
/*                                               */
/* Called when the option to save a URL as a URI */
/* or ANT URL file is changed.                   */
/*                                               */
/* Parameters: Object ID of the relevant Save    */
/*             File dialogue.                    */
/*************************************************/

static _kernel_oserror * savefile_toggle_uri_or_url(ObjectId window)
{
  int set_url = savefile_alternative_selected();

  if (set_url) return savefile_set_filetype(window, SaveFileDrag, FileType_URL, 0);
  else         return savefile_set_filetype(window, SaveFileDrag, FileType_URI, 0);
}

/*************************************************/
/* savefile_text_sprite_or_original()            */
/*                                               */
/* Set the text in the radio buttons or option   */
/* button of a Save File dialogue giving the     */
/* choice of saving an image as a sprite or in   */
/* its original format.                          */
/*                                               */
/* Parameters: Object ID of the relevant Save    */
/*             File dialogue.                    */
/*************************************************/

static _kernel_oserror * savefile_text_sprite_or_original(ObjectId window)
{
  /* These can all fail silently if the gadget isn't present */

  optionbutton_set_label(0,
                         window,
                         SaveFileOption,
                         lookup_token("SaveFileOpOriginal:Save original image",0,0));

  radiobutton_set_label (0,
                         window,
                         SaveFileRadio1,
                         lookup_token("SaveFileRaSprite:Sprite format",0,0));

  radiobutton_set_label (0,
                         window,
                         SaveFileRadio2,
                         lookup_token("SaveFileRaOriginal:Original format",0,0));

  return 0;
}

/*************************************************/
/* savefile_toggle_sprite_or_original()          */
/*                                               */
/* Called when the option to save an image as a  */
/* sprite or in its original format is changed.  */
/*                                               */
/* Parameters: Object ID of the relevant Save    */
/*             File dialogue.                    */
/*************************************************/

static _kernel_oserror * savefile_toggle_sprite_or_original(ObjectId window, const char * src)
{
  int set_original = savefile_alternative_selected();

  if (!set_original) return savefile_set_filetype(window, SaveFileDrag, FileType_SPR, 0);
  else
  {
    const char * dot = NULL;
    int          filetype;

    /* Try and determine the filetype from the URL of the image */

    if (src) dot = strrchr(src, '.');

    if (dot)
    {
      if (mimemap_extension_to_riscos(dot, &filetype)) filetype = FileType_DATA;
    }
    else filetype = FileType_DATA;

    return savefile_set_filetype(window, SaveFileDrag, filetype, 0);
  }
}

/*************************************************/
/* savefile_text_without_backgrounds_or_with()   */
/*                                               */
/* Set the text in the radio buttons or option   */
/* button of a Save File dialogue giving the     */
/* choice of saving a Draw file with or without  */
/* a background image.                           */
/*                                               */
/* Parameters: Object ID of the relevant Save    */
/*             File dialogue.                    */
/*************************************************/

static _kernel_oserror * savefile_text_without_backgrounds_or_with(ObjectId window)
{
  /* These can all fail silently if the gadget isn't present */

  optionbutton_set_label(0,
                         window,
                         SaveFileOption,
                         lookup_token("SaveFileOpBack:With background",0,0));

  radiobutton_set_label (0,
                         window,
                         SaveFileRadio1,
                         lookup_token("SaveFileRaNoBack:No background",0,0));

  radiobutton_set_label (0,
                         window,
                         SaveFileRadio2,
                         lookup_token("SaveFileRaBack:With background",0,0));

  return 0;
}

/*************************************************/
/* savefile_toggle_without_backgrounds_or_with() */
/*                                               */
/* Called when the option to save as a Draw file */
/* with or without a background image is         */
/* changed.                                      */
/*                                               */
/* Parameters: Object ID of the relevant Save    */
/*             File dialogue.                    */
/*************************************************/

static _kernel_oserror * savefile_toggle_without_backgrounds_or_with(ObjectId window)
{
  /* There's very little to do here presently */

  return savefile_set_filetype(window, SaveFileDrag, FileType_DRAW, 0);
}

/*************************************************/
/* savefile_set_correct_extent()                 */
/*                                               */
/* Ensure the width and height (in terms of the  */
/* visible area and actual extent) is correct    */
/* according to the presence of:                 */
/*                                               */
/* SaveFileRightGapMarker                        */
/* SaveFileBottomGapMarker                       */
/*                                               */
/* Parameters: Object ID of the dialogue to      */
/*             alter;                            */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             new size (xmax - xmin, ymax -     */
/*             ymin).                            */
/*************************************************/

static _kernel_oserror * savefile_set_correct_extent(ObjectId window, BBox * ra1)
{
  WimpGetWindowStateBlock state;
  int                     open;
  BBox                    opt, ra2;

  /* Find out what the window extent should be */

  RetError(window_get_extent(0, window, &opt));

  /* Set the window extent ymin to the extent ymax minus the  */
  /* height; the height being the ymin of the OK button minus */
  /* the height of the SaveFileBottomGapMarker gadget. Set    */
  /* the width on a similar basis.                            */

  RetError(gadget_get_bbox(0, window, SaveFileBottomGapMarker, &ra2));
  opt.ymin = opt.ymax + ra1->ymin - (ra2.ymax - ra2.ymin);

  RetError(gadget_get_bbox(0, window, SaveFileRightGapMarker, &ra2));
  opt.xmax = opt.xmin + ra1->xmax + (ra2.xmax - ra2.xmin);

  RetError(window_set_extent(0, window, &opt));

  /* We need to ensure the window is opened to full size, but it may be */
  /* already open - so get the state first.                             */

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

  /* Remember if it is open or not */

  open = state.flags & WimpWindow_Open;

  /* Set the visible area to match the extent, extending */
  /* it to the right and downwards.                      */

  state.visible_area.xmax = state.visible_area.xmin + opt.xmax - opt.xmin;
  state.visible_area.ymin = state.visible_area.ymax - opt.ymax + opt.ymin;

  /* Show the window with these coordinates */

  RetError(wimp_open_window((WimpOpenWindowBlock *) &state));

  /* If it wasn't already open, close the window */

  if (!open) RetError(wimp_close_window(&state.window_handle));

  /* Finished. */

  return NULL;
}

/*************************************************/
/* savefile_auto_width()                         */
/*                                               */
/* If the SaveFileAutoWidthMarker gadget         */
/* (SaveFile.h) is present, check the width of   */
/* various text items in the Save File dialogue  */
/* and auto-width other components to make the   */
/* dialogue fit the widest. The following        */
/* components are assumed to be present:         */
/*                                               */
/* SaveFileRightGapMarker                        */
/* SaveFileBottomGapMarker                       */
/*                                               */
/* Parameters: Object ID of the dialogue to      */
/*             auto-width.                       */
/*************************************************/

static _kernel_oserror * savefile_auto_width(ObjectId window)
{
  BBox min,    max;
  BBox optbox, ra1box, ra2box, miscbox;

  char opt[Limits_SaveFile_Option];
  char ra1[Limits_SaveFile_Radios];
  char ra2[Limits_SaveFile_Radios];

  int  showing = 0;
  int  noopt   = 0;
  int  norad   = 0;

  int  width;
  int  margin;

  /* If we haven't got the auto width marker, we shouldn't resize */

  if (gadget_get_bbox(0, window, SaveFileAutoWidthMarker, &min)) return NULL;

  /* Use the right hand gap marker as a margin indicator */

  RetError(gadget_get_bbox(0, window, SaveFileRightGapMarker, &max));
  margin = (max.xmax - max.xmin) * 2;

  /* Use the bottom gap marker as a maximum width indicator */

  RetError(gadget_get_bbox(0, window, SaveFileBottomGapMarker, &max));

  /* Get the option and radio button label texts */

  if (
       optionbutton_get_label(0,
                              window,
                              SaveFileOption,
                              opt,
                              sizeof(opt),
                              NULL)
     )
     *opt = '\0';

  else opt[sizeof(opt) - 1] = '\0';

  if (
       radiobutton_get_label (0,
                              window,
                              SaveFileRadio1,
                              ra1,
                              sizeof(ra1),
                              NULL)
     )
     *ra1 = '\0';

  else ra1[sizeof(ra1) - 1] = '\0';

  if (
       radiobutton_get_label (0,
                              window,
                              SaveFileRadio2,
                              ra2,
                              sizeof(ra2),
                              NULL)
     )
     *ra2 = '\0';

  else ra2[sizeof(ra2) - 1] = '\0';

  /* Find the bounding boxes of the option and radio */
  /* button gadgets to see what is visible right now */

  if (gadget_get_bbox(0, window, SaveFileOption, &optbox)) noopt = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio1, &ra1box)) norad = 1;
  if (gadget_get_bbox(0, window, SaveFileRadio2, &ra2box)) norad = 1;

  if      (!noopt && optbox.xmin < optbox.xmax - optbox.xmin) showing = 1;
  else if (!norad && ra1box.xmin < ra1box.xmax - ra1box.xmin) showing = 2;

  /* Minimum width comes from the auto width marker,  */
  /* but this includes the margin width which we want */
  /* to exclude for the moment.                       */

  width = min.xmax - min.xmin - margin;

  if (showing == 1)
  {
    int text;

    /* Showing the option button, so work out width of it */

    RetError(utils_text_width(opt, &text, 0));

    text += 64; /* Hack :-) - overestimate of the option button size itself plus gap between button and label */

    if (text > width) width = text;
  }
  else if (showing == 2)
  {
    int text1, text2;

    /* Showing the radio buttons, so work out width of them */

    RetError(utils_text_width(ra1, &text1, 0));
    RetError(utils_text_width(ra2, &text2, 0));

    text1 += 64; /* Magic number as for 'showing == 1' case above */
    text2 += 64;

    if (text1 > width) width = text1;
    if (text2 > width) width = text2;
  }

  if (width > max.xmax - max.xmin - margin) width = max.xmax - max.xmin - margin;

  /* We now know how wide things are to be. The writable gadget   */
  /* and option or radio buttons should be 'width', the draggable */
  /* icon should just move to stay centred, to start with.        */

  if (!noopt)
  {
    optbox.xmax = optbox.xmin + width;

    RetError(gadget_move_gadget(0, window, SaveFileOption, &optbox));
  }

  if (!norad)
  {
    ra1box.xmax = ra1box.xmin + width;
    ra2box.xmax = ra2box.xmin + width;

    RetError(gadget_move_gadget(0, window, SaveFileRadio1, &ra1box));
    RetError(gadget_move_gadget(0, window, SaveFileRadio2, &ra2box));
  }

  RetError(gadget_get_bbox(0, window, SaveFileWrit, &miscbox));

  miscbox.xmax = miscbox.xmin + width;

  RetError(gadget_move_gadget(0, window, SaveFileWrit, &miscbox));

  {
    int drag;

    RetError(gadget_get_bbox(0, window, SaveFileDrag, &miscbox));

    drag = miscbox.xmax - miscbox.xmin;

    miscbox.xmin = (width + margin - drag) / 2;
    miscbox.xmax = miscbox.xmin + drag;

    RetError(gadget_move_gadget(0, window, SaveFileDrag, &miscbox));
  }

  /* The OK and Cancel buttons - these have a 'margin' gap between */
  /* them, and the OK button should be 8 wider than Cancel (it's   */
  /* the default action button, and has a thicker border). 16 is   */
  /* the technically correct value, but it doesn't look good!      */

  {
    int button;

    button = (width - margin - 8) / 2;

    RetError(gadget_get_bbox(0, window, SaveFileCancel, &ra1box));
    RetError(gadget_get_bbox(0, window, SaveFileOK,     &ra2box));

    ra1box.xmax = ra1box.xmin + button;

    ra2box.xmin = ra1box.xmax + margin;
    ra2box.xmax = ra2box.xmin + button + 8;

    RetError(gadget_move_gadget(0, window, SaveFileCancel, &ra1box));
    RetError(gadget_move_gadget(0, window, SaveFileOK,     &ra2box));
  }

  /* Finally, set the window width */

  {
    WimpGetWindowStateBlock state;
    int                     open;

    /* Find out what the window vertical extent should be */

    RetError(window_get_extent(0, window, &miscbox));

    /* Set the width */

    miscbox.xmax = miscbox.xmin + width + margin;

    RetError(window_set_extent(0, window, &miscbox));

    /* We need to ensure the window is opened to full size, but it may be */
    /* already open - so get the state first.                             */

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

    /* Remember if it is open or not */

    open = state.flags & WimpWindow_Open;

    /* Set the visible area to match the extent, extending */
    /* it to the right and downwards.                      */

    state.visible_area.xmax = state.visible_area.xmin + miscbox.xmax - miscbox.xmin;
    state.visible_area.ymin = state.visible_area.ymax - miscbox.ymax + miscbox.ymin;

    /* Show the window with these coordinates */

    RetError(wimp_open_window((WimpOpenWindowBlock *) &state));

    /* If it wasn't already open, close the window */

    if (!open) RetError(wimp_close_window(&state.window_handle));
  }

  /* Finished. */

  return NULL;
}