/* 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   : Windows.c                              */
/*                                                 */
/* Purpose: Window related functions for the       */
/*          browser (open, close, create, etc.).   */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 20-Nov-96: Created.                    */
/*          15-Mar-97: Split up to form Browser.c. */
/***************************************************/

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

#include "swis.h"
#include "flex.h"

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

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

#include "Dialler.h"
#include "NestWimp.h"

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

#include "Browser.h"
#include "ChoiceDefs.h"
#include "Fetch.h" /* (Which itself includes URLstat.h) */
#include "FetchHTML.h"
#include "FetchPage.h"
#include "FontManage.h"
#include "Forms.h"
#include "Frames.h"
#include "Handlers.h"
#include "History.h"
#include "Images.h"
#include "Main.h"
#include "Memory.h"
#include "Menus.h"
#include "Meta.h"
#include "Object.h"
#include "OpenURL.h"
#include "PlugIn.h"
#include "Printing.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Tables.h"
#include "Toolbars.h"
#include "URLutils.h"

#include "Windows.h"

/* Static function prototypes */

static void windows_check_reformat (browser_data * b, BBox * size, int old_width);

/* Locals */

static int title_height   = 0;
static int vscroll_width  = 0;
static int hscroll_height = 0;

/*************************************************/
/* windows_check_reformat()                      */
/*                                               */
/* After resizing, checks whether the contents   */
/* of a given browser need reformatting and      */
/* ensures tool presence is up to date.          */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the browser;          */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             new visible area of the window;   */
/*                                               */
/*             The old display_width field of    */
/*             the browser_data struct to        */
/*             compare against the width defined */
/*             by the given BBox.                */
/*************************************************/

static void windows_check_reformat(browser_data * b, BBox * size, int old_width)
{
  /* If the width has changed, need to reformat. Notice that the  */
  /* horizontal extent is forced up to at least a certain minimum */
  /* value, but is never shrunk.                                  */

  if (size->xmax - size->xmin != old_width)
  {
    int h1, h2;

    b->display_extent = b->display_width = size->xmax - size->xmin;
    if (b->display_extent < MinimumWidth) b->display_extent = MinimumWidth;

    h1 = toolbars_button_height(b) + toolbars_url_height(b);
    h2 = toolbars_status_height(b);

    if (h1) h1 += wimpt_dy();
    if (h2) h2 += wimpt_dy();

    b->display_height = size->ymax - size->ymin - h1 - h2;

    if (b->ancestor || b->full_screen) windows_set_tools(b, size, !b->ancestor, 0, 0, 0);
    reformat_format_from(b, -1, 1, -1);
  }

// Blimey. This *really* needs sorting out. As for the windows_remember_size
// call - which duplicates this functionality more or less! - you should NOT
// need to redraw here. The display_width has not changed, so centered objects
// will not move. If it *has* changed, whatever changed it screwed up!

//  /* Otherwise just force a redraw (to make sure centred objects */
//  /* are OK) and check the tools are up to date.                 */
//
//  else
//  {
//    WimpGetWindowStateBlock state;
//
//    state.window_handle = b->window_handle;
//
//    if (!wimp_get_window_state(&state))
//    {
//      coords_box_toworkarea(&state.visible_area, (WimpRedrawWindowBlock *) &state);
//
//      wimp_force_redraw(state.window_handle,
//                        state.visible_area.xmin,
//                        state.visible_area.ymin,
//                        state.visible_area.xmax,
//                        state.visible_area.ymax);
//    }
//
//    windows_check_tools(b, NULL);
//  }
}

/*************************************************/
/* windows_new_browser()                         */
/*                                               */
/* Creates a new Browser window. Parameters are  */
/* as standard for a Toolbox event handler, with */
/* void * handle being NULL to open with the     */
/* current home page, else a pointer to a URL.   */
/*************************************************/

int windows_new_browser(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  #ifndef SINGLE_USER

    /* If in a multiuser build and not logged in, claim */
    /* the event but don't do anything with it.         */

    if (!logged_in) return 1;

  #endif

  if (handle) ChkError(windows_create_browser((char *) handle, NULL, NULL, NULL, Windows_CreateBrowser_Normal));
  else
  {
    char home[Limits_URL];

    urlutils_create_home_url(home, sizeof(home));

    ChkError(windows_create_browser(home, NULL, NULL, NULL, Windows_CreateBrowser_Normal));
  }

  return 1;
}

/*************************************************/
/* windows_shut_browser()                        */
/*                                               */
/* Close a browser window. Closes the ancestor   */
/* and all child frames. Parameters are as       */
/* standard for a Toolbox event handler.         */
/*************************************************/

int windows_shut_browser(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  browser_data * b;
  ObjectId       browser = idb->ancestor_id;

  if (!browser) browser = idb->self_id;

  ChkError(toolbox_get_client_handle(0, browser, (void *) &b));
  if (!is_known_browser(b)) return 0;

  /* Always use the ancestor */

  b = utils_ancestor(b);

  /* Close it */

  return handle_close_browser(0, NULL, NULL, b);
}

/*************************************************/
/* windows_create_browser()                      */
/*                                               */
/* Creates and initialises a browser_data struct */
/* and may create an associated window object.   */
/*                                               */
/* Parameters: Pointer to a URL to fetch (or     */
/*             NULL to fetch none);              */
/*                                               */
/*             Pointer to a parent structure if  */
/*             this is a frame, else NULL;       */
/*                                               */
/*             Pointer to a BBox describing the  */
/*             visible area, or NULL to use      */
/*             default sizes and a window        */
/*             position based on the last        */
/*             default open position;            */
/*                                               */
/*             Pointer to the name for this      */
/*             window (for targetted URLs), or   */
/*             NULL for none. The string will be */
/*             copied to a browser local malloc  */
/*             block, so it can come from pretty */
/*             much anywhere that isn't going to */
/*             shift over a function call        */
/*             boundary (i.e. not flex!);        */
/*                                               */
/*             Fetch type value; see definitions */
/*             in Windows.h. Note that the       */
/*             ForUIElement type is not          */
/*             implemented - don't use it!       */
/*************************************************/

_kernel_oserror * windows_create_browser(const char * url, browser_data * real_parent,
                                         BBox * size, const char * name, int save_type)
{
  ObjectId          o;
  browser_data    * b;
  reformat_cell   * cell;
  _kernel_oserror * e = NULL;
  int               width, height;

  /* If no size BBox is given, use the Choices file specified sizes, */
  /* else use the sizes defined by the BBox.                         */

  if (!size) width = choices.width,           height = choices.height;
  else       width = size->xmax - size->xmin, height = size->ymax - size->ymin;

  /* malloc a block of space, treating it as holding a browser_data */
  /* structure. This is defined in global.h, and holds all the      */
  /* information needed with a given browser window (e.g. the       */
  /* document currently on display in that window, etc).            */

  #ifdef TRACE
    if (tl & (1u<<12))
    {
      Printf("windows_create_browser: memory_alloc_and_set %d for 'browser_data' structure\n",sizeof(browser_data));
      Printf("                                         and %d for 'reformat_cell' structure\n",sizeof(reformat_cell));
    }
  #endif

  b    = (browser_data  *) memory_alloc_and_set(sizeof(browser_data),  0);
  cell = (reformat_cell *) memory_alloc_and_set(sizeof(reformat_cell), 0);

  if (!b || !cell)
  {
    erb.errnum = Utils_Error_Custom_Message;

    StrNCpy0(erb.errmess,
             lookup_token("NoMemWin:There is not enough free memory to open another browser window.",
                          0,
                          0));

    return &erb;
  }
  else
  {
    #ifdef TRACE
      malloccount += (sizeof(browser_data) + sizeof(reformat_cell));
      if (tl & (1u<<13)) Printf("** malloccount (windows_create_browser): \0211%d\0217\n",malloccount);
    #endif

    /* The allocation succeeded, so create the object, fill in the browser_data */
    /* stucture, and link the stucture into the list of structures for all the  */
    /* browser windows. The toolbox is given a pointer to the structure as a    */
    /* client handle for the window, so whenever we get an event relating to    */
    /* the window this pointer is also supplied.                                */

    b->cell = cell;

    if (save_type != Windows_CreateBrowser_Normal && controls.use_small)
    {
      /* If the browser is created just to save an object, */
      /* build a 'mini' browser for just that.             */

      e = toolbox_create_object(0, "BrowsrFetch", &o);

      b->small_fetch = 1;
    }
    else
    {
      /* Otherwise, build a base browser, or if this is a frame, a child */
      /* browser object.                                                 */

      if (!real_parent) e = toolbox_create_object(0, "Browser", &o);
      else
      {
        if (nested_wimp) e = toolbox_create_object(0, "BrChild",     &o);
        else             e = toolbox_create_object(0, "BrChildBrdr", &o);
      }
    }

    if (e) return e;

    RetError(toolbox_set_client_handle(0, o, b));

    /* Regardless of whether this is an ancestor browser window or a frame, */
    /* its next pointer must still be set to NULL.                          */

    b->next = NULL;

    /* Link this into the list of browser_data structures only if it's a parent   */
    /* browser window - i.e. not part of a frame set; there will be no parent     */
    /* browser_data struct passed into the function as this means you're a child. */

    b->previous = last_browser;
    if (last_browser) last_browser->next = b;
    last_browser = b;

    /* Sort out parent/child linking considerations */

    if (!real_parent)
    {
      b->ancestor    = NULL; /* If not a child, this is an ancestor object */
      b->parent      = NULL;
      b->real_parent = NULL;
    }
    else
    {
      /* Allocate memory in the array of children for this entry */

      RetError(memory_set_chunk_size(real_parent, NULL, CK_CHIL, (real_parent->nchildren + 1) * sizeof(browser_data *)));

      /* Add the entry - nchildren hasn't been incremented yet, so */
      /* [real_parent->nchildren] is correct for the new entry.    */

      real_parent->children[real_parent->nchildren] = b;

      /* Fill in the child's parent and ancestor fields */

      b->real_parent = real_parent;
      b->parent      = real_parent;
      b->ancestor    = real_parent->ancestor;

      if (!b->ancestor) b->ancestor = real_parent;

      /* Finally, increment the parent's number of children counter */

      real_parent->nchildren++;
    }

    b->self_id = o;

    #ifdef TRACE
      if (tl & (1u<<3))
      {
        Printf("\nCreated object ID %p\n",(void *) o);
        Printf("Associated structure is %p\n\n",(void *) b);
      }
    #endif

    /* Opening width considerations... */

    b->display_width = b->display_extent = width;

    if (b->display_extent < MinimumWidth) b->display_extent = MinimumWidth, b->display_width = MinimumWidth;

    /* ...and opening height */

    b->display_height = height;

    /* Default page layout information */

    b->left_margin  = choices.left_margin;
    b->right_margin = choices.right_margin;
    b->quote_margin = choices.quote_margin;
    b->leading      = choices.leading;
    b->left_indent  = choices.left_indent;

    /* Default encoding */

    b->encoding          = choices.encoding;
    b->encoding_priority = priority_default;

    /* Set up the deferred reformatter */

    b->refo_time = 0;
    b->refo_line = Reformat_UnrealisticallyHighLineNumber;

    /* Remember the Wimp window handle to save having to ask for it elsewhere */

    RetError(window_get_wimp_handle(0,o,&(b->window_handle)));

    /* Allocate memory for the fetching URL and write that */
    /* URL into the block                                  */

    b->urlddata = NULL; /* No current displayed URL */

    /* If handle is non-zero treat as a pointer to a URL, else */
    /* fetch the Home Page.                                    */

    if (url)
    {
      #ifdef TRACE
        if (tl & (1u<<12)) Printf("windows_create_browser: Chunk CK_FURL set to %d\n",strlen(url) + 1);
      #endif

      RetError(memory_set_chunk_size(b, NULL, CK_FURL, strlen(url) + 1));

      strcpy(b->urlfdata, url);
    }

    /* If there's a given window name, set it */

    if (name && *name)
    {
      #ifdef TRACE
        if (tl & (1u<<12)) Printf("windows_create_browser: Chunk CK_NAME set to %d\n",strlen(name) + 1);
      #endif

      RetError(memory_set_chunk_size(b, NULL, CK_NAME, strlen(name) + 1));

      strcpy(b->window_name, name);
    }

    /* Set some local options according to the global Choices */

    b->underline_links = choices.underline_links;
    b->use_source_cols = choices.use_source_cols;
    b->show_foreground = choices.show_foreground;
    b->show_background = choices.show_background;

    /* Initialise various other fields */

    b->background_image = -1;
    b->full_screen      = !real_parent ? choices.full_screen : 0;
    b->full_size        = 0;

    if (save_type != Windows_CreateBrowser_Normal) b->save_link = 1;
    else                                           b->save_link = 0;

    /* Register some browser-specific Wimp event handlers */

    RetError(event_register_wimp_handler(o, Wimp_ERedrawWindow,  (WimpEventHandler *) windows_redraw_browser,   b));
    RetError(event_register_wimp_handler(o, Wimp_EOpenWindow,    (WimpEventHandler *) windows_open_browser,     b));
    RetError(event_register_wimp_handler(o, Wimp_EScrollRequest, (WimpEventHandler *) handle_scroll_requests,   b));
    RetError(event_register_wimp_handler(o, Wimp_EKeyPressed,    (WimpEventHandler *) handle_keys_from_browser, b));

    /* For toolbars and titles, need to be a parent (i.e. have no */
    /* parent browser_data struct, as that means you're a child). */

    if (!real_parent)
    {
      ObjectId ttop, tbot;
      int      scrap;
      char     text[Limits_URLBarWrit];

      if (!b->small_fetch)
      {
        /* Set the window title to contain the default URL for rendering. */

        RetError(window_set_title(0, o, lookup_token("BlankPage:Blank page",0,0)));
      }
      else
      {
        /* Set the window title to contain a default string for file fetching. */

        RetError(window_set_title(0, o, lookup_token("DataFetch:Fetching to file...",0,0)));
      }

      /* Set various toolbar flags up, and make */
      /* sure the right toolbars are visible.   */

      if (save_type != Windows_CreateBrowser_Normal && controls.use_small)
      {
        b->url_bar    = 1;
        b->button_bar = 1;
        b->status_bar = 1;
      }
      else
      {
        b->url_bar    = choices.url_bar;
        b->button_bar = choices.button_bar;
        b->status_bar = choices.status_bar;
      }

      /* Work out what toolbars are available */

      RetError(window_get_tool_bars(InternalTopLeft | InternalBottomLeft,
                                    o,
                                    &tbot,
                                    &ttop,
                                    NULL,
                                    NULL));

      if (!ttop) b->all_in_bottom = 1;
      if (!tbot) b->all_in_top = 1;

      /* Now make sure that ttop and tbot reflect the updated values in */
      /* light of the all_in_bottom/all_in_top/swap_bars flags.         */

      ttop = toolbars_get_upper(b);
      tbot = toolbars_get_lower(b); /* (These are needed further down). */

      /* Read scroll bar choices */

      b->frame_hscroll = choices.h_scroll;
      b->frame_vscroll = choices.v_scroll;

      /* Only allow Wimp events to close a window if it's a parent; */
      /* otherwise, want entirely browser-driven control of when    */
      /* the child closes (for example, it Would Be Bad if some     */
      /* dodgy PD application that closed the window under the      */
      /* pointer on noticing some hotkey was activated on a child). */

      RetError(event_register_wimp_handler(o, Wimp_ECloseWindow, (WimpEventHandler *) handle_close_browser, b));

      /* Register event handlers for toolbar items, if a parent. Similarly, work out */
      /* what to do with toolbar buttons and writables, etc., only if a parent (a    */
      /* child as no toolbars).                                                      */

      if (ttop)
      {
        RetError(event_register_wimp_handler(ttop, Wimp_EMouseClick, (WimpEventHandler *) handle_clicks, b));

        /* If there's a dialler display field, install a long-term handler */
        /* to keep it up to date.                                          */

        e = gadget_get_type(0, ttop, URLBarDiallerStatus, &scrap);

        if (!e)
        {
          if (!taskmodule_ds_registered)
          {
            /* Register the dialler's 'status changed' service call */
            /* with TaskModule. This allows a Wimp message to be    */
            /* sent whenever the status changes.                    */

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

                      0,
                      Service_DiallerStatus,
                      task_handle);

            /* If the above fails, don't bother reporting the error, */
            /* just skip the function - it is non-essential and the  */
            /* most likely cause of failure - unknown SWI - is taken */
            /* as an indicator that the dialler service is not       */
            /* needed.                                               */

            if (!e) taskmodule_ds_registered = 1;
          }
          else e = NULL;

          if (!e)
          {
            /* Message handler for the TaskModule's Message_Service message, */
            /* which is sent when the dialler sends out a 'status changed'   */
            /* service call following the above registration SWI call.       */

            RetError(event_register_message_handler(Message_Service, handle_dialler_service, b));
          }
        }
      }

      /* Handle border and colour control of the URL writable, status */
      /* field, and dialler display field.                            */

      if (ttop)
      {
        /* URL writable */

        RetError(windows_process_component_text(ttop, URLBarWrit, text, sizeof(text), 0, 1));

        /* Dialler status display field */

        RetError(windows_process_component_text(ttop, URLBarDiallerStatus, text, sizeof(text), 0, 1));
      }

      if (tbot)
      {
        int bytes_flags;

        /* Main status display field */

        RetError(windows_process_component_text(tbot, StatusBarStatus, text, sizeof(text), 0, 1));

        /* Progress indicator */

        RetError(windows_process_component_text(tbot, StatusBarProgress, text, sizeof(text), 0, 1));

        /* Remember whatever foreground colour the StatusBarProgress gadget now has */

        if (!button_get_flags(0, tbot, StatusBarProgress, &bytes_flags));
        {
          b->progress_colour = (bytes_flags & 0x0f000000) >> 24;
        }
      }

      /* Are the URL writable and status display to be merged? */
      /* Can only merge them if the toolbars are merged.       */
      /*                                                       */
      /* Take this opportunity to look for bistate and         */
      /* tristate button definitions, in the same way.         */

      if (b->all_in_top || b->all_in_bottom)
      {
        /* If the toolbars are merged, it doesn't matter if we try */
        /* to get the upper or lower toolbar object ID - the       */
        /* returned result will be the same.                       */
        /*                                                         */
        /* Can only merge things if there's a toolbar to look at!  */

        if (ttop)
        {
          /* See if the URL writable and status displays are to be merged. */

          {
            BBox sta, url;

            /* If finding the BBox of either generates an error, can't */
            /* say they're to be merged.                               */

            if (
                 !gadget_get_bbox(0, ttop, URLBarWrit,   &url) &&
                 !gadget_get_bbox(0, ttop, StatusBarStatus, &sta)
               )
            {
              /* Otherwise, compare their bounding boxes. If identical, */
              /* the two are to be merged.                              */

              if (
                   url.xmin == sta.xmin &&
                   url.ymin == sta.ymin &&
                   url.xmax == sta.xmax &&
                   url.ymax == sta.ymax
                 )
              {
                b->merged_url = 1;
                toolbars_merged_to_status(b, ttop);
              }

            /* Closure of long 'if' checking that the bounding boxes of */
            /* the URL writable and status display fields were obtained */
            /* without error (the code above excutes if this is true).  */
            }
          }

          /* See if there are any recognised bistate buttons present. */
          /* Code is similar to the above URL/status merge code.      */

          {
            BBox s1, s2;
            int  scrap;

            /* Must have a working gadget, component ID ButtonBarBistate, */
            /* to use the bistate button.                                 */

            if (!gadget_get_type(0, ttop, ButtonBarBistate, &scrap))
            {
              /* Check for type BiState_Cancel_Back */

              if (
                   !gadget_get_bbox(0, ttop, ButtonBarCancel, &s1) &&
                   !gadget_get_bbox(0, ttop, ButtonBarBack,   &s2)
                 )
              {
                if (
                     s1.xmin == s2.xmin &&
                     s1.ymin == s2.ymin &&
                     s1.xmax == s2.xmax &&
                     s1.ymax == s2.ymax
                   )
                {
                  b->bistate = BiState_Cancel_Back;

                  /* Ensure the reference gadgets are hidden */

                  hide_gadget(ttop, ButtonBarBack);
                  hide_gadget(ttop, ButtonBarCancel);

                  /* Set the default starting state to 'Back' */

                  toolbars_set_bistate_state(b, ttop, 1);
                }
              }
            }
          }

          /* See if there are any recognised tristate buttons present. */
          /* Code is similar to the above bistate button code.         */

          {
            BBox s1, s2, s3;
            int  scrap;

            /* Must have a working gadget, component ID ButtonBarTristate, */
            /* to use the tristate button.                                 */

            if (!gadget_get_type(0, ttop, ButtonBarTristate, &scrap))
            {
              /* Check for type TriState_Go_GoTo_Stop */

              if (
                   !gadget_get_bbox(0, ttop, ButtonBarGo,   &s1) &&
                   !gadget_get_bbox(0, ttop, ButtonBarGoTo, &s2) &&
                   !gadget_get_bbox(0, ttop, ButtonBarStop, &s3)
                 )
              {
                if (
                     s1.xmin == s2.xmin &&
                     s1.ymin == s2.ymin &&
                     s1.xmax == s2.xmax &&
                     s1.ymax == s2.ymax &&
                     s1.xmin == s3.xmin &&
                     s1.ymin == s3.ymin &&
                     s1.xmax == s3.xmax &&
                     s1.ymax == s3.ymax
                   )
                {
                  b->tristate = TriState_Go_GoTo_Stop;

                  /* Ensure the reference gadgets are hidden */

                  hide_gadget(ttop, ButtonBarGoTo);
                  hide_gadget(ttop, ButtonBarGo);
                  hide_gadget(ttop, ButtonBarStop);

                  /* If fetching, set the tristate to 'stop', else 'go to'. */

                  if (url) toolbars_set_tristate_state(b, ttop, 2);
                  else     toolbars_set_tristate_state(b, ttop, 1);
                }
              }
            }
          }

        /* Closure of long 'if' checking the upper toolbar object ID */
        /* was non-zero. If so, the code above executes.             */
        }

      /* Closure of long 'if' checking that the upper and lower toolbars */
      /* are merged into one; the code above executes if so.             */
      }

      /* Set the default status. Must do this after the */
      /* routines that process icon text etc. have been */
      /* run, or the status routines could trash a      */
      /* special icon string before it's dealt with...  */

      toolbars_update_status(b, Toolbars_Status_Ready);

    /* Closure of long 'if' checking if the window was a parent. The */
    /* code above executes if so.                                    */
    }

    /* Handler for on-page clicks */

    RetError(event_register_wimp_handler(o,
                                         Wimp_EMouseClick,
                                         (WimpEventHandler *) handle_link_clicks,
                                         b));

    /* Open the window and register a Wimp handler that will be called   */
    /* whenever there is an Open Window request generated for the window */
    /* (this deals with window extent settings etc.).                    */

    if (!size) b->small_size.visible_area.xmin = b->small_size.visible_area.ymin = -1;
    else       b->small_size.visible_area      = *size;

    /* This call will fill in small_size depending on the above. */

    if (save_type != Windows_CreateBrowser_ForPlugIn || choices.see_fetches) windows_show_browser(b);

    /* Ensure the URL bar is up to date, and set the fetch status to BS_START. */

    if (url)
    {
      toolbars_update_url(b);
      b->fetch_status = BS_START;
    }

    /* Must be called after windows_show_browser so the parent/ancestor IDs are set up */

    if (!real_parent)
    {
      BBox w;

      toolbars_set_presence(b, InternalTopLeft | InternalBottomLeft);

      /* Ensure that the window extent is at least as wide and tall */
      /* as the minimum extent settings require.                    */

      if (!b->small_fetch)
      {
        RetError(window_get_extent(0,o,&w));

        if ((w.xmax - w.xmin) < MinimumWidth) w.xmin = 0, w.xmax = MinimumWidth;
        if ((w.ymax - w.ymin) < MinimumHeight) w.ymin = -MinimumHeight, w.ymax = 0;

        RetError(window_set_extent(0,o,&w));

        /* Give the window the input focus, if it's an ancestor */

        RetError(browser_give_general_focus(b));
      }
    }

    /* Start a fetch for this page's current URL. Note the allow_cancel */
    /* flag is unset, so that any flags set up above, which cancel      */
    /* functions may touch (e.g. save_link), will not be altered.       */

    if (url) e = fetchpage_new(b, b->urlfdata, 0, 0);

    else if (b->small_fetch)
    {
      /* For a small fetch window, we may have no URL (Plug-Ins - already */
      /* have the data in a file, and want the fetcher to be kicked       */
      /* briefly to notice the fetch is finished before it even started   */
      /* and fire off the relevant messages).                             */

      fetchpage_claim_nulls(b);
    }

    /* Now ensure that future fetches may call fetch_cancel */
    /* before proceeding.                                   */

    b->allow_cancel = 1;

    return e;

  /* Closure of long 'if' ensuring the mallocs for the browser_data */
  /* and associated reformat_cell structures were successful - the  */
  /* code above only executes if so.                                */
  }
}

/*************************************************/
/* windows_open_browser()                        */
/*                                               */
/* (Re)opens a Browser window, setting extent if */
/* necessary. Parameters are as standard for a   */
/* Wimp event handler.                           */
/*                                               */
/* Assumes:    The poll block and ID block       */
/*             pointers may be NULL, though in   */
/*             the latter case only for full     */
/*             screen opening;                   */
/*                                               */
/*             The event code is not read and    */
/*             can by any integer value;         */
/*                                               */
/*             That the browser_data pointer is  */
/*             not NULL.                         */
/*************************************************/

int windows_open_browser(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  /* Internally...                                                             */
  /*                                                                           */
  /* 's' holds the browser window state, and is a WimpGetWindowStateBlock      */
  /* 'w' holds various bits of data, and is a BBox. It always ends up holding  */
  /*     information for setting the window extent.                            */
  /* 't' is a BBox holding the browser window's initial extent.                */
  /* 'o' holds the current window outline, and is a WimpGetWindowOutlineBlock. */
  /*     It's used for the part of the code dealing with toggle size.          */
  /* 'i' is a WimpGetPointerInfoBlock and is briefly used in the toggle size   */
  /*     code to see if Adjust was pressed, since then you don't want to bring */
  /*     the window to the front.                                              */
  /*                                                                           */
  /* The Wimp will have sent a poll block containing open_window_request       */
  /* data; this is in 'b'. The toolbox will have filled in an ID block 'idb'   */
  /* and the routine is called with 'handle' containing a pointer to a         */
  /* browser_data structure (though it has to be declared as a void * rather   */
  /* than browser_data * for the function to be a Wimp event handler, hence    */
  /* all the casting that goes on).                                            */

  WimpGetWindowStateBlock s;
  BBox                    w, t;

  /* One simple case is Full Screen handling. The '!idb' check is */
  /* used for external functions which want to go to full screen  */
  /* mode; they can call this function with 0, NULL, NULL and the */
  /* browser_data pointer (with full_screen set to 1, as this is  */
  /* needed generally) without worrying that part of idb may be   */
  /* needed within the function for some reason - it is used      */
  /* further down and if conditions on this first 'if' failed,    */
  /* there would be a problem if idb were not filled in.          */

  if ((handle->full_screen && modechanged) || !idb)
  {
    WimpGetWindowStateBlock state;

    if (modechanged)
    {
      windows_open_full_screen(handle,
                               1,
                               0,
                               choices.v_scroll,
                               choices.h_scroll);

      modechanged = 0;
    }

    /* Reformat if required */

    state.window_handle = handle->window_handle;
    ChkError(wimp_get_window_state(&state));

    ChkError(frames_resize_frameset(handle, &state.visible_area));

    windows_check_reformat(handle, &state.visible_area, handle->display_width);
  }
  else
  {
    int old_display_width;

    /* Remember the current visible area width for use later, */
    /* to see if a page reformat is needed.                   */

    old_display_width = handle->display_width;

    s.window_handle = b->open_window_request.window_handle;
    ChkError(wimp_get_window_state(&s));

    ChkError(window_get_extent(0,idb->self_id,&t));

    /* Was this a toggle size event? If so, handle manually */

    if ((s.flags & WimpWindow_Toggled) != 0)
    {
      WimpGetWindowOutlineBlock o;
      int                       shift, ctrl, cmos;

      /* First check if Shift is pressed; the 'shift' variable will hold  */
      /* 255 if pressed or 0 if not. This is because the full size window */
      /* shouldn't obscure the icon bar if shift is held down.            */

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

            121,
            128,

            &shift);

      /* Similarly check for Control being held down. We'll only increase */
      /* the vertical size if this is the case.                           */

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

            121,
            129,

            &ctrl);

      /* Check CMOS RAM byte 28, bit 4 - if set, reverse the sense of Shift */

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

            161,
            28,

            &cmos);

      if (cmos & (1u<<4)) shift = shift ? 0 : 255;

      /* Read the screen size in OS units */

      w.xmin = bbc_modevar(-1, BBC_XEigFactor);
      w.ymin = bbc_modevar(-1, BBC_YEigFactor);
      w.xmax = bbc_modevar(-1, BBC_XWindLimit);
      w.ymax = bbc_modevar(-1, BBC_YWindLimit);

      w.xmax = (w.xmax + 1) << w.xmin;
      w.ymax = (w.ymax + 1) << w.ymin;

      /* This isn't good enough, as we need to take account of the window */
      /* frame and tools. Since the window must already be open, we can   */
      /* use Wimp_GetWindowOutline and compare this to the visible size,  */
      /* where the difference is the size of borders and tools.           */

      o.window_handle = b->open_window_request.window_handle;
      ChkError(wimp_get_window_outline(&o));

      w.xmin = (o.outline.xmax - o.outline.xmin) - (s.visible_area.xmax - s.visible_area.xmin);
      w.ymin = (o.outline.ymax - o.outline.ymin) - (s.visible_area.ymax - s.visible_area.ymin);

      /* Now have the information on the window dimensions to occupy the */
      /* whole screen.                                                   */

      if (!handle->full_size)
      {
        int h1, h2;

        /* Toggle up to full size. First, remember the current (small) size. */

        handle->small_size.visible_area = s.visible_area;
        handle->small_size.xscroll      = s.xscroll;
        handle->small_size.yscroll      = s.yscroll;
        handle->small_size.behind       = find_behind(b->open_window_request.window_handle);

        /* Set the poll block contents to open the window in the right place. */
        /* Notice if Shift is held down and don't cover icon bar, and if Ctrl */
        /* is held down we leave the X positions alone.                       */

        if (!ctrl)
        {
          b->open_window_request.visible_area.xmin = 2;
          b->open_window_request.visible_area.xmax = w.xmax - w.xmin + 2;
        }
        else
        {
          b->open_window_request.visible_area.xmin = s.visible_area.xmin;
          b->open_window_request.visible_area.xmax = s.visible_area.xmax;

          w.xmin = s.visible_area.xmin;
          w.xmax = s.visible_area.xmax;
        }

        b->open_window_request.visible_area.ymin = s.visible_area.ymin - o.outline.ymin + (shift ? 128 : 0);
        b->open_window_request.visible_area.ymax = w.ymax - w.ymin + (s.visible_area.ymin - o.outline.ymin);

        /* Is adjust being pressed? If so, want to open at the same place */
        /* in the window stack as at present. NB, the Wimp seems to take  */
        /* Select as higher priority than Adjust, so if both are pressed  */
        /* the window is brought to the front. This is why the code looks */
        /* at the state of both buttons.                                  */

        {
          WimpGetPointerInfoBlock i;

          ChkError(wimp_get_pointer_info(&i));

          if (
               (i.button_state & Wimp_MouseButtonAdjust) != 0 &&
               (i.button_state & Wimp_MouseButtonSelect) == 0
             )

             b->open_window_request.behind = s.behind;

          else b->open_window_request.behind = -1;
        }

        /* Ensure horizontal and vertical extent isn't too small, and don't ever */
        /* shrink them - only reformat_set_extent is allowed to do that, in      */
        /* Reformat.c. The exception case is if the parent window has child      */
        /* frames, in which case we want the extent to track the visible area    */
        /* exactly, at all times.                                                */

        if (!handle->nchildren)
        {
          BBox extent;

          if (w.xmax - w.xmin < handle->display_extent) w.xmin = 0, w.xmax = handle->display_extent;

          window_get_extent(0,idb->self_id,&extent);

          if (w.ymax - w.ymin < extent.ymax - extent.ymin) w.ymax = w.ymin + extent.ymax - extent.ymin;
          if (w.ymax - w.ymin < MinimumHeight)             w.ymax = w.ymin + MinimumHeight;
        }

        ChkError(set_corrected_extent(0, idb->self_id, &w));

        /* Show the object */

        ChkError(toolbox_show_object(0,
                                     idb->self_id,
                                     1,
                                     &(b->open_window_request.visible_area),
                                     idb->parent_id,
                                     -1));

        /* Flag that the window is full size */

        handle->full_size = 1;

        /* Make sure the toolbars, if present, are up to date */

        if (choices.move_gadgets != Choices_MoveGadgets_Never) toolbars_move_gadgets(handle);

        /* Update the visible area width and height fields */

        handle->display_width  = b->open_window_request.visible_area.xmax -
                                 b->open_window_request.visible_area.xmin;

        h1 = toolbars_button_height(handle) + toolbars_url_height(handle);
        h2 = toolbars_status_height(handle);

        if (h1) h1 += wimpt_dy();
        if (h2) h2 += wimpt_dy();

        handle->display_height = b->open_window_request.visible_area.ymax -
                                 b->open_window_request.visible_area.ymin -
                                 h1                                       -
                                 h2;

        /* Resize any child frames and reformat if necessary */

        ChkError(frames_resize_frameset(handle, &b->open_window_request.visible_area));
        windows_check_reformat(handle, &b->open_window_request.visible_area, old_display_width);
      }
      else
      {
        int h1, h2;

        w = handle->small_size.visible_area;

        /* An irritating feature of the Wimp - if you call Wimp_SetExtent, */
        /* then the next Wimp_OpenWindow call is forced on-screen. This    */
        /* means that if toggle size is used when the window is partially  */
        /* dragged off-screen, then the shrinking horizontal extent when   */
        /* it is toggled back would ensure that the window was brought     */
        /* back fully on screen. Sometimes this is annoying.               */
        /*                                                                 */
        /* It turns out though, that if the window is *already* partially  */
        /* off-screen when the SetExtent call is made, the Wimp brings the */
        /* window (internally) back on screen, and doesn't bother to do    */
        /* this on the next OpenWindow call. So to allow the window to be  */
        /* toggled back to a small size which may be partially off-screen, */
        /* we open the window miles off screen somewhere before setting    */
        /* the extent.                                                     */
        /*                                                                 */
        /* This is not pretty but it is relatively solid; the worst that   */
        /* can happen is the Wimp *does* decide to force the window back   */
        /* on screen at the next OpenWindow call, in which case the        */
        /* behaviour is as it would be without the extra code here.        */

        b->open_window_request.visible_area.xmin = w.xmin + 16384;
        b->open_window_request.visible_area.ymin = w.ymin - 16384;
        b->open_window_request.visible_area.xmax = w.xmax + 16384;
        b->open_window_request.visible_area.ymax = w.ymax - 16384;
        b->open_window_request.behind            = handle->small_size.behind;

        toolbox_show_object(0,
                            idb->self_id,
                            1,
                            &(b->open_window_request.visible_area),
                            idb->parent_id,
                            -1);

        /* Store the new details in b for later use in the show_object call */

        b->open_window_request.visible_area = w;
        b->open_window_request.xscroll      = handle->small_size.xscroll;
        b->open_window_request.yscroll      = handle->small_size.yscroll;
        b->open_window_request.behind       = handle->small_size.behind;

        /* Set the extent, with usual restrictions on when not to shrink it (see */
        /* elsewhere for more information, around similar pieces of code)        */

        if (!handle->nchildren)
        {
          BBox extent;

          if (w.xmax - w.xmin < handle->display_extent) w.xmin = 0, w.xmax = handle->display_extent;

          window_get_extent(0,idb->self_id,&extent);

          if (w.ymax - w.ymin < extent.ymax - extent.ymin) w.ymax = w.ymin + extent.ymax - extent.ymin;
          if (w.ymax - w.ymin < MinimumHeight)             w.ymax = w.ymin + MinimumHeight;
        }

        ChkError(set_corrected_extent(0, idb->self_id, &w));

        ChkError(toolbox_show_object(0,
                                     idb->self_id,
                                     1,
                                     &(b->open_window_request.visible_area),
                                     idb->parent_id,
                                     -1));

        handle->full_size = 0;

        /* Make sure the toolbars, if present, are up to date */

        if (choices.move_gadgets != Choices_MoveGadgets_Never) toolbars_move_gadgets(handle);

        /* Update the visible area width and height fields */

        handle->display_width  = b->open_window_request.visible_area.xmax -
                                 b->open_window_request.visible_area.xmin;

        h1 = toolbars_button_height(handle) + toolbars_url_height(handle);
        h2 = toolbars_status_height(handle);

        if (h1) h1 += wimpt_dy();
        if (h2) h2 += wimpt_dy();

        handle->display_height = b->open_window_request.visible_area.ymax -
                                 b->open_window_request.visible_area.ymin -
                                 h1                                       -
                                 h2;

        /* Resize any child frames and reformat if needed */

        ChkError(frames_resize_frameset(handle, &b->open_window_request.visible_area));

        windows_check_reformat(handle, &b->open_window_request.visible_area, old_display_width);
      }
    }

    /* This isn't a Toggle Size event; so move or resize. Whilst the   */
    /* Toggle Size code above remembers the small size of a window if  */
    /* toggling up to full screen, this isn't enough as the user can   */
    /* drag the window to full size, particularly if the machine has   */
    /* been configured to allow windows off screen to the bottom and   */
    /* right of the screen. In this case, dragging the resize icon of  */
    /* a window off screen shuffles the window up and/or left, so it   */
    /* becomes easy to drag out to full screen. In this case, we don't */
    /* know what the small size of the window was before the drag      */
    /* began, and so we can't return to it when the toggle size icon   */
    /* is used - you end up with the *previously stored* small size.   */
    /*                                                                 */
    /* To get round this, we need to install a null event handler when */
    /* a window drag is detected. As soon as a null event is received  */
    /* without a mouse button being pressed, the drag must have ended. */
    /* We can then check the size of the window, and if it isn't full  */
    /* screen, remember that size for toggle size later on. It's very  */
    /* long winded but that's what you get for taking over the window  */
    /* handling to such an extent...!                                  */

    else
    {
      /* For child frames (i.e. browser_data structs with a filled in ancestor field), */
      /* this function will only be called from frames_resize_frames itself. This in   */
      /* turn is originally only called if the ancestor window resizes in some way.    */
      /* It's not possible to apply the 'has the visible area changed' check as        */
      /* rounding errors can lead to some frames just shuffling a bit, not resizing,   */
      /* and any children they had would then not be resized (or rather, moved) to fit */
      /* the parent.                                                                   */
      /*                                                                               */
      /* We would in theory want to resize (and implicitly move) any frames even if    */
      /* the parent window moves, but doesn't resize. In the case of the nested Wimp   */
      /* though, the children are moved for you and moving them again produces some    */
      /* very odd effects! Consequently there are checks for the nested_wimp flag in   */
      /* the code.                                                                     */

      if (handle->ancestor || !nested_wimp)
      {
        ChkError(frames_resize_frameset(handle, &b->open_window_request.visible_area));
      }

      /* Claim null events, if they aren't already being claimed */

      if (!handle->watching_resize)
      {
        register_null_claimant(Wimp_ENull,(WimpEventHandler *) windows_remember_size,(void *) idb->self_id);
        handle->watching_resize = 1;
      }

      /* If the current and Wimp requested visible areas are the same in */
      /* terms of horizontal and vertical size, the window is being      */
      /* moved; so don't do any extent setting stuff. However, if the    */
      /* Force On Screen bit is set, the then window has been shrunk or  */
      /* moved due to a mode change, so make sure the extent is right    */
      /* and the toolbars are set up correctly. Because the Wimp won't   */
      /* actually set the window size up for us until the next call to   */
      /* WimpOpenWindow, we need a separate handler for this case.       */

      if (
           b->open_window_request.visible_area.xmax - b->open_window_request.visible_area.xmin != s.visible_area.xmax - s.visible_area.xmin ||
           b->open_window_request.visible_area.ymax - b->open_window_request.visible_area.ymin != s.visible_area.ymax - s.visible_area.ymin ||
           modechanged
         )
      {
        /* See comments on the previous line that called frames_resize_frameset; */
        /* for the ancestor, want to only resize frames if the ancestor resized, */
        /* unless in a non-nested Wimp variant.                                  */

        if (nested_wimp && !handle->ancestor)
        {
          ChkError(frames_resize_frameset(handle, &b->open_window_request.visible_area));
        }

        if (modechanged)
        {
          /* If there's been a mode change, call Wimp_OpenWindow to get the window's */
          /* correct visible area and then sort out the correct extent.              */

          toolbox_show_object(0,
                              idb->self_id,
                              1,
                              &(b->open_window_request.visible_area),
                              idb->parent_id,
                              -1);

          wimp_get_window_state(&s);

          w = s.visible_area;
        }
        else
        {
          /* The requested visible area is different, so consider this to be a window */
          /* resize event. In this case, set the horizontal extent to the visible     */
          /* area, provided this means the extent has grown.                          */

          w = b->open_window_request.visible_area;
        }

        /* Set the extent, with usual restrictions on when not to shrink it (see */
        /* elsewhere for more information, around similar pieces of code)        */

        if (!handle->nchildren)
        {
          BBox extent;
//Printf("woo, resizing\n");
          if (w.xmax - w.xmin < handle->display_extent) w.xmin = 0, w.xmax = handle->display_extent;

          window_get_extent(0,idb->self_id,&extent);

          if (w.ymax - w.ymin < extent.ymax - extent.ymin) w.ymax = w.ymin + extent.ymax - extent.ymin;
          if (w.ymax - w.ymin < MinimumHeight)             w.ymax = w.ymin + MinimumHeight;
        }

        ChkError(set_corrected_extent(0, idb->self_id, &w));

        if (modechanged) modechanged = 0;

        /* Show the object */

        toolbox_show_object(0,
                            idb->self_id,
                            1,
                            &(b->open_window_request.visible_area),
                            idb->parent_id,
                            -1);

        /* Ensure toolbar gadgets are up to date */

        if (choices.move_gadgets == Choices_MoveGadgets_During) toolbars_move_gadgets(handle);

        /* If an ancestor is highlighted, remove the highlight */

        if (!handle->ancestor && handle == highlight_frame) frames_remove_highlight();
      }
      else
      {
        /* Nothing has altered size; just move the window */

        toolbox_show_object(0,
                            idb->self_id,
                            1,
                            &(b->open_window_request.visible_area),
                            idb->parent_id,
                            -1);
      }
    }
  }

  return 1;
}

/*************************************************/
/* windows_remember_size()                       */
/*                                               */
/* This event handler is registered when an Open */
/* Window request is received by the event       */
/* handler windows_open_browser, which should be */
/* referred to for more information. This        */
/* handler is called on null polls, and when no  */
/* mouse buttons are being pressed, deregisters  */
/* itself and looks at the browser window it is  */
/* associated with. If the window is not open    */
/* full size, the size is remembered so that the */
/* toggle size code in windows_open_browser can  */
/* operate correctly.                            */
/*************************************************/

int windows_remember_size(int eventcode, WimpPollBlock * b, IdBlock * idb, void * handle)
{
  WimpGetPointerInfoBlock   p;
  browser_data            * d;

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

  /* First thing - move the gadgets, if the choices indicate that */
  /* we should, to fit the current window size.                   */

  ChkError(toolbox_get_client_handle(0, (ObjectId) handle, (void *) &d));

  /* Is this a valid pointer? If not, exit */

  if (!is_known_browser(d)) return 0;

  /* Otherwise, proceed */

  if (choices.move_gadgets == Choices_MoveGadgets_AtEnd) toolbars_move_gadgets(d);

  /* Are buttons being pressed? */

  ChkError(wimp_get_pointer_info(&p));

  /* If not... */

  if (!p.button_state)
  {
    WimpGetWindowOutlineBlock o;
    WimpGetWindowStateBlock s;
    BBox w;

    /* Deregister the null event handler and flag that we */
    /* are not watching resize events anymore.            */

    deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) windows_remember_size,handle);
    d->watching_resize = 0;

    /* Get the window state (of interest is the visible area) */
    /* and it's outline. This is so we can later work out the */
    /* size of the window's tools.                            */

    s.window_handle = d->window_handle;
    o.window_handle = s.window_handle;
    ChkError(wimp_get_window_state(&s));
    ChkError(wimp_get_window_outline(&o));

    /* Read the screen size in OS units */

    w.xmin = bbc_modevar(-1, BBC_XEigFactor);
    w.ymin = bbc_modevar(-1, BBC_YEigFactor);
    w.xmax = bbc_modevar(-1, BBC_XWindLimit);
    w.ymax = bbc_modevar(-1, BBC_YWindLimit);

    w.xmax = (w.xmax + 1) << w.xmin;
    w.ymax = (w.ymax + 1) << w.ymin;

    /* Take account of window tools using Wimp_GetWindowOutline info from above */

    w.xmin = (o.outline.xmax - o.outline.xmin) - (s.visible_area.xmax - s.visible_area.xmin);
    w.ymin = (o.outline.ymax - o.outline.ymin) - (s.visible_area.ymax - s.visible_area.ymin);

    /* Now know what the extent would be for a full screen window, so */
    /* compare that to the current visible size.                      */

    if   (((s.visible_area.xmax - s.visible_area.xmin) < (w.xmax - w.xmin))
       | ((s.visible_area.ymax - s.visible_area.ymin) < (w.ymax - w.ymin)))
    {
      /* Window isn't full screen, so remember the size */

      d->small_size.visible_area = s.visible_area;
      d->small_size.behind       = find_behind(s.window_handle);

      d->full_size = 0;
    }
    else d->full_size = 1;

    /* Update the visible area width field */

    {
      int width;

      width = s.visible_area.xmax - s.visible_area.xmin;
      if (width < MinimumWidth) width = MinimumWidth;

      /* Tolerance is defined in Windows.h. We leave the display_width */
      /* field alone until the change since it was last updated has    */
      /* exceeded the tolerance value, at which point it is set to the */
      /* visible width (along with display_extent) and the page gets   */
      /* reformatted from the top.                                     */

      if (width != d->display_width)
      {
        if (
             width - d->display_width > Tolerance ||
             width - d->display_width < -Tolerance
           )
        {
          if (!d->page_is_text)
          {
            int h1, h2;

            d->display_extent = d->display_width = width;

            h1 = toolbars_button_height(d) + toolbars_url_height(d);
            h2 = toolbars_status_height(d);

            if (h1) h1 += wimpt_dy();
            if (h2) h2 += wimpt_dy();

            d->display_height = s.visible_area.ymax - s.visible_area.ymin - h1 - h2;

            if (d->ancestor) windows_set_tools(d, &s.visible_area, 0, 0, 0, 0);
            reformat_format_from(d, -1, 1, -1);
          }
        }

        /* Leaving display_width alone has the side effect that centred  */
        /* objects stay centred relative to that value, not the slightly */
        /* altered visible width, so no redraw problems occur if we      */
        /* don't bother forcing a redraw if the reformat isn't done.     */
        /*                                                               */
        /* Hence, no 'else' case here.                                   */
      }
    }

    /* In any case remember the scroll position, or it could jump */
    /* around when the small window size is later restored.       */

    d->small_size.xscroll = s.xscroll;
    d->small_size.yscroll = s.yscroll;
  }

  #ifdef TRACE
    if (tl & (1u<<3)) Printf("windows_remember_size: Successful\n");
  #endif

  return 0;
}

/*************************************************/
/* windows_close_browser()                       */
/*                                               */
/* Closes a Browser window, freeing up memory    */
/* claimed for it.                               */
/*                                               */
/* This DOES NOT CLOSE FRAMESETS, and memory     */
/* claimed for that isn't freed. You should use  */
/* the frames collapsing functions in Frames.c   */
/* to do this. If there's any doubt, always      */
/* close through the general handler in          */
/* c.Handlers as this takes care of frames.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window.           */
/*************************************************/

void windows_close_browser(browser_data * handle)
{
  browser_data * ancestor = handle->ancestor; /* Not utils_ancestor() as we want to know if 'ancestor' is NULL later */

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

  /* This may take a while if there's lots of flex freeing to do... */

  _swix(Hourglass_Start, _IN(0), 10);

  /* Cancel any fetches */

  ChkError(fetch_cancel(handle));

  /* Stop the later code trying to destroy source / close the stream twice */

  if (handle->fetch_handle == handle->display_handle) handle->display_handle = 0;

  /* If this frame is highlighted, this will clear that highlight */

  ChkError(frames_remove_highlight_if_present(handle));

  /* If the Open URL dialogue was opened from the window, this will close it */

  ChkError(openurl_close(handle->self_id, 0));

  /* If a main menu is open for the window, this closes it */

  menus_close_document_if_mine(handle);

  /* If the Print dialogue was opened from the window, this will close */
  /* it and the Print Style window (again, if open).                   */

  ChkError(print_close(handle->self_id, 0));

  /* If there is an ancestor and it has a selected token, */
  /* is that token in this window? If so, want to clear   */
  /* that selection. At the same time, deal with status   */
  /* bar issues.                                          */

  if (ancestor)
  {
    if (ancestor->selected_owner == handle)
    {
      ancestor->selected       = NULL;
      ancestor->selected_owner = NULL;
    }

    if (ancestor->selected_frame == handle) ancestor->selected_frame = NULL;

    /* Ensure that this window is removed from the array of */
    /* status_contents structures associated with the       */
    /* ancestor's status bar.                               */

    ChkError(toolbars_remove_status_item(handle, ancestor));
  }

  /* Get rid of any attached event handlers on the window and its */
  /* toolbars (if any).                                           */
  /*                                                              */
  /* Because null events need to be claimed/released through the  */
  /* pollword, need to still use the internal routine to get rid  */
  /* of null event handlers, before calling the general eventlib  */
  /* routine to remove anything else. Otherwise the internal      */
  /* counter checking if nulls should be released won't be        */
  /* decremented correctly (Bad Things May Then Happen...).       */

  if (handle->watching_resize) deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) windows_remember_size,(void *) handle->self_id);
  handle->watching_resize = 0;

  if (handle->fetch_handler) fetchpage_release_nulls(handle);

  if (handle->anim_drift)
  {
    deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation_drift, handle);
    handle->anim_drift = 0;
  }

  if (handle->plugin_active)
  {
    deregister_null_claimant(Wimp_ENull,(WimpEventHandler *) toolbars_animation, handle);
    handle->plugin_active = 0;
  }

  if (handle->status_handler) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) toolbars_timeout_status, handle);
  handle->status_handler = 0;

  reformat_stop_pending(handle); /* Deregisters stuff as required */

  if (handle->meta_refresh_at) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) meta_check_refresh, handle);
  handle->meta_refresh_at = 0;

  if (handle->dragging) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) handle_drags, handle);
  handle->dragging = 0;
  drag_in_progress = 0;

  /* If watching the dialler's status changed service call,   */
  /* or displaying online time, remove the relevant handlers. */

  if (handle->dialler_status) event_deregister_message_handler(Message_Service, handle_dialler_service, handle);
  if (handle->dialler_last) deregister_null_claimant(Wimp_ENull, (WimpEventHandler *) handle_dialler_display, handle);

  handle->dialler_status = 0;
  handle->dialler_last   = 0;

  /* Remove any pointer-related handlers */

  browser_pointer_over_deleted(handle);

  /* Now release everything else */

  {
    ObjectId tupper, tlower;

    /* Handlers for the main window */

    ChkError(event_deregister_wimp_handlers_for_object(handle->self_id));
    ChkError(event_deregister_toolbox_handlers_for_object(handle->self_id));

    tupper = toolbars_get_upper(handle);

    if (tupper)
    {
      /* Handlers for the upper toolbar */

      ChkError(event_deregister_wimp_handlers_for_object(tupper));
      ChkError(event_deregister_toolbox_handlers_for_object(tupper));
    }

    tlower = toolbars_get_lower(handle);

    if (tlower && tlower != tupper)
    {
      /* Handlers for the lower toolbar */

      ChkError(event_deregister_wimp_handlers_for_object(tlower));
      ChkError(event_deregister_toolbox_handlers_for_object(tlower));
    }
  }

  /* Delete any related run-time created objects */

  if (handle->save_dbox)
  {
    ChkError(toolbox_delete_object(0, handle->save_dbox));
    handle->save_dbox = 0;
  }

  /* Get rid of the fetched source / stream */

  browser_destroy_source(handle);

  if (handle->fetch_handle)
  {
    ChkError(html_close(handle->fetch_handle));
  }

  if (handle->display_handle)
  {
    ChkError(html_close(handle->display_handle));
  }

  /* Close any saving files */

  if (handle->save_file)
  {
    fclose(handle->save_file);
    handle->save_file = NULL;
  }

  /* Get rid of any pending message blocks */

  if (handle->pending_data_load)
  {
    free(handle->pending_data_load);

    #ifdef TRACE
      malloccount -= sizeof(WimpMessage);
      if (tl & (1u<<13)) Printf("** malloccount (windows_close_browser): \0211%d\0217\n",malloccount);
    #endif
  }

  /* If we were streaming data for a Plug-In, close the */
  /* stream.                                            */

  if (handle->pstream) plugin_abort_stream(handle);

  /* Get rid of forms related data */

  ChkError(form_discard(handle));

  /* Get rid of any extra forms data */

  #ifdef TRACE
    if (handle->post_data)
    {
      if (tl & (1u<<12)) Printf("windows_close_browser: flex_free block %p\n",&handle->post_data);
      flexcount -= flex_size((flex_ptr) &handle->post_data);
      if (tl & (1u<<13)) Printf("**   flexcount: %d\n",flexcount);
    }
  #endif

  if (handle->post_data) flex_free((flex_ptr) &handle->post_data);

  /* Remove any Plug-In status message */

  free(handle->plugin_status);
  handle->plugin_status = NULL;

  /* Remove any History references */

  history_remove(handle, NULL);

  /* Get rid of images and Objects */

  ChkError(image_discard(handle));
  ChkError(object_discard(handle));

  #ifdef TRACE
    if ((tl & (1u<<3)) && handle->ancestor)
    {
      Printf("\nwindows_close_browser: Structure %p is a child structure, ancestor %p\n",handle,handle->ancestor);
      if (handle->nchildren) Printf("                       and has children.\n");
      else                   Printf("                       and has no children.\n");
    }
  #endif

  /* Delete the window object. */

  ChkError(toolbox_delete_object(0,handle->self_id));

  #ifdef TRACE
    if (tl & (1u<<3)) Printf("\nDeleted object ID %p\n",(void *) handle->self_id);
  #endif

  /* Ensure any line list data relating to tables is freed */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("windows_close_browser: Freeing table chunks\n");
  #endif

  tables_free_memory(1, handle, handle->cell, 0);

  /* Ensure all memory chunks are freed */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("windows_close_browser: Chunk CK_FURL\n"
                          "                             CK_DURL\n"
                          "                             CK_CHIL\n"
                          "                             CK_NAME\n"
                          "                             CK_FWID\n"
                          "                             CK_FHEI\n"
                          "                             CK_LINE\n"
                          "                             CK_LDAT\n"
                          "                             CK_STAT set to 0\n");
  #endif

  ChkError(memory_set_chunk_size(handle, NULL, CK_FURL, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_DURL, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_CHIL, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_NAME, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_FWID, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_FHEI, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_LINE, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_LDAT, 0));
  ChkError(memory_set_chunk_size(handle, NULL, CK_STAT, 0));

  /* Lose any claimed fonts */

  fm_lose_fonts(handle);

  /* Sort out the linked list next and previous pointers in */
  /* the previous and next entries to this one (if they are */
  /* present)                                               */

  if (handle->previous) (handle->previous)->next = handle->next;
  if (handle->next) (handle->next)->previous = handle->previous;
  else last_browser = handle->previous;

  #ifdef TRACE
    if (tl & (1u<<3))
    {
      if (handle->previous) Printf("Previous structure %p: Next field pointed at %p.\n",(void *) handle->previous, (void *) handle->next);
      else Printf("No Previous structure\n");
      if (handle->next) Printf("Next structure %p: Previous field pointed at %p.\n",(void *) handle->next, (void *) handle->previous);
      else Printf("No Next structure, last_browser set to previous\n");
    }
  #endif

  /* Finally, free the browser_data and associated reformat_cell structures */

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("windows_close_browser: free block %p, which held 'reformat_cell' structure\n",handle->cell);
    malloccount -= sizeof(reformat_cell);
    if (tl & (1u<<13)) Printf("** malloccount (windows_close_browser): \0212%d\0217\n",malloccount);
  #endif

  free(handle->cell);

  #ifdef TRACE
    if (tl & (1u<<12)) Printf("windows_close_browser: free block %p, which held 'browser_data' structure\n",handle);
    malloccount -= sizeof(browser_data);
    if (tl & (1u<<13)) Printf("** malloccount (windows_close_browser): \0212%d\0217\n",malloccount);
  #endif

  free(handle);

  #ifdef TRACE
    if (tl & (1u<<3)) Printf("Structure memory freed\n");
  #endif

  _swix(Hourglass_Off, 0);

  #ifdef TRACE
    if (tl & (1u<<3)) Printf("windows_close_browser: Successful\n");
  #endif

  return;
}

/*************************************************/
/* windows_redraw_browser()                      */
/*                                               */
/* Called on receiving a redraw request from the */
/* Wimp. Calls the browser redraw routines.      */
/*                                               */
/* Parameters are as for a standard Wimp event   */
/* handler.                                      */
/*************************************************/

int windows_redraw_browser(int eventcode, WimpPollBlock * b, IdBlock * idb, browser_data * handle)
{
  WimpRedrawWindowBlock * redraw = (WimpRedrawWindowBlock *) b;
  int                     more;

  ChkError(wimp_redraw_window(redraw, &more));

  if (more)
  {
    ChkError(redraw_draw(handle,
                         redraw,
                         0,
                         0));
  }

  return 1;
}

/*************************************************/
/* windows_show_browser()                        */
/*                                               */
/* Shows a browser window, initially centred on  */
/* the screen but futher windows are opened at   */
/* successive sensible offsets to avoid them     */
/* exactly overlapping one another.              */
/*                                               */
/* This default show position can be overridden  */
/* in two ways; though the Choices specifying an */
/* absolute position to open at, or through the  */
/* small_size.visible_area field of the          */
/* browser_data struct holding an xmin and ymin  */
/* that are not -1 on entry.                     */
/*                                               */
/* Small fetch windows will be shown near the    */
/* pointer.                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window, with the  */
/*             small_size.visible_area BBox      */
/*             filled in with -1 or a valid      */
/*             visible area as described above.  */
/*************************************************/

void windows_show_browser(browser_data * b)
{
  WimpOpenWindowBlock w;
  static int          offset;
  ObjectId            p, o;
  int                 fixed_size = 0;

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

  o = b->self_id;

  if (!b->small_fetch)
  {
    /* Is there a valid small_size.visible_area block? */

    if (b->small_size.visible_area.xmin != -1 && b->small_size.visible_area.ymin != -1)
    {
      w.visible_area = b->small_size.visible_area;
      fixed_size = 1;
    }
    else
    {
      fixed_size = 0;

      /* Find the screen x and y size in pixels and scale them */
      /* to OS units using OS_ReadModeVariable calls via. the  */
      /* bbc_modevar function; also work out the top left      */
      /* coordinates at the same time.                         */

      if (!choices.override_x)
      {
        w.visible_area.xmax = choices.width;

        w.xscroll = bbc_modevar(-1, BBC_XWindLimit);
        w.yscroll = bbc_modevar(-1, BBC_XEigFactor);

        w.visible_area.xmin = (((w.xscroll + 1) << w.yscroll) - w.visible_area.xmax) / 2;
        w.visible_area.xmax += w.visible_area.xmin;
      }
      else
      {
        w.visible_area.xmin = choices.override_x;
        w.visible_area.xmax = choices.width + choices.override_x;
      }

      if (!choices.override_y)
      {
        w.visible_area.ymin = choices.height;

        w.xscroll = bbc_modevar(-1, BBC_YWindLimit);
        w.yscroll = bbc_modevar(-1, BBC_YEigFactor);

        w.visible_area.ymax = (((w.xscroll + 1) << w.yscroll) + w.visible_area.ymin) / 2;
        w.visible_area.ymin = w.visible_area.ymax - w.visible_area.ymin;
      }
      else
      {
        w.visible_area.ymin = choices.override_y;
        w.visible_area.ymax = choices.height + choices.override_y;
      }

      /* Handle offset for successive windows, but don't cover */
      /* up the icon bar                                       */

      if ((w.visible_area.ymin - offset) >= 132)
      {
        w.visible_area.ymin -= offset;
        w.visible_area.ymax -= offset;
        offset += 48;
      }
      else offset = 48;
    }

    w.xscroll = 0;
    w.yscroll = 0;
    w.behind = -1;

    ChkError(set_corrected_extent(0, o, &w.visible_area));
  }

  ChkError(toolbox_get_parent(0,o,&p,NULL));

  if (!b->ancestor || !nested_wimp)
  {
    /* For an ancestor window (i.e. a window which does not have */
    /* an ancestor itself) or no nested Wimp, open it as normal. */

    if (!b->small_fetch)
    {
      ChkError(toolbox_show_object(0,
                                   o,
                                   Toolbox_ShowObject_FullSpec,
                                   &w.visible_area,
                                   p,
                                   -1));
    }
    else
    {
      ChkError(toolbox_show_object(0,
                                   o,
                                   Toolbox_ShowObject_AtPointer,
                                   NULL,
                                   p,
                                   -1));
    }
  }
  else
  {
    /* Otherwise, take advantage of the nested Wimp for child windows */

    WindowShowObjectBlock   show;
    WimpGetWindowStateBlock state;

    state.window_handle = b->ancestor->window_handle;
    ChkError(wimp_get_window_state(&state));

    show.visible_area = w.visible_area;
    show.xscroll      = w.xscroll;
    show.yscroll      = w.yscroll;
    show.behind       = w.behind;

    show.parent_window_handle = (b->ancestor)->window_handle;

    show.alignment_flags = Alignment_LeftEdge_WorkArea | Alignment_RightEdge_WorkArea  |
                           Alignment_TopEdge_WorkArea  | Alignment_BottomEdge_WorkArea |
                           Alignment_XScroll_WorkArea  | Alignment_YScroll_WorkArea;

    ChkError(toolbox_show_object(Toolbox_ShowObject_AsSubWindow,
                                 o,
                                 Toolbox_ShowObject_FullSpec,
                                 &show,
                                 p,
                                 -1));
  }

  if (!fixed_size && !b->small_fetch)
  {
    /* Fill in the small_size information for the window in its */
    /* browser_data structure                                   */

    b->small_size.visible_area = w.visible_area;

    /* Now that the window is open, can get it's outline etc.; */
    /* so can go up to full screen if required.                */

    if (b->full_screen) windows_open_full_screen(b,
                                                 1,
                                                 0,
                                                 choices.v_scroll,
                                                 choices.h_scroll);
  }

  /* If the requested size was larger than the available screen, */
  /* (most often seen on A5000s where 640x480x256 is in use and  */
  /* a new browser window is opened - the Choices want to make   */
  /* it larger than the space that's availale)                   */

//
//
//
//    /* Opening width considerations... */
//
//    b->display_width = b->display_extent = width;
//
//    if (b->display_extent < MinimumWidth) b->display_extent = MinimumWidth, b->display_width = MinimumWidth;
//
//    /* ...and opening height */
//
//    b->display_height = height;
//
//

  #ifdef TRACE
    if (tl & (1u<<3)) Printf("windows_show_browser: Successful\n");
  #endif
}

/*************************************************/
/* windows_open_full_screen()                    */
/*                                               */
/* Opens a browser window Full Screen or small,  */
/* optionally recording its previous size in the */
/* small_size fields when going to Full Screen.  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             1 for Full Screen, else 0;        */
/*                                               */
/*             1 to record the size, else 0;     */
/*                                               */
/*             1 to leave a vertical scroll bar  */
/*             visible, else 0;                  */
/*                                               */
/*             1 to leave a horizontal scroll    */
/*             bar visible, else 0.              */
/*************************************************/

_kernel_oserror * windows_open_full_screen(browser_data * b, int full_screen, int record, int vertsc, int horisc)
{
  BBox                      w;
  WimpOpenWindowBlock       o;
  WimpGetWindowStateBlock   s;
  WimpGetWindowOutlineBlock outline;
  ObjectId                  parent;

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

  /* Get the window state (for flags, scroll etc.) and its outline. */

  s.window_handle = o.window_handle = outline.window_handle = b->window_handle;

  RetError(wimp_get_window_state(&s));
  RetError(wimp_get_window_outline(&outline));

  /* If being asked to open to Full Screen, set up the BBox 'w' to */
  /* hold a visible area appropriate to a full screen window.      */

  if (full_screen)
  {
    /* Get the screen size in OS units */

    w.xmin = bbc_modevar(-1, BBC_XEigFactor);
    w.ymin = bbc_modevar(-1, BBC_YEigFactor);
    w.xmax = bbc_modevar(-1, BBC_XWindLimit);
    w.ymax = bbc_modevar(-1, BBC_YWindLimit);

    w.xmax = (w.xmax + 1) << w.xmin;
    w.ymax = (w.ymax + 1) << w.ymin;

    w.xmax -= ((s.flags & WimpWindow_VScroll) && (choices.v_scroll)) ? (outline.outline.xmax - s.visible_area.xmax) : 2;
    w.xmin  = 2;
    w.ymax -= 2;
    w.ymin  = ((s.flags & WimpWindow_HScroll) && (choices.h_scroll)) ? (s.visible_area.ymin - outline.outline.ymin) : 2;

    if (record)
    {
      /* Remember the current (small) size */

      b->small_size.visible_area = s.visible_area;
      b->small_size.xscroll      = s.xscroll;
      b->small_size.yscroll      = s.yscroll;
      b->small_size.behind       = -1;
    }
  }

  /* Otherwise, go back to the visible area stored in the window's */
  /* small_size.visible_area BBox.                                 */

  else
  {
    /* Revert to a previously stored small size */

    w = b->small_size.visible_area;
  }

  /* An irritating feature of the Wimp - if you call Wimp_SetExtent, */
  /* then the next Wimp_OpenWindow call is forced on-screen. This    */
  /* means that if toggle size is used when the window is partially  */
  /* dragged off-screen, then the shrinking horizontal extent when   */
  /* it is toggled back would ensure that the window was brought     */
  /* back fully on screen. Sometimes this is annoying.               */
  /*                                                                 */
  /* It turns out though, that if the window is *already* partially  */
  /* off-screen when the SetExtent call is made, the Wimp brings the */
  /* window (internally) back on screen, and doesn't bother to do    */
  /* this on the next OpenWindow call. So to allow the window to be  */
  /* toggled back to a small size which may be partially off-screen, */
  /* we open the window miles off screen somewhere before setting    */
  /* the extent.                                                     */
  /*                                                                 */
  /* This is not pretty but it is relatively solid; the worst that   */
  /* can happen is the Wimp *does* decide to force the window back   */
  /* on screen at the next OpenWindow call, in which case the        */
  /* behaviour is as it would be without the extra code here.        */

  o.visible_area.xmin = w.xmin + 16384;
  o.visible_area.ymin = w.ymin - 16384;
  o.visible_area.xmax = w.xmax + 16384;
  o.visible_area.ymax = w.ymax - 16384;
  o.behind            = -1;

  /* Show the object off-screen, making sure we don't trash the parent object ID information */

  RetError(toolbox_get_parent (0, b->self_id, &parent, NULL));
  RetError(toolbox_show_object(0, b->self_id, 1, &o.visible_area,parent, -1));

  /* Set up the OpenWindow block */

  o.window_handle = b->window_handle;
  o.visible_area  = w;
  o.xscroll       = s.xscroll;
  o.yscroll       = s.yscroll;
  o.behind        = -1;

  /* w holds the extent we require. If the horizontal extent is smaller */
  /* than the horizontal minimum defined for this window, make sure it  */
  /* is increased to that minimum. Ensure also that the vertical        */
  /* extent isn't too small, and never shrink it either. Note that the  */
  /* exception case is for when the parent has child frames - in that   */
  /* case, leave w alone, so the extent will track the visible area     */
  /* exactly (which is the desired effect).                             */

  if (!b->nchildren)
  {
    BBox extent;

    if (w.xmax - w.xmin < MinimumWidth) w.xmax = w.xmin + MinimumWidth;

    window_get_extent(0,b->self_id,&extent);

    if (w.ymax - w.ymin < extent.ymax - extent.ymin) w.ymax = w.ymin + extent.ymax - extent.ymin;
    if (w.ymax - w.ymin < MinimumHeight)             w.ymax = w.ymin + MinimumHeight;
  }

  RetError(set_corrected_extent(0, b->self_id, &w));

  /* If the Wimp supports changing window flags, set the */
  /* Back and No Bounds bits for a full screen show,     */
  /* else clear them.                                    */

  if (nested_wimp)
  {
    unsigned int clear, eor;

    clear = (
              (choices.full_screen && controls.back_window)
              ?
              WimpWindow_Back
              :
              0
            )
            | WimpWindow_NoBounds;

    if (full_screen) eor = clear;
    else             eor = 0;

    RetError(set_window_flags(b->window_handle, clear, eor));
  }

  /* Show the object in the correct on-screen position */

  RetError(toolbox_show_object(0,
                               b->self_id,
                               1,
                               &o.visible_area,
                               parent,
                               -1));

  /* Make sure the toolbars, if present, are up to date */

  if (choices.move_gadgets != Choices_MoveGadgets_Never) toolbars_move_gadgets(b);

  /* Update the visible area width and height fields */

  b->display_width = b->display_extent = o.visible_area.xmax - o.visible_area.xmin;
  if (b->display_extent < MinimumWidth) b->display_width = b->display_extent = MinimumWidth;

  {
    int h1, h2;

    h1 = toolbars_button_height(b) + toolbars_url_height(b);
    h2 = toolbars_status_height(b);

    if (h1) h1 += wimpt_dy();
    if (h2) h2 += wimpt_dy();

    b->display_height = o.visible_area.ymax - o.visible_area.ymin - h1 - h2;
  }

  /* Ensure any child frames are resized to fit */

  #ifdef TRACE
    if (tl & (1u<<3)) Printf("windows_open_full_screen: Exitting through frames_resize_frameset\n");
  #endif

  return frames_resize_frameset(b, &o.visible_area);
}

/*************************************************/
/* windows_process_icon_text()                   */
/*                                               */
/* Takes a Wimp icon handle and looks at a given */
/* text string, which can contain a special      */
/* string of characters instructing the function */
/* to perform various changes to the icon.       */
/*                                               */
/* The string must start with '@!' to indicate   */
/* it isn't some ordinary preinitialised string, */
/* and can contain these comma separated values: */
/*                                               */
/* N       Ensure the icon has (N)o border.      */
/* H       Ensure the icon (H)as a border.       */
/* Bn      Set background to colour n (n=hex).   */
/* Fn      Set foreground to colour n (n=hex).   */
/*                                               */
/* Parameters: Pointer to WimpGetIconStateBlock  */
/*             holding the window handle the     */
/*             icon is in and the icon's handle  */
/*             (the full icon state will be      */
/*             read in by this function, it does */
/*             not need to be done externally);  */
/*                                               */
/*             Pointer to the special text;      */
/*                                               */
/*             1 to set the first character of   */
/*             the text to a null byte (i.e. get */
/*             rid of the string) if it is a     */
/*             special string (otherwise it is   */
/*             left untouched), else 0.          */
/*************************************************/

void windows_process_icon_text(WimpGetIconStateBlock * icon, char * text, int remove)
{
  /* Check this is special text */

  if (!text || strlen(text) < 3) return;
  if (text[0] != '@' || text[1] != '!') return;

  /* It is, so process it */

  if (wimp_get_icon_state(icon)) return;
  else
  {
    WimpSetIconStateBlock state;
    int                   array, value;
    unsigned int          flags;

    array = 2;
    flags = icon->icon.flags;

    /* Parse the text */

    while (text[array])
    {
      switch (text[array])
      {
        case 'N': flags &= ~WimpIcon_Border, array++; break;
        case 'H': flags |=  WimpIcon_Border, array++; break;

        case 'B':
        case 'F':
        case 'C':
        {
          unsigned int mask, shift;

          if      (text[array] == 'B') shift = WimpIcon_BGColour;
          else if (text[array] == 'C') shift = WimpIcon_ButtonType;
          else                         shift = WimpIcon_FGColour;

          array ++;
          value = (int) strtoul(text + array, NULL, 16);
          array ++;

          if (value < 0)  value = 0;
          if (value > 15) value = 15;

          mask = 15 * shift;
          flags &= ~mask;

          flags |= (value * shift);
        }
        break;

        default: array++; break;
      }

      while (text[array] == ',') array++;
    }

    /* Set the new flags, if they've changed */

    if (flags != icon->icon.flags)
    {
      state.window_handle = icon->window_handle;
      state.icon_handle   = icon->icon_handle;
      state.clear_word    = 0xffffffff;
      state.EOR_word      = flags;

      wimp_set_icon_state(&state);
    }
  }

  /* Remove the text, if asked to do so */

  if (remove) text[0] = 0;

  return;
}

/*************************************************/
/* windows_process_component_text()              */
/*                                               */
/* Calls windows_process_icon_text for a given   */
/* component of a given Toolbox object.          */
/*                                               */
/* Parameters: Object ID the component lies in;  */
/*                                               */
/*             The component ID;                 */
/*                                               */
/*             Pointer to a buffer to hold the   */
/*             icon's text;                      */
/*                                               */
/*             Size of the buffer;               */
/*                                               */
/*             Out of the icons that make up the */
/*             component, the number of the one  */
/*             to alter (starting from 0);       */
/*                                               */
/*             1 to set the first character of   */
/*             the text to a null byte (i.e. get */
/*             rid of the string) if it is a     */
/*             special string (otherwise it is   */
/*             left untouched), else 0.          */
/*************************************************/

_kernel_oserror * windows_process_component_text(ObjectId o, ComponentId c, char * buffer,
                                                 int buffsize, int iconnum, int remove)
{
  _kernel_oserror       * e;
  WimpGetIconStateBlock   icon;
  int                     iconlist[10];

  if (buffsize < 2) return NULL;

  /* Get the object's window handle and the icon handle for the given component */

  e = window_get_wimp_handle(0, o, &icon.window_handle);

  #ifdef TRACE
    if (e) return e;
  #else
    if (e) return NULL;
  #endif

  e = gadget_get_icon_list(0, o, c, iconlist, sizeof(iconlist), NULL);
  if (e) return NULL; /* Don't throw errors as may not want this component for some builds */

  icon.icon_handle = iconlist[iconnum];

  /* Read, process, and set the text */

  e = wimp_get_icon_state(&icon);
  if (e) return NULL;

  strncpy(buffer, icon.icon.data.it.buffer, buffsize - 1);
  buffer[buffsize - 1] = 0;

  windows_process_icon_text(&icon, buffer, remove);

  if (icon.icon.data.it.buffer_size > 1) strncpy(icon.icon.data.it.buffer, buffer, icon.icon.data.it.buffer_size - 1);

  return NULL;
}

/*************************************************/
/* windows_initialise_tool_sizes()               */
/*                                               */
/* Sets up local copies of the current window    */
/* tool sizes through find_tool_sizes.           */
/*************************************************/

_kernel_oserror * windows_initialise_tool_sizes(void)
{
  return find_tool_sizes(&title_height, &hscroll_height, &vscroll_width);
}

/*************************************************/
/* windows_return_tool_sizes()                   */
/*                                               */
/* Returns the title bar and scroll bar widths   */
/* in OS units, including their outlines (some   */
/* of which will form part of the visible area   */
/* border).                                      */
/*                                               */
/* This will only work correctly if a call to    */
/* windows_initialise_tool_sizes has been made   */
/* at least once before.                         */
/*                                               */
/* Parameters: Pointer to an int, in which the   */
/*             title bar height is placed;       */
/*                                               */
/*             Pointer to an int, in which the   */
/*             horizontal scroll bar bar height  */
/*             is placed;                        */
/*                                               */
/*             Pointer to an int, in which the   */
/*             vertical scroll bar width is      */
/*             placed.                           */
/*                                               */
/* Assumes:    Any of the pointers may be NULL.  */
/*************************************************/

void windows_return_tool_sizes(int * theight, int * hheight, int * vwidth)
{
  if (theight) *theight = title_height;
  if (hheight) *hheight = hscroll_height;
  if (vwidth)  *vwidth  = vscroll_width;

  return;
}

/*************************************************/
/* windows_set_tools()                           */
/*                                               */
/* If using the nested Wimp, turns on/off        */
/* selected tools around a window.               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a BBox, in which the   */
/*             proposed visible area of the      */
/*             window may be placed. If NULL,    */
/*             it will be read from a call to    */
/*             Wimp_GetWindowState - however,    */
/*             scrollbars that you're trying to  */
/*             remove may have forced the area   */
/*             up somewhat, hence the ability to */
/*             give a desired pre-removal BBox;  */
/*                                               */
/*             1 for a title bar, else 0;        */
/*                                               */
/*             1 for a vertical scroll bar, else */
/*             0;                                */
/*                                               */
/*             1 for a horizontal scroll bar,    */
/*             else 0;                           */
/*                                               */
/*             1 for a resize icon, else 0.      */
/*                                               */
/* Assumes:    The BBox pointer may be NULL.     */
/*************************************************/

_kernel_oserror * windows_set_tools(browser_data * b, BBox * box, int title, int vscroll, int hscroll, int resize)
{
  if (!nested_wimp) return NULL;
  else
  {
    _kernel_oserror         * e;
    WimpGetWindowStateBlock   s;
    BBox                      extent;
    unsigned int              parent, align;
    unsigned int              old_flags;
    int                       th, hh, vw, must_redraw = 0, must_reformat = 0;

    /* If there's a highlight on this frame, remove it to avoid */
    /* complications with it getting left in the wrong place.   */

    if (b == highlight_frame) frames_remove_highlight();

    /* Get the window state and extent */

    s.window_handle = b->window_handle;

    RetError(_swix(Wimp_GetWindowState,
                   _INR(1, 2) | _OUTR(3, 4),

                   &s,
                   Magic_Word_TASK, /* See MiscDefs.h */

                   &parent,
                   &align));

    RetError(window_get_extent(0, b->self_id, &extent));

    /* Without the following line, the resize routines (such as windows_remember_size) can */
    /* set up display_extent and display_width to appropriate values and call the          */
    /* reformatter, waiting for it to set the actual extent to match these values. However */
    /* if this function gets in first - and it usually will - it will read the extent and  */
    /* then (re)set the values to the old settings; unless, that is, the following line is */
    /* included to spot that the actual extent and display_extent settings differ.         */
    /*                                                                                     */
    /* Of course, it's never *that* easy, so what we have to do is only set it if the      */
    /* extent we would have used is smaller. This is because during resizes of frames,     */
    /* documents can continue to reformat or reload (client pull) in the background, since */
    /* null events are coming in (compare to a parent window resize, where event 1,        */
    /* Wimp_EOpenWindow, is being generated). This can mean that display_extent is out of  */
    /* date, and forcing the extent to it shrinks the window. The right hand edge of the   */
    /* frame thus suddenly gets pulled along with the left (or vice versa) and the visual  */
    /* effect is that the frameset tears itself apart. Not nice.                           */
    /*                                                                                     */
    /* And yes, I know this is *another* overlong comment, but necessary nonetheless.      */

    if (extent.xmax - extent.xmin < b->display_extent) extent.xmax = extent.xmin + b->display_extent;

    /* Flags will only be set if they've changed */

    old_flags = s.flags;

    /* Need to know the window tool sizes */

    windows_return_tool_sizes(&th, &hh, &vw);

    /* The Wimp won't try to keep the visible footprint of the window */
    /* the same as border icons appear and disappear. This is pretty  */
    /* useless from the browser's point of view; need to manually     */
    /* play around with the visible area to cope.                     */

    if ((s.flags & WimpWindow_TitleIcon) && !title) s.visible_area.ymax += th, extent.ymax += th;
    if (!(s.flags & WimpWindow_TitleIcon) && title) s.visible_area.ymax -= th, extent.ymax -= th;

    if ((s.flags & WimpWindow_VScroll) && !vscroll) s.visible_area.xmax += vw, extent.xmax += vw, must_redraw = 1;
    if (!(s.flags & WimpWindow_VScroll) && vscroll) s.visible_area.xmax -= vw, extent.xmax -= vw, must_redraw = 1, must_reformat = 1;

    if ((s.flags & WimpWindow_HScroll) && !hscroll) s.visible_area.ymin -= hh, extent.ymin -= hh;
    if (!(s.flags & WimpWindow_HScroll) && hscroll) s.visible_area.ymin += hh, extent.ymin += hh;

    /* Modify them */

    if (title)   s.flags = s.flags |  WimpWindow_TitleIcon;
    else         s.flags = s.flags &~ WimpWindow_TitleIcon;

    if (vscroll) s.flags = s.flags |  WimpWindow_VScroll;
    else         s.flags = s.flags &~ WimpWindow_VScroll;

    if (hscroll) s.flags = s.flags |  WimpWindow_HScroll;
    else         s.flags = s.flags &~ WimpWindow_HScroll;

    if (resize)  s.flags = s.flags |  WimpWindow_SizeIcon;
    else         s.flags = s.flags &~ WimpWindow_SizeIcon;

    /* If the flags have changed... */

    if (s.flags != old_flags)
    {
      WindowShowObjectBlock show;
      ObjectId              tparent;

      /* If there's a highlight on this frame, remove it to avoid */
      /* complications with it getting left in the wrong place.   */

      if (b == highlight_frame) frames_remove_highlight();

      /* Ensure the extent is corrected for the addition or removal of tools */

      RetError(window_set_extent(0, b->self_id, &extent));

      /* Alter the flags */

      show.visible_area         = s.visible_area;
      show.xscroll              = s.xscroll;
      show.yscroll              = s.yscroll;
      show.behind               = s.behind;
      show.window_flags         = s.flags;
      show.parent_window_handle = parent;
      show.alignment_flags      = align | Alignment_NewFlagsGiven;

      RetError(toolbox_get_parent(0, b->self_id, &tparent, NULL));

      if (nested_wimp)
      {
        /* For the nested Wimp, simple - just reopen with the new-style call */

        e = toolbox_show_object(Toolbox_ShowObject_AsSubWindow,
                                b->self_id,
                                Toolbox_ShowObject_FullSpec,
                                &show,
                                tparent,
                                -1);
      }
      else
      {
        // This code not done yet (not urgent enough for now).
        // *If* eventually implemented, the 'if' wrapper around
        // the whole of this function's code obviously needs to
        // be removed first.

        e = NULL;
      }

      if (e) return e;

      /* If must_redraw is set, redraw errors could occur unless the window is redrawn now. */

      if (must_redraw)
      {
        BBox workarea = s.visible_area;

        coords_box_toworkarea(&workarea, (WimpRedrawWindowBlock *) &s);

        RetError(wimp_force_redraw(s.window_handle,
                                   workarea.xmin,
                                   workarea.ymin,
                                   workarea.xmax,
                                   workarea.ymax));

        /* May well need to move the toolbar gadgets, too */

        if (choices.move_gadgets != Choices_MoveGadgets_Never) toolbars_move_gadgets(b);
      }

      /* If must_reformat is set, for childless windows, reformat the page */

      if (must_reformat && !b->nchildren) reformat_format_from(b, -1, 1, -1);
    }

    /* Update the various browser width records in light of any */
    /* changes in the window, and similarly update the height   */

    b->display_extent = extent.xmax - extent.xmin;
    b->display_width  = s.visible_area.xmax - s.visible_area.xmin;
    if (b->display_width < MinimumWidth) b->display_width = MinimumWidth;

    {
      int h1, h2;

      h1 = toolbars_button_height(b) + toolbars_url_height(b);
      h2 = toolbars_status_height(b);

      if (h1) h1 += wimpt_dy();
      if (h2) h2 += wimpt_dy();

      b->display_height = s.visible_area.ymax - s.visible_area.ymin - h1 - h2;
    }

    /* If there are children and this is an ancestor window, resize the children */

    if (!b->ancestor && b->nchildren) frames_resize_frameset(b, &s.visible_area);

    return NULL;
  }
}

/*************************************************/
/* windows_check_tools()                         */
/*                                               */
/* Looks at a window, and if in a certain        */
/* direction the visible area and extent are     */
/* the same, turns off scroll bars (they aren't  */
/* needed).                                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the window;           */
/*                                               */
/*             Pointer to a BBox, passed on to   */
/*             windows_set_tools (see that       */
/*             function for details).            */
/*************************************************/

_kernel_oserror * windows_check_tools(browser_data * b, BBox * box)
{
  WimpGetWindowStateBlock s;
  BBox                    extent;
  int                     hscroll = 0, vscroll = 0;

  /* For small fetch windows, don't want to touch the tools */

  if (b->small_fetch) return NULL;

  /* Otherwise, proceed */

  s.window_handle = b->window_handle;

  RetError(wimp_get_window_state(&s));
  RetError(window_get_extent(0, b->self_id, &extent));

  /* Turn on scroll bars if the visible area is less than the */
  /* extent in the appropriate direction.                     */

  if (s.visible_area.xmax - s.visible_area.xmin < extent.xmax - extent.xmin) hscroll = 1;
  if (s.visible_area.ymax - s.visible_area.ymin < extent.ymax - extent.ymin) vscroll = 1;

  if (!b->ancestor && !b->full_screen)
  {
    /* Ancestor windows should always have both scroll bars, unless */
    /* running full screen.                                         */

    hscroll = vscroll = 1;
  }
  else
  {
    /* Otherwise, read the scroll bar flags */

    if (!b->nchildren)
    {
      /* Frames or full screen windows with no children themselves*/

      if      (b->frame_hscroll == 0) hscroll = 0; /* No */
      else if (b->frame_hscroll == 2) hscroll = 1; /* Yes, else use values found above (i.e. auto) */

      /* If there is a vertical scroll bar present already, and this window */
      /* has no children, don't want to remove that bar. This is so that    */
      /* frames can start with no scroll bar, then later the reformatter    */
      /* may decide to add one. Since the page width changes the            */
      /* windows_set_tools call will call the reformatter again. However,   */
      /* the new reformat could turn off the vertical scroll bar - so the   */
      /* reformatter locks into a recursive loop. Hence, can only ever      */
      /* remove a vscroll for a childless window by an explicit call to     */
      /* windows_set_tools; otherwise, once it's added, it stays.           */
      /*                                                                    */
      /* The exception, of course, is for frames with scrolling explicitly  */
      /* set to 'no'.                                                       */

      if (!b->frame_vscroll)                                            vscroll = 0;
      else if (b->frame_vscroll == 2 || (s.flags & WimpWindow_VScroll)) vscroll = 1;

//      if      (b->frame_vscroll == 0) vscroll = 0;
//      else if (b->frame_vscroll == 2) vscroll = 1;
    }
    else
    {
      /* Frames with children *must* have no scroll bars, or resize routines */
      /* later on, which read visible areas, will fail, and the bars will be */
      /* visible in gaps between frames.                                     */

      hscroll = vscroll = 0;
    }
  }

  RetError(windows_set_tools(b, box, !b->ancestor, vscroll, hscroll, !b->full_screen && !b->ancestor));

  return NULL;
}