/* 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   : Printing.c                             */
/*                                                 */
/* Purpose: Printing functions for the browser.    */
/*                                                 */
/*          This source is fairly closely tied to  */
/*          PrintStyle.c, as the Print dialogue    */
/*          can open and close the Print Style     */
/*          dialogue.                              */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 27-Jan-97: Created.                    */
/*          25-Aug-97: Overhaul (read rewrite) to  */
/*                     the new dialogue handling   */
/*                     model, as for Open URL etc. */
/***************************************************/

#include "signal.h"

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

#include "swis.h"

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

#include "toolbox.h"
#include "printdbox.h"

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

#include "Browser.h"
#include "FontManage.h"
#include "Images.h"
#include "Memory.h"
#include "Protocols.h"
#include "PrintStyle.h"
#include "Redraw.h"
#include "Reformat.h"
#include "Toolaction.h"
#include "Toolbars.h"
#include "Windows.h"

#include "Printing.h"

/* Local structures.                                           */
/*                                                             */
/* Holds info on the Print dialogue's contents; small enough   */
/* to hold as a static, as the code to dynamically allocate it */
/* would occupy more room than the structure itself.           */

#define End_Whole   0
#define End_Visible 1
#define End_Many    2

typedef struct
{
  int            copies;          /* Number of copies to print.                                                        */
  int            pages;           /* If 'end' is 2, the number of pages to fill.                                       */
  unsigned int   end          :2; /* 0 = whole page, 1 = to bottom of visible area, 2 = for 'pages' pages.             */
  unsigned int   start        :1; /* 1 = whole page, 0 = top of visible area.                                          */
  unsigned int   reformat     :1; /* 1 = reformat to fit page (if start is not 0 and end is not -1), else don't.       */
  unsigned int   orientation  :1; /* 1 = portrait, 0 = landscape.                                                      */

} print_contents;

/* The following stores the four basic display type settings */
/* (underline links, show images etc.) for the browser to be */
/* printed. This is so that the settings may be restored     */
/* after a print.                                            */

typedef struct
{
  unsigned int underline_links :1;
  unsigned int use_source_cols :1;
  unsigned int show_foreground :1;
  unsigned int show_background :1;

} print_restorable;

/* Local variables */

static int              globaljob        = 0;
static int              globalold_job    = 0;

static int              defaults_set     = 0;

static ObjectId         self_id          = 0;
static ObjectId         window_id        = 0;
static ObjectId         ancestor_id      = 0;
static browser_data   * ancestor_browser = NULL;

static print_contents   contents;
static print_restorable restore;

/* Static function prototypes */

static _kernel_oserror * print_read_contents   (ObjectId dialogue, print_contents * contents);
static _kernel_oserror * print_set_contents    (ObjectId dialogue, print_contents * contents);

static int               print_start           (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);
static int               print_cancel          (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);

static int               print_check_contents  (int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle);

static _kernel_oserror * print_page            (browser_data * b, int copies, int from, int end, int to, int reformat, int orientation, const char * path);

static void              print_prepare_browser (browser_data * source, browser_data * store, int lmarg, int rmarg, int tmarg, int bmarg);
static void              print_restore_browser (browser_data * original, browser_data * copy);

/*************************************************/
/* print_open_for()                              */
/*                                               */
/* Creates and opens a Print dialogue for a      */
/* given browser, opening near the pointer.      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             which is the ancestor of the      */
/*             dialogue;                         */
/*                                               */
/*             Object ID to use as a parent, or  */
/*             0 for none.                       */
/*************************************************/

_kernel_oserror * print_open_for(browser_data * b, ObjectId parent)
{
  ObjectId id;

  /* Create the object - if it is already created, this will */
  /* just return the ID of the existing object.              */

  RetError(toolbox_create_object(0,
                                 "PrintDbox",
                                 &id));

  RetError(toolbox_show_object(0,
                               id,
                               Toolbox_ShowObject_Centre,
                               NULL,
                               parent,
                               -1));

  return NULL;
}

/*************************************************/
/* print_read_contents()                         */
/*                                               */
/* Reads the contents of the Print dialogue      */
/* into a print_contents structure.              */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Pointer to the structure to write */
/*             to.                               */
/*************************************************/

static _kernel_oserror * print_read_contents(ObjectId dialogue, print_contents * contents)
{
  int state, selected;

  /* Start at top of visible area (0) or whole page (1) radios */

  RetError(radiobutton_get_state(0, dialogue, PStartWhole, &state, NULL));
  contents->start = !!state;

  /* End radios - bottom of page, of visible area, or stop after */
  /* pages defined in the 'PEndManyNum' number range gadget      */

  RetError(radiobutton_get_state(0, dialogue, PEndWhole, NULL, &selected));
  RetError(numberrange_get_value(0, dialogue, PEndManyNum, &contents->pages));

  /* Note that PEndVisible etc. are component IDs defined in */
  /* Print.h, whilst End_Visible (with the underscore) etc.  */
  /* are option values defined at the top of this file.      */

  switch (selected)
  {
    default:
    case PEndWhole:   contents->end = End_Whole;
    break;

    case PEndVisible: contents->end = End_Visible;
    break;

    case PEndMany:    contents->end = End_Many;
    break;
  }

  /* Reformat page to fit */

  RetError(optionbutton_get_state(0, dialogue, PReformatToFit, &state));
  contents->reformat = !!state;

  /* Orientation radios; portrait (1) or landscape (0) */

  RetError(radiobutton_get_state(0, dialogue, POriUpright, &state, NULL));
  contents->orientation = !!state;

  /* Read the 'Number of copies' number range gadget */

  RetError(numberrange_get_value(0, dialogue, PCopiesNum, &contents->copies));

  return NULL;
}

/*************************************************/
/* print_set_contents()                          */
/*                                               */
/* Sets the contents of the Print dialogue from  */
/* a print_contents structure.                   */
/*                                               */
/* Parameters: Object ID of the dialogue;        */
/*                                               */
/*             Pointer to the structure to read  */
/*             from.                             */
/*************************************************/

static _kernel_oserror * print_set_contents(ObjectId dialogue, print_contents * contents)
{
  /* Start position */

  if (!contents->start) RetError(radiobutton_set_state(0, dialogue, PStartVisible, 1))
  else                  RetError(radiobutton_set_state(0, dialogue, PStartWhole,   1));

  /* End position, including the 'number of pages to fill' number range */

  switch (contents->end)
  {
    default:
    case End_Whole:     RetError(radiobutton_set_state(0, dialogue, PEndWhole,   1));
    break;
    case End_Visible:   RetError(radiobutton_set_state(0, dialogue, PEndVisible, 1));
    break;
    case End_Many:      RetError(radiobutton_set_state(0, dialogue, PEndMany,    1));
    break;
  }

  RetError(numberrange_set_value(0, dialogue, PEndManyNum, contents->pages));

  /* The reformat option, including greying / ungreying it */

  RetError(optionbutton_set_state(0, dialogue, PReformatToFit, contents->reformat));

  /* As well as greying / ungreying the reformat option, this handles */
  /* the label text on the 'pages to fill' number range.              */

  print_check_contents(0, NULL, NULL, NULL);

  /* Orientation */

  if (!contents->orientation) RetError(radiobutton_set_state(0, dialogue, POriSideways, 1))
  else                        RetError(radiobutton_set_state(0, dialogue, POriUpright,  1));

  /* Number of copies */

  RetError(numberrange_set_value(0, dialogue, PCopiesNum, contents->copies));

  return NULL;
}

/*************************************************/
/* print_set_defaults()                          */
/*                                               */
/* Fills in the local print_contents structure   */
/* with the default values to put in a Print     */
/* dialogue, if they have not already been       */
/* filled in.                                    */
/*                                               */
/* If the dialogue is open, the contents are     */
/* updated.                                      */
/*                                               */
/* Returns:    1 if the structure was filled in, */
/*             else 0.                           */
/*************************************************/

int print_set_defaults(void)
{
  if (!defaults_set)
  {
    /* Number of copies */

    contents.copies = atoi(lookup_choice("PrintCopies:1",0,0));

    /* Check it is within bounds */

    if (contents.copies < Limits_Lower_Copies) contents.copies = Limits_Lower_Copies;
    if (contents.copies > Limits_Upper_Copies) contents.copies = Limits_Upper_Copies;

    /* Start position - 'start' or 'visible', though in fact any */
    /* non-'visible' string defaults as 'start'.                 */

    if (!strcmp(lookup_choice("PrintStart:start",0,0),"visible")) contents.start = 0;
    else contents.start = 1;

    /* End position - print the whole page, down to the bottom of the */
    /* visible area, or fill up as many sheets as specified in the    */
    /* 'pages' field of the print_contents structure (see below).     */

    if (!strcmp(lookup_choice("PrintEnd:end",0,0),"end"))      contents.end = End_Whole;
    else if (!strcmp(lookup_choice("PrintEnd",1,0),"visible")) contents.end = End_Visible;
    else                                                       contents.end = End_Many;

    contents.pages = atoi(lookup_choice("PrintEnd",1,0));

    /* Check it is within bounds */

    if (contents.pages < Limits_Lower_Sheets) contents.pages = Limits_Lower_Sheets;
    if (contents.pages > Limits_Upper_Sheets) contents.pages = Limits_Upper_Sheets;

    /* Reformat - 'yes' or 'no', default to 'yes' */

    if (!strcmp(lookup_choice("PrintReform:yes",0,0),"no")) contents.reformat = 0;
    else contents.reformat = 1;

    /* Orientation - 'upright' or 'sideways', though in fact any */
    /* non-'sideways' string defaults as 'upright'.              */

    if (!strcmp(lookup_choice("PrintOrient:upright",0,0),"sideways")) contents.orientation = 0;
    else contents.orientation = 1;

    defaults_set = 1;

    if (window_id) print_set_contents(window_id, &contents);

    return 1;
  }

  else return 0;
}

/*************************************************/
/* print_to_be_shown()                           */
/*                                               */
/* Called before a print dialogue opens. Deals   */
/* with setting this up with default values and  */
/* filling in print_old as appropriate, so that  */
/* if the dialogue is cancelled its contents may */
/* be correctly restored.                        */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event hander.                                 */
/*************************************************/

int print_to_be_shown(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  int      was_open = 0;
  ObjectId ps_window;
  ObjectId ps_ancestor;

  /* If the stored dialogue ID is non-zero on entry, the dialogue */
  /* was reopened without closing - so get rid of the various     */
  /* event handlers before we reregister them.                    */

  if (window_id)
  {
    /* Was the Print Style window open too? */

    printstyle_return_dialogue_info(&ps_window, &ps_ancestor);

    if (ps_window) was_open = 1;

    /* This will close the Print window and Print Style (if open) */

    print_close(0, 1);
  }

  /* Record the dialogue ID, the ancestor ID, and if this is */
  /* non-zero, the browser to which that ID refers.          */

  self_id     = idb->self_id;
  ancestor_id = idb->ancestor_id;

  if (ancestor_id) ChkError(toolbox_get_client_handle(0, ancestor_id, (void *) &ancestor_browser));

  /* If this is for an ancestor browser, use whatever frame is selected */
  /* instead - allows toolbar buttons and keyboard shortcuts to work in */
  /* a sensible fashion...                                              */

  if (ancestor_browser->selected_frame)
  {
    ancestor_browser = ancestor_browser->selected_frame;
    ancestor_id      = ancestor_browser->self_id;
  }

  /* If we have a browser, remember its restorable details. */

  if (ancestor_browser)
  {
    restore.underline_links = ancestor_browser->underline_links;
    restore.use_source_cols = ancestor_browser->use_source_cols;
    restore.show_foreground = ancestor_browser->show_foreground;
    restore.show_background = ancestor_browser->show_background;

    /* If required, force background images off */

    if (!strcmp(lookup_choice("PrintPlain:yes",0,0),"yes")) browser_set_look(ancestor_browser,
                                                                             window_id,
                                                                             -1,
                                                                             -1,
                                                                             -1,
                                                                             0);
  }

  /* Get the underlying window ID */

  ChkError(printdbox_get_window_id(0, self_id, &window_id));

  /* The Print Style dialogue may be open, too. We could take the lazy route */
  /* and just close it, but instead, we will ask it for its window details,  */
  /* and then recall its close code and ToBeShown code with the right info.  */

  if (was_open)
  {
    IdBlock  ps_id;

    ps_id.self_id = ps_window;

    /* (Make sure the Print Style routines ask the Print routines for the (new) ancestor) */

    ps_id.ancestor_id = NULL;

    printstyle_to_be_shown(0, NULL, &ps_id, NULL);
  }
  /* Register handlers for alternate Print/Cancel buttons */

  ChkError(event_register_toolbox_handler(window_id,
                                          EPStartPrint,
                                          print_start,
                                          (void *) ancestor_id));

  ChkError(event_register_toolbox_handler(window_id,
                                          EPCancelPrint,
                                          print_cancel,
                                          (void *) ancestor_id));

  /* Various alterations of icons / buttons for different UI styles */

  if (!strcmp(lookup_control("AlterNumranges:no",0,0),"yes"))
  {
    _kernel_oserror       * e;
    WimpGetIconStateBlock   icon;
    int                     iconlist [Limits_NRangeIcons];
    char                    buffer   [Limits_Message];

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

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

    if (!e)
    {
      ComponentId writable;
      int         loop;

      for (loop = 0; loop < 2; loop ++)
      {
        /* Get the number range's writable component ID */

        e = numberrange_get_components(NumberRange_GetComponents_ReturnNumericalField,
                                      window_id,
                                      loop == 1 ? PCopiesNum : PEndManyNum,
                                      &writable,
                                      NULL,
                                      NULL,
                                      NULL);

        /* Turn this into an icon handle */

        if (!e) e = gadget_get_icon_list(0, window_id, writable, iconlist, sizeof(iconlist), NULL);

        if (!e)
        {
          icon.icon_handle = iconlist[0];

          /* Get the icon state and set the icon flags with the */
          /* programming text defined in the Messages file      */

          e = wimp_get_icon_state(&icon);

          if (!e)
          {
            strncpy(buffer, lookup_control("AlterWith",1,0), sizeof(buffer) - 1);
            buffer[sizeof(buffer) - 1] = 0;

            windows_process_icon_text(&icon, buffer, 0);
          }
        }
      }
    }
  }

  /* Register a handler to cope with the pages number range changing */

  ChkError(event_register_toolbox_handler(window_id,
                                          NumberRange_ValueChanged,
                                          print_check_contents,
                                          (void *) window_id));

  /* Similarly, the same function is called to ensure things are greyed */
  /* or ungreyed as required when the radio buttons that affect the     */
  /* 'Reformat page to fit paper' option are activated.                 */

  ChkError(event_register_toolbox_handler(window_id,
                                          EPEnableReformat,
                                          print_check_contents,
                                          (void *) window_id));

  /* Install an animation handler, if there's an appropriate gadget */

  if (
       controls.dbox_anims &&
       !gadget_get_type(0, window_id, StatusBarAnimAnim, NULL)
     )
     register_null_claimant(Wimp_ENull,
                            toolbars_animate_slow,
                            (void *) window_id);

  /* If defaults have never been set before, set them now */

  print_set_defaults();

  /* Make sure the Print Style dialogue is set up, too */

  printstyle_set_defaults();

  /* Done! */

  return 1;
}

/*************************************************/
/* print_start()                                 */
/*                                               */
/* Handles clicks on the 'OK' (or 'Print', etc.) */
/* button in the Print dialogue.                 */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int print_start(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  /* First, make sure we effectively OK the contents of the */
  /* Print Style dialogue.                                  */

  printstyle_ok(0, NULL, NULL, NULL);

  /* Because the printing starts from reception of an external message, */
  /* can't use a local copy of the print_contents structure and allow   */
  /* Adjust-clicks on Print/OK/Whatever. So always close the window.    */

  ChkError(print_read_contents(window_id, &contents));
  ChkError(print_close(0, 0));

  /* First stage of printing protocol: Broadcast a PrintSave message */

  ChkError(protocols_pp_send_print_save());

  return 1;
}

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

static int print_cancel(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  WimpGetPointerInfoBlock info;

  /* Restore the old contents */

  ChkError(print_set_contents(window_id, &contents));

  /* If Select was pressed, the dialogue should close. */
  /* (No button => Escape was pressed).                */

  ChkError(wimp_get_pointer_info(&info));

  if ((info.button_state & Wimp_MouseButtonSelect) || !info.button_state)
  {
    ChkError(print_close(0, 0));

    /* If we forced background images off, put them back again */

    if (!strcmp(lookup_choice("PrintPlain:yes",0,0),"yes")) browser_set_look(ancestor_browser,
                                                                             window_id,
                                                                             -1,
                                                                             -1,
                                                                             -1,
                                                                             restore.show_background);
  }

  return 1;
}

/*************************************************/
/* print_close()                                 */
/*                                               */
/* If the Print dialogue is opened, this will    */
/* close it, deregistering any associated event  */
/* handlers.                                     */
/*                                               */
/* Parameters: An object ID, or 0. If not zero,  */
/*             the ID must match the ancestor    */
/*             recorded when the dialogue was    */
/*             opened or no action is taken.     */
/*                                               */
/*             0 to close the dialogue, 1 to do  */
/*             everything except that.           */
/*************************************************/

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

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

  /* If the Print Style window is open, this will close it */

  printstyle_close(ancestor_id, do_not_close);

  if (window_id)
  {
    /* Deregister associated event handlers */

    e = event_deregister_toolbox_handlers_for_object(window_id);
    if (e) goto print_close_exit;

    /* If there was a null handler, remove it */

    if (
         controls.dbox_anims &&
         !gadget_get_type(0, window_id, StatusBarAnimAnim, NULL)
       )
       deregister_null_claimant(Wimp_ENull,
                                toolbars_animate_slow,
                                (void *) window_id);

    if (!do_not_close)
    {
      /* Restore input focus to the browser window, if the */
      /* print dialogue still had it.                      */

      if (ancestor_id != NULL_ObjectId)
      {
        WimpGetCaretPositionBlock caret_b;
        int                       caret_w;

        /* Do we have the input focus? */

        e = wimp_get_caret_position(&caret_b);

        if (!e)
        {
          e = window_get_wimp_handle(0,
                                     window_id,
                                     &caret_w);

          if (caret_w == caret_b.window_handle)
          {
            e = browser_give_general_focus(ancestor_browser);
            if (e) goto print_close_exit;
          }
        }
      }

      /* Close the dialogue */

      e = toolbox_hide_object(0, self_id);
    }
  }

print_close_exit:

  self_id = window_id = 0;

  return e;
}

/*************************************************/
/* print_check_contents()                        */
/*                                               */
/* If the state of the various radio buttons     */
/* changes, this may be called to see if the     */
/* Reformat option in the Print dialogue should  */
/* be enabled (ungreyed) or disabled (greyed).   */
/* Similarly, if the contents of the number of   */
/* sheets to fill number range changes, this     */
/* should be called to ensure the label has the  */
/* correct pluralisation applied.                */
/*                                               */
/* Parameters are as standard for a Toolbox      */
/* event handler.                                */
/*************************************************/

static int print_check_contents(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
{
  int          state1, state2, pages;
  unsigned int flags;

  /* Get the radio button states */

  ChkError(radiobutton_get_state(0, window_id, PStartWhole, NULL, &state1));
  ChkError(radiobutton_get_state(0, window_id, PEndWhole,   NULL, &state2));

  /* If the PStartVisible and PEndVisible radios are not selected, */
  /* can enable the Reformat option; else disable it. But only     */
  /* change it's state (don't grey it if already greyed, say).     */

  ChkError(gadget_get_flags(0, window_id, PReformatToFit, &flags));

  if (state1 != PStartVisible && state2 != PEndVisible)
  {
    if (flags & Gadget_Faded)
    {
      ChkError(gadget_set_flags(0, window_id, PReformatToFit, flags & ~Gadget_Faded));
    }
  }
  else
  {
    if (!(flags & Gadget_Faded))
    {
      ChkError(gadget_set_flags(0, window_id, PReformatToFit, flags | Gadget_Faded));
    }
  }

  /* Check the pages number range, and update the label if necessary. */

  {
    char text[Limits_PEndManyLabel];

    ChkError(numberrange_get_value(0, window_id, PEndManyNum, &pages));

    ChkError(button_get_value(0, window_id, PEndManyLabel, text, Limits_PEndManyLabel, NULL));
    text[sizeof(text) - 1] = 0;

    if (pages == 1)
    {
      /* If the existing text isn't what we intend to change it to, then change it; */
      /* i.e. don't set the same thing twice, as this will flicker badly.           */

      if (strcmp(text, lookup_token("PagesSingle:sheet is filled",0,0)))
      {
        ChkError(button_set_value(0, window_id, PEndManyLabel, lookup_token("PagesSingle:sheet is filled",0,0)));
      }
    }
    else
    {
      /* Again, only change the text - don't set the same thing twice. */

      if (strcmp(text, lookup_token("PagesMany:sheets are filled",0,0)))
      {
        ChkError(button_set_value(0, window_id, PEndManyLabel, lookup_token("PagesMany:sheets are filled",0,0)));
      }
    }
  }

  return 1;
}

/*************************************************/
/* print_print()                                 */
/*                                               */
/* Calls the printing engine with parameters     */
/* specified in the local static print_info      */
/* structure 'print_current'.                    */
/*                                               */
/* Entry point is typically from a handler       */
/* dealing with the printing message protocol    */
/* (see handle_messages).                        */
/*                                               */
/* Parameters: Pointer to pathname to print to,  */
/*             or NULL to go straight to the     */
/*             'printer:' device.                */
/*************************************************/

void print_print(const char * path)
{
  _kernel_oserror * e;

  /* Must have a browser to print */

  if (!ancestor_browser) return;

  /* Do the printing */

  printing = 1;

  e = print_page(ancestor_browser,
                 contents.copies,
                 contents.start,
                 contents.end,
                 contents.pages,
                 contents.reformat,
                 contents.orientation,
                 path);

  printing = 0;

  if (e) show_error_ret(e);

  /* On completion, with or without error (as e.g. Escape may */
  /* be pressed and you'd still want the following), restore  */
  /* the basic browser display characteristics, if the Print  */
  /* dialogue was closed (i.e. Print activated with Select).  */

  if (!window_id)
  {
    show_error_ret(browser_set_look(ancestor_browser,
                                    0,
                                    restore.underline_links,
                                    restore.use_source_cols,
                                    restore.show_foreground,
                                    restore.show_background));
  }
}

/*************************************************/
/* print_page()                                  */
/*                                               */
/* Prints out a page, assuming that all the      */
/* relevant protocol stuff to ensure it's OK to  */
/* proceed has been done already.                */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page to print;    */
/*                                               */
/*             The number of copies to print;    */
/*                                               */
/*             1 to start at the top of the web  */
/*             page, else start from the top of  */
/*             the visible area in the window;   */
/*                                               */
/*             Where to end - End_Whole,         */
/*             End_Visible or End_Many (as       */
/*             defined at the top of the file);  */
/*                                               */
/*             For End_Many, how many sheets to  */
/*             fill;                             */
/*                                               */
/*             1 to reformat to fit the page     */
/*             width (orientation is taken into  */
/*             account), else 0 to keep the      */
/*             width of the window (if it falls  */
/*             off the page, tough...!);         */
/*                                               */
/*             1 = portrait, 0 = landscape;      */
/*                                               */
/*             Pointer to pathname to print to,  */
/*             or NULL to go straight to the     */
/*             'printer:' device.                */
/*************************************************/

static _kernel_oserror * print_page(browser_data * b, int copies, int from, int end, int to,
                                    int reformat, int orientation, const char * path)
{
  _kernel_oserror       * e = NULL;

  WimpRedrawWindowBlock   redraw;

  BBox                    box, last_rect;

  int                     must_restore;
  int                     job,            old_job;
  int                     more,           page;
  int                     top,            bottom;
  int                     next_line,      temp;
  int                     estimated_pages;
  int                     area_completed, page_area;
  int                     lmarg,          bmarg;
  int                     rmarg,          tmarg;

  unsigned int            features;

  int                     portrait  [2] [2] = { {0x10000,  0}, {0, 0x10000} };
  int                     landscape [2] [2] = { {0, -0x10000}, {0x10000, 0} };

  browser_data            localbrowser;

  void (*old_sigint_handler) (int);

  /* Check to see if there is a printer driver ready */

  e = _swix(PDriver_Info,
            _OUT(3),

            &features);

  if (e) return e;

  /* Find the current page margins (and therefore, page size, */
  /* as all margins are expressed as offsets from the bottom  */
  /* left hand corner of the paper).                          */

  e = _swix(PDriver_PageSize,
            _OUTR(3,6),

            &lmarg,
            &bmarg,
            &rmarg,
            &tmarg);

  if (e) return e;

  /* If in landscape mode, want to treat the margins */
  /* in a reversed sense.                            */

  if (!orientation)
  {
    Swap(tmarg,rmarg);
    Swap(bmarg,lmarg);
  }

  /* Start the hourglass, this could take a while.              */
  /*                                                            */
  /* Using Hourglass_Start as otherwise the first percentage    */
  /* setting may be missed, since the hourglass isn't actually  */
  /* on yet (there's a default delay before appearance with     */
  /* calling Hourglass_On).                                     */

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

  /* If the user specified printing to or from something that */
  /* depends upon the visible area, can't then reformat.      */

  if (reformat && end != End_Visible && from)
  {
    int leds = 1;

    /* Reformat the page ready for printing. It used to be possible to do all of */
    /* this in a separate browser_data structure and, being careful about flex,  */
    /* reformat in that 'virtual' browser. This enabled reformatting internally  */
    /* not to affect the main browser page.                                      */
    /*                                                                           */
    /* Tables, however, screwed this up big time. Table cells were malloced, but */
    /* no record of this was kept, in the first cut of the code. So in the end,  */
    /* the address of the cell array was kept in the HStream defining the table. */
    /* However, you can only have one user of that at any one time...            */
    /*                                                                           */
    /* Four solutions to this (where NA = Not Acceptable):                       */
    /*                                                                           */
    /* 1. Get rid of the 'reformat to fit page' option (NA)                      */
    /* 2. Only allow the above when there are no tables on the page (NA)         */
    /* 3. Copy the entire token stream as well as the flex data (NA)             */
    /* 4. Reformat in the actual browser and have it reformat again afterwards.  */
    /*                                                                           */
    /* Since 1 to 3 aren't acceptable - 3 mostly because not only is it a lot of */
    /* memory to have to find, but it's in malloc space -> WimpSlot problems -   */
    /* only 4 is left. So this is what we now do here. Consequently, lots of     */
    /* bits of the browser_data structure have to be copied away and restored    */
    /* later, which can get quite messy.                                         */
    /*                                                                           */
    /* The fact that option 4 was chosen doesn't mean it isn't hideous...        */

    print_prepare_browser(b, &localbrowser, lmarg, rmarg, tmarg, bmarg);
    must_restore = 1;

    /* Now call the reformatter, and loop round until finished. */

    e = reformat_format_from(b, -1, 1, -1);
    if (e) return e;

    while (reformat_formatting(b))
    {
      reformat_reformatter(b);

      /* It is virtually impossible to assess progress without */
      /* doing something time consuming like scan the token    */
      /* list and work out how far down it we are, compared to */
      /* the whole length. Instead, alternate the LEDs - this  */
      /* fits in well with what the table reformatter code     */
      /* will be doing with the hourglass.                     */

      leds ^= 3;

      _swix(Hourglass_LEDs,
            _INR(0,1),

            3,
            leds);
    }

    _swix(Hourglass_LEDs, 0, 0);
  }
  else must_restore = 0;

  localbrowser.use_source_cols = 0;

  /* Open up the output stream */

  e = _swix(OS_Find,
            _INR(0,1) | _OUT(0),

            0x8F,
            path ? path : "printer:",

            &job);

  if (e) goto out3;

  /* Estimate the number of pages to print */

  {
    int page_height;

    /* Get the printable page height */

    convert_to_os(tmarg - bmarg, &page_height);

    estimated_pages = (reformat_return_extent(b, NULL) / page_height) + 1;
  }

  globaljob = job;

  /* Stop the C library intercepting Escape, since */
  /* this should be left to the print SWIs.        */

  old_sigint_handler = signal(SIGINT, SIG_IGN);

  /* Start up the printing system */

  e = _swix(PDriver_SelectJob,
            _INR(0,1) | _OUT(0),

            job,
            lookup_token("PJobName:Web page",0,0),

            &old_job);

  if (e) goto out1;

  globalold_job = old_job;

  /* Declare fonts that have been used */

  if (features & Browser_Printer_DeclareFont)
  {
    fm_face h;

    /* If using system font, only the system faces will be */
    /* used; otherwise, need to declare the sans, serif    */
    /* and fixed faces.                                    */

    if (!choices.system_font)
    {
      h = fm_find_font(NULL, "sans",   192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "sans",   192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "sans",   192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "sans",   192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);

      h = fm_find_font(NULL, "serif",  192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "serif",  192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "serif",  192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "serif",  192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);

      h = fm_find_font(NULL, "fixed",  192,192,0,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "fixed",  192,192,1,0); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "fixed",  192,192,0,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
      h = fm_find_font(NULL, "fixed",  192,192,1,1); _swix(PDriver_DeclareFont,_INR(0,2),h,0,2); fm_lose_font(NULL, h);
    }

    /* Finish declaring fonts */

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

              0,
              0,
              2);

    if (e) goto out2;
  }

  /* Set the bottom left hand corner of the rectangle to redraw */

  box.xmin = orientation ? lmarg : bmarg;
  box.ymin = orientation ? bmarg : rmarg;

  /* Set 'top' to the offset from the top of the document to get  */
  /* to the top of the currently visible portion, and 'bottom'    */
  /* to the offset to get to the bottom of the currently visible  */
  /* portion. The positive direction is downwards (so they should */
  /* both be positive numbers).                                   */

  {
    WimpGetWindowStateBlock state;
    int                     htop, hbot;

    state.window_handle = b->window_handle;
    e = wimp_get_window_state(&state);
    if (e) goto out2;

    /* Get the basic offsets */

    top    = -state.yscroll;
    bottom = state.visible_area.ymax - state.visible_area.ymin;

    /* Correct for toolbar presence */

    if (!controls.swap_bars)
    {
      htop = toolbars_button_height(b) + toolbars_url_height(b);
      hbot = toolbars_status_height(b);
    }
    else
    {
      htop = toolbars_status_height(b);
      hbot = toolbars_button_height(b) + toolbars_url_height(b);
    }

    if (htop) htop += wimpt_dy();
    if (hbot) hbot += wimpt_dy();

    top    += htop;
    bottom -= (htop + hbot - top);

    /* If 'from' is non-zero, want to print from the top of the whole   */
    /* page; else from the top of the visible area as worked out above. */

    if (from) top = htop;
  }

  redraw.yscroll = -top;
  redraw.xscroll = 0;

  /* Loop round for all pages. The y scroll position is set */
  /* initially to be correct for the place we want to print */
  /* from, and xscroll is set to 0 to mark that no calls    */
  /* to redraw_draw have happened yet.                      */

  page = 0;

  while (
          (
            end == End_Many &&
            page < to
          )
          ||
          (
            end != End_Many
          )
        )
  {
    page ++;

    /* Set up the Redraw block ready for the calls to redraw_draw */

    redraw.visible_area.xmax = rmarg - lmarg;
    redraw.visible_area.xmin = 0;

    convert_to_os(redraw.visible_area.xmax, &redraw.visible_area.xmax);

    /* Vertical margins are complicated by the user settings. For printing    */
    /* down to the bottom of the web page, want to use a full page rectangle; */
    /* for printing down to the bottom of the visible area, want to use the   */
    /* 'bottom' variable worked out above. Note the checking to work out      */
    /* pagination - it *could* be possible that the visible area is taller    */
    /* than a single sheet of paper for the current printer.                  */

    convert_to_os(tmarg - bmarg, &temp); /* 'temp' now holds the printable page height in OS units */

    if (end != End_Visible)
    {
      redraw.visible_area.ymax = temp,
      redraw.visible_area.ymin = 0;
    }
    else
    {
      /* Need to subtract an amount from 'bottom' to mark that a page has been  */
      /* done, but must do that after the page rendering so the redraw routines */
      /* have filled in redraw.xscroll with the last coordinate used - if we    */
      /* don't do this, we can't tell how much of the web page was actually     */
      /* used. Remember that xscroll and yscroll are negative; xscroll refers   */
      /* to the vertical offset to start the next page at, and yscroll was the  */
      /* offset that this one started at.                                       */

      if (redraw.xscroll && bottom) bottom -= (redraw.yscroll - redraw.xscroll);

      if (bottom - top > temp) /* And *not* '>= temp'! See 'else' code below */
      {
        redraw.visible_area.ymax = temp;
      }
      else
      {
        redraw.visible_area.ymax = bottom - top;
        bottom = 0;

        /* Since this rectangle is at most a full page in height but probably */
        /* less, must shift the bottom left hand coordinate of it up an       */
        /* appropriate amount to print the page fragment at the top of the    */
        /* paper rather than the bottom.                                      */

        temp -= redraw.visible_area.ymax; /* (Page height minus rectangle height in OS units) */

        convert_to_points(temp, &temp);

        if (orientation) box.ymin += temp;
        else             box.xmin += temp;

        /* Signal to redraw_draw that the last line on this page may be split */
        /* over the bottom of the page, as that's what the user is seeing in  */
        /* the real window right now.                                         */

        printing = 2;
      }

      redraw.visible_area.ymin = 0;
    }

    /* redraw_draw will give the y coordinate of the next line to */
    /* print when it exits by placing an appropriate coordinate   */
    /* in the xscroll field of the redraw block that is passed to */
    /* it during the main printer redraw loop.                    */

    if (redraw.xscroll) redraw.yscroll = redraw.xscroll, redraw.xscroll = 0;

    /* Start drawing things */

    e = _swix(PDriver_GiveRectangle,
              _INR(0,4),

              0, /* Rectangle ID word - only 1 per page, so 0 will do */
              &redraw.visible_area,
              orientation ? &portrait : &landscape,
              &box,
              Redraw_Colour_White);

    if (e) goto out2;

    e = _swix(PDriver_DrawPage,
              _INR(0,3) | _OUT(0),

              (copies) | ((features & Browser_Printer_PreScansRectangles) ? (1<<24) : (0)),
              &redraw.redraw_area,
              0,
              0,

              &more);

    if (e) goto out2;

    /* Give an indication of progress */

    {
      int percent;

      percent = (100 * (page - 1)) / estimated_pages;

      if (percent > 99) percent = 99;

      _swix(Hourglass_Percentage, _IN(0), percent);
    }

    /* The redraw loop itself. The area stuff is for the */
    /* in-page hourglass percentage; see later comments. */

    page_area = (redraw.visible_area.xmax - redraw.visible_area.xmin) *
                (redraw.visible_area.ymax - redraw.visible_area.ymin);

    last_rect.xmin = last_rect.xmax = last_rect.ymin = last_rect.ymax = 0;

    next_line = area_completed = 0;

    while (more)
    {
      /* Ensure images are correct for the current mode */

      image_mode_change();

      /* Do the redraw */

      e = redraw_draw(b,
                      &redraw,
                      0,
                      0);

      if (e) goto out2;

      /* Don't want to start pagination issues if this is just a prescan */

      if ((features & Browser_Printer_PreScansRectangles) && (more & 1<<24))
      {
        redraw.xscroll = 0;
      }
      else
      {
        int    this_area;
        BBox * i;

        if (redraw.xscroll != 0) next_line = redraw.xscroll, redraw.xscroll = 0;

        /* Give a percentage completed indicator. This is first based */
        /* on the current page being printed, so there's some scaling */
        /* of the 100% range to cope with the fact that if you're on  */
        /* page 3 of 4, the variation must be between 50% and 75%,    */
        /* for example. Since rectangle order cannot be relied upon,  */
        /* need to use the area printed so far for the calculation.   */
        /* This may fail under unusual circumstances, and certainly   */
        /* is not fully accurate as the rectangles always overlap by  */
        /* a small amount (the printer driver gives room for rounding */
        /* errors by overlapping the rectangles) but in any case      */
        /* there will at least be some kind of percentage indication! */
        /* With bit image printing, which can be painfully slow, this */
        /* is extremely important.                                    */
        /*                                                            */
        /* There is an attempt to correct for overlapping rectangles, */
        /* if this one and the last were overlapping.                 */

        i = intersection(&redraw.redraw_area, &last_rect);

        if (i)
        {
          this_area = (redraw.redraw_area.xmax - redraw.redraw_area.xmin) *
                      (redraw.redraw_area.ymax - redraw.redraw_area.ymin)
                    - (i->xmax                 - i->xmin) *
                      (i->ymax                 - i->ymin);
        }
        else
        {
          this_area = (redraw.redraw_area.xmax - redraw.redraw_area.xmin) *
                      (redraw.redraw_area.ymax - redraw.redraw_area.ymin);
        }

        last_rect = redraw.redraw_area;

        area_completed += this_area;
        if (area_completed > page_area) area_completed = page_area;

        {
          int percent;

          percent = (100 * (page - 1)) / estimated_pages +
                    ((100 / estimated_pages) * area_completed) / page_area;

          if (percent < 0)  percent = 0;
          if (percent > 99) percent = 99;

          _swix(Hourglass_Percentage, _IN(0), percent);
        }
      }

      /* Get the next rectangle */

      e = _swix(PDriver_GetRectangle,
                _IN(1) | _OUT(0),

                &redraw.redraw_area,
                &more);

      if (e) goto out2;
    }

    if (next_line) redraw.xscroll = next_line;

    /* If we should print down to the bottom of the visible */
    /* area of the page, this is flagged with 'end' set to  */
    /* End_Visible; if 'bottom' is zero as well, there's    */
    /* nothing more to print.                               */

    if (end == End_Visible && !bottom) break;

    /* If xscroll is 0, redraw_draw must not have found any */
    /* lines that fell off the bottom of the page - so      */
    /* there cannot be any more pages.                      */

    if (!redraw.xscroll) break;

    /* Otherwise, close the outer while loop - which */
    /* may mean we loop for another page.            */
  }

  /* Finished, so end the job, close the output stream, */
  /* turn off the hourglass and restore the previous    */
  /* job.                                               */

  e = _swix(PDriver_EndJob,
            _IN(0),

            job);

  if (e) goto out2;

  /* Ensure images are restored to the correct mode */

  image_mode_change();

  /* Restore the old Escape handler */

  signal(SIGINT, old_sigint_handler);

  /* Close the output stream */

  globaljob = 0;

  e = _swix(OS_Find,
            _INR(0,1),

            0x00,
            job);

  if (e) goto out3;

  /* Remove the hourglass percentage indicator */

  _swix(Hourglass_Percentage,
        _IN(0),

        100);

  /* Now we have to put the page back where it was... */

  if (must_restore)
  {
    int leds = 1;

    print_restore_browser(b, &localbrowser);

    /* As before, sit around whilst the reformatter reformats. */

    e = reformat_format_from(b, -1, 1, -1);
    if (e) return e;

    while (reformat_formatting(b))
    {
      reformat_reformatter(b);

      leds ^= 3;

      _swix(Hourglass_LEDs,
            _INR(0,1),

            3,
            leds);
    }

    /* We don't need to restore anything now */

    must_restore = 0;
  }

  /* Turn off the hourglass */

  _swix(Hourglass_Off, 0);

  if (must_restore)
  {
    print_restore_browser(b, &localbrowser);
    reformat_format_from(b, -1, 1, -1);
  }

  /* Restore the previous print job */

  globalold_job = 0;

  e = _swix(PDriver_SelectJob,
            _INR(0,1),

            old_job,
            0);

  return e;

out1: /* 'Emergency exit' if PDriver_SelectJob fails */

  signal(SIGINT, old_sigint_handler);
  globaljob = 0;

  /* Close the output stream */

  _swix(OS_Find,
        _INR(0,1),

        0x00,
        job);

  /* Flag that printing has finished */

  printing = 0;

  /* Force the hourglass off */

  _swix(Hourglass_Smash, 0);

  /* Put the browser back together again */

  if (must_restore)
  {
    print_restore_browser(b, &localbrowser);
    reformat_format_from(b, -1, 1, -1);
  }

  /* Ensure images are still OK */

  image_mode_change();

  return e;

out2: /* 'Emergency exit' for errors whilst printing */

  signal(SIGINT, old_sigint_handler);

  print_abort_print();

  printing = 0;

  if (must_restore)
  {
    print_restore_browser(b, &localbrowser);
    reformat_format_from(b, -1, 1, -1);
  }

  image_mode_change();

  return e;

out3: /* 'Emergency exit' for errors after printing */

  _swix(Hourglass_Smash, 0);

  printing = 0;

  if (must_restore)
  {
    print_restore_browser(b, &localbrowser);
    reformat_format_from(b, -1, 1, -1);
  }

  image_mode_change();

  return e;
}

/*************************************************/
/* print_abort_print()                           */
/*                                               */
/* Forcibly aborts a print job.                  */
/*************************************************/

void print_abort_print(void)
{
  /* Abort the current print job */

  _swix(PDriver_AbortJob,
        _IN(0),

        globaljob);

  /* Close the output stream */

  _swix(OS_Find,
        _INR(0,1),

        0x00,
        globaljob);

  globaljob = 0;

  /* Restore the previous print job */

  _swix(PDriver_SelectJob,
        _INR(0,1),

        globalold_job,
        0);

  globalold_job = 0;

  /* Force the hourglass off */

  _swix(Hourglass_Smash, 0);
}

/*************************************************/
/* print_prepare_browser()                       */
/*                                               */
/* Prepares a browser for internal reformatting  */
/* prior to printing a page, storing various     */
/* overwritten values into an alternative        */
/* given structure.                              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the page to print;    */
/*                                               */
/*             Pointer to the browser_data       */
/*             struct to copy into;              */
/*                                               */
/*             Left hand page margin, in         */
/*             millipoints;                      */
/*                                               */
/*             Right hand page margin, in        */
/*             millipoints.                      */
/*************************************************/

static void print_prepare_browser(browser_data * source, browser_data * store, int lmarg, int rmarg, int tmarg, int bmarg)
{
  store->previous        = source->previous;
  store->next            = source->next;
  source->previous       = source->next = NULL;

  store->display_width   = source->display_width;
  source->display_width  = rmarg - lmarg;

  convert_to_os(source->display_width, &source->display_width);

  store->display_height  = source->display_height;
  source->display_height = bmarg - tmarg;

  convert_to_os(source->display_height, &source->display_height);

  store->display_extent  = source->display_extent;
  source->display_extent = source->display_width;

  store->fetch_status    = source->fetch_status;
  source->fetch_status   = BS_IDLE;
  store->fetch_handle    = source->fetch_handle;
  source->fetch_handle   = source->display_handle;
}

/*************************************************/
/* print_restore_browser()                       */
/*                                               */
/* Puts back the bits and pieces replaced in the */
/* browser_data struct given to the print        */
/* routines, copied out because a reformat was   */
/* required on the page.                         */
/*                                               */
/* Parameters: Pointer to the browser_data       */
/*             struct given to the print         */
/*             routines;                         */
/*                                               */
/*             Pointer to the browser_data       */
/*             struct used to store the over-    */
/*             written values from the original  */
/*             copy in print_prepare_browser.    */
/*************************************************/

static void print_restore_browser(browser_data * original, browser_data * copy)
{
  original->previous       = copy->previous;
  original->next           = copy->next;
  original->display_width  = copy->display_width;
  original->display_extent = copy->display_extent;
  original->display_height = copy->display_height;
  original->fetch_status   = copy->fetch_status;
  original->fetch_handle   = copy->fetch_handle;
}

/*************************************************/
/* print_return_dialogue_info()                  */
/*                                               */
/* Returns information on the Print dialogue,    */
/* and its ancestor.                             */
/*                                               */
/* Parameters: Pointer to an ObjectId, in which  */
/*             the ID of the PrintDBox object    */
/*             is placed;                        */
/*                                               */
/*             Pointer to an ObjectId, in which  */
/*             the ID of the underlying window   */
/*             object is placed;                 */
/*                                               */
/*             Pointer to an ObjectId, in which  */
/*             the ID of the ancestor window is  */
/*             placed;                           */
/*                                               */
/*             Pointer to a pointer to a         */
/*             browser_data struct, in which the */
/*             address of the browser_data       */
/*             struct associated with the        */
/*             ancestor object is placed.        */
/*                                               */
/* Returns:    See parameters list, and note     */
/*             that the returned values will be  */
/*             0, 0, 0 and NULL if the Print     */
/*             dialogue is closed.               */
/*                                               */
/* Assumes:    Any of the pointers may be NULL.  */
/*************************************************/

void print_return_dialogue_info(ObjectId * self, ObjectId * window, ObjectId * ancestor, browser_data ** ancestor_b)
{
  if (self)       *self       = self_id;
  if (window)     *window     = window_id;
  if (ancestor)   *ancestor   = ancestor_id;
  if (ancestor_b) *ancestor_b = ancestor_browser;
}