/*
  ArtToSpr Artworks/Draw to Sprite convertor
  Copyright (c) 1998 Tony Houghton

  This source is distributed under the GPL. Please see the file
  "COPYING" for details.
*/

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

#include "colourtran.h"
#include "hourglass.h"
#include "osfile.h"

#include "flex.h"

#include "dcs.h"
#include "event.h"
#include "fileinfo.h"
#include "gadgets.h"
#include "menu.h"
#include "saveas.h"
#include "scale.h"
#include "wimplib.h"
#include "window.h"

#include "CRstring.h"
#include "err.h"
#include "msgtrans.h"
#include "picwin.h"
#include "rescodes.h"

#define SNAME "arttospr"
#define SPRID ((osspriteop_id) SNAME)
#define MNAME "maskwk"
#define MSKID ((osspriteop_id) MNAME)

#define PI 3.1415926535897931
#define DEGTORAD(d) ((d) * PI / 180.0)

static ObjectId dcs_id;
static bool dcs_ignore_complete;

static linklist_header picwin_list = { 0, 0 };

static bool picwin_generate_factors(picwin *pic)
{
  os_mode smode;
  int log2bpp;
  os_PALETTE(20) palblk;
  os_palette *pal = (os_palette *) -1;
  int xeig, yeig;
  int size;

  pic->scale.xmul = pic->scale.ymul = pic->zoom;
  pic->scale.xdiv = pic->scale.ydiv = 100;
  FE(xos_read_mode_variable((os_mode) -1, 4, &xeig, 0));
  FE(xos_read_mode_variable((os_mode) -1, 5, &yeig, 0));
  if (xeig > pic->xeig)		/* Screen is lower res than sprite */
    pic->scale.xdiv <<= xeig - pic->xeig;
  else
    pic->scale.xmul <<= pic->xeig - xeig;
  if (yeig > pic->yeig)		/* Screen is lower res than sprite */
    pic->scale.ydiv <<= yeig - pic->yeig;
  else
    pic->scale.ymul <<= pic->yeig - yeig;
  if (FE(xosspriteop_read_sprite_size(256, pic->sparea, SPRID,
  	0, 0, 0, &smode)))
    return false;
  FE(xos_read_mode_variable(smode, 9, &log2bpp, 0));
  if (log2bpp < 3)
  {
    FE(wimp_read_palette((Palette *) &palblk));
    pal = (os_palette *) &palblk;
  }
  free(pic->pixtrans);
  pic->pixtrans = 0;
  if (E(xcolourtrans_generate_table(smode, pal,
  	(os_mode) -1, (const os_palette *) -1, 0, 0,
  	0, 0, &size)))
    return false;
  pic->pixtrans = malloc(size);
  if (!pic->pixtrans)
  {
  #ifndef NDEBUG
    FE(msgs_nomem());
  #endif
    return false;
  }
  if (E(xcolourtrans_select_table(smode, pal,
  	(os_mode) -1, (const os_palette *) -1, pic->pixtrans)))
  {
    free(pic->pixtrans);
    pic->pixtrans = 0;
    return false;
  }
  return true;
}

static bool picwin_redraw(int c, WimpPollBlock *e, IdBlock *id, picwin *pic)
{
  bool more;
  WimpRedrawWindowBlock redraw;
  int x0, y0;
  BBox extent;

  c=c;

  redraw.window_handle = e->redraw_window_request.window_handle;
  FE(wimp_redraw_window(&redraw, &more));
  x0 = redraw.visible_area.xmin - redraw.xscroll;
  y0 = redraw.visible_area.ymax - redraw.yscroll;
  FE(window_get_extent(0, id->self_id, &extent));
  y0 += extent.ymin;
  while (more)
  {
    FE(xosspriteop_put_sprite_scaled(256, pic->sparea, SPRID,
    	x0, y0, 0, &pic->scale, pic->pixtrans));
    FE(wimp_get_rectangle(&redraw, &more));
  }
  return true;
}

static void picwin_destroy(picwin *, bool gui);

static bool picwin_close_handler(int c, WimpPollBlock *e, IdBlock *id,
	picwin *pic)
{
  c=c; e=e;

  if (pic->unsaved)
  {
    E(toolbox_show_object(Toolbox_ShowObject_AsMenu, dcs_id,
    	Toolbox_ShowObject_Default, 0, id->self_id, id->self_component));
    pic->save_discard = true;
    dcs_ignore_complete = false;
  }
  else
    picwin_destroy(pic, true);
  return true;
}

static void picwin_destroy(picwin *pic, bool gui)
{
  if (!pic)
    return;
  if (gui)
  {
    LinkList_Unlink(&picwin_list, &pic->node);
    event_deregister_wimp_handler(pic->mainwin, Wimp_ERedrawWindow,
    	(WimpEventHandler *) picwin_redraw, pic);
    event_deregister_wimp_handler(pic->mainwin, Wimp_ECloseWindow,
    	(WimpEventHandler *) picwin_close_handler, pic);
    opts_release(&pic->opts, pic->optswin);
    E(toolbox_delete_object(0, pic->mainwin));
  }
  free(pic->pixtrans);
  free(pic->filename);
  if (pic->sparea)
    flex_free((flex_ptr) &pic->sparea);
  free(pic);
}

static bool picwin_scale(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  picwin *pic;
  BBox extent;

  c=c; h=h;

  if (E(toolbox_get_client_handle(0, id->ancestor_id, (void **) &pic)))
    return false;
  pic->zoom = e->data.words[0];
  picwin_generate_factors(pic);
  extent.xmin = 0;
  extent.xmax = pic->width * pic->zoom / 100;
  extent.ymin = -pic->height * pic->zoom / 100;;
  extent.ymax = 0;
  E(window_set_extent(0, id->ancestor_id, &extent));
  /*E(toolbox_show_object(0, id->ancestor_id, Toolbox_ShowObject_Default, 0,
  	NULL_ObjectId, NULL_ComponentId));*/
  E(window_force_redraw(0, id->ancestor_id, &extent));
  return false;
}

static bool picwin_showsave(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  picwin *pic;

  e=e; h=h;

  if (E(toolbox_get_client_handle(0, id->ancestor_id, (void **) &pic)))
    return false;
  c = flex_size((flex_ptr) &pic->sparea) - sizeof(int);
  E(saveas_set_file_size(0, id->self_id, c));
  E(saveas_set_data_address(0, id->self_id,
  	(char *) pic->sparea + sizeof(int), c,
  	(char *) pic->sparea + sizeof(int), c));
  return true;
}

static bool picwin_save(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  picwin *pic;

  c=c; h=h;

  if (E(toolbox_get_client_handle(0, id->ancestor_id, (void **) &pic)))
    return false;
  if (e->hdr.flags & SaveAs_DestinationSafe)
  {
    if (pic->save_discard)
      picwin_destroy(pic, true);
    else
    {
      char title[64];
      char *leaf;

      leaf = strrchr(pic->filename, '.');
      if (!leaf++)
      {
        leaf = strrchr(pic->filename, ':');
        if (!leaf++)
          leaf = pic->filename;
      }
      CRstrcpy(title, msgs_qlookup("DispTitle"));
      strcat(title, leaf);
      E(window_set_title(0, pic->mainwin, title));
      pic->unsaved = false;
    }
  }
  return true;
}

static bool picwin_mode_change(WimpMessage *m, void *h)
{
  picwin *pic;

  m=m; h=h;

  for (pic = LinkList_FirstItem(&picwin_list); pic;
  	pic = LinkList_NextItem(pic))
  {
    picwin_generate_factors(pic);
    opts_set_current_mode(&pic->opts);
    opts_update_mode(&pic->opts, pic->optswin);
  }
  return false;
}

static bool picwin_discard(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  picwin *pic;

  c=c; e=e; h=h;

  dcs_ignore_complete = true;
  if (E(toolbox_get_client_handle(0, id->ancestor_id, (void **) &pic)))
    return false;
  picwin_destroy(pic, true);
  return true;
}

static bool picwin_dcs_save(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  ObjectId atchd;

  c=c; e=e; h=h;

  dcs_ignore_complete = true;
  if (E(window_get_menu(0, id->ancestor_id, &atchd)))
    return false;
  if (menu_get_sub_menu_show(0, atchd, displaymenu_SAVE, &atchd))
    return false;
  E(toolbox_show_object(Toolbox_ShowObject_AsMenu, atchd,
  	Toolbox_ShowObject_Default, 0,
  	id->ancestor_id, id->ancestor_component));
  return true;
}

static bool picwin_dcs_cancel(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  picwin *pic;

  c=c; e=e; h=h;

  if (!dcs_ignore_complete)
  {
    if (E(toolbox_get_client_handle(0, id->ancestor_id, (void **) &pic)))
      return false;
    pic->save_discard = false;
  }
  return true;
}

static bool picwin_make_sprite(picwin *pic)
{
  picture_matrix trfm;
  BBox extent;
  int xpix, ypix;
  os_mode smode = 0;
  int size, msize = 0;
  int context0, context1, context2, context3;
  os_error *e;
  bool pal = false;
  int x, y;

/*fprintf(stderr, "Angle = %f degrees\n", pic->opts.angle);*/
  trfm.d = trfm.a = (int) (cos(DEGTORAD(pic->opts.angle)) * 65536.0);
  trfm.c = - (trfm.b = (int) (sin(DEGTORAD(pic->opts.angle)) * 65536.0));
  trfm.e = trfm.f = 0;
/*fprintf(stderr, "Matrix with angle only = %d %d %d %d\n",
trfm.a, trfm.b, trfm.c, trfm.d);*/
  if (pic->opts.scale_to_fit)
  {
    extent.xmax = pic->opts.scale_fit_x;
    extent.ymax = pic->opts.scale_fit_y;
    if (!picture_scale_to_fit(&pic->pic, &trfm, pic->opts.margin,
    	&extent.xmax, &extent.ymax,
    	pic->opts.keep_aspect, pic->opts.aspect))
      return false;
  }
  else
  {
/*fprintf(stderr, "Scale is %f %d\n", pic->opts.scale_x, pic->opts.scale_y);*/
    trfm.a = (int) ((double) trfm.a * pic->opts.scale_x);
    trfm.b = (int) ((double) trfm.b * pic->opts.scale_y);
    trfm.c = (int) ((double) trfm.c * pic->opts.scale_x);
    trfm.d = (int) ((double) trfm.d * pic->opts.scale_y);
/*fprintf(stderr, "Matrix with scale = %d %d %d %d\n",
trfm.a, trfm.b, trfm.c, trfm.d);*/
    if (!picture_sprite_size(&pic->pic, &trfm, pic->opts.margin,
    	&extent.xmax, &extent.ymax))
      return false;
  }
  extent.xmin = extent.ymin = 0;
/*fprintf(stderr, "Dimensions are %d %d\n", extent.xmax, extent.ymax);*/

  /* Make sprite */
  switch (pic->opts.resolution)
  {
    case dpi_22x22:
      pic->xeig = pic->yeig = 3;
      smode = (os_mode) (1 + (22<<1) + (22<<14) +
      	(pic->opts.colours + 1 << 27));
      break;
    case dpi_45x45:
      pic->xeig = pic->yeig = 2;
      smode = (os_mode) opts_calculate_mode(pic->opts.colours, dpi_45x45);
      if (smode == (os_mode) -1)
        smode = (os_mode) (1 + (45<<1) + (45<<14) +
        	(pic->opts.colours + 1 << 27));
      break;
    case dpi_90x45:
      pic->xeig = 1;
      pic->yeig = 2;
      smode = (os_mode) opts_calculate_mode(pic->opts.colours, dpi_90x45);
      if (smode == (os_mode) -1)
        smode = (os_mode) (1 + (90<<1) + (45<<14) +
        	(pic->opts.colours + 1 << 27));
      break;
    case dpi_90x90:
      pic->xeig = pic->yeig = 1;
      smode = (os_mode) opts_calculate_mode(pic->opts.colours, dpi_90x90);
      if (smode == (os_mode) -1)
        smode = (os_mode) (1 + (90<<1) + (90<<14) +
        	(pic->opts.colours + 1 << 27));
      break;
    case dpi_180x180:
      pic->xeig = pic->yeig = 0;
      smode = (os_mode) (1 + (180<<1) + (180<<14) +
        	(pic->opts.colours + 1 << 27));
  }

  xpix = extent.xmax + (1<<pic->xeig) - 1 >> pic->xeig;
  ypix = extent.ymax + (1<<pic->yeig) - 1 >> pic->yeig;
  size = (((xpix << pic->opts.colours) + 31 & ~31)
  	/* Number of bits in width rounded to 32 */
  	>> 3)	/* Bytes */
  	* ypix;
  /* If masked, need room for mask, plus 2nd sprite for working out which
     pixels are masked */
  if (pic->opts.mask)
  {
    if ((int) pic->opts.colours > (int) colours_256)
    {
      size += ((xpix + 31 & ~31) >> 3) * ypix
              + (msize = size + sizeof(osspriteop_header));
    }
    else
    {
      msize = size + sizeof(osspriteop_header);
      size = size * 3 + sizeof(osspriteop_header);
    }
  }
  size += sizeof(osspriteop_header) + sizeof(osspriteop_area);
  /*
  if (pic->opts.colours < 3)
  {
    size += 2 * sizeof(int) << pic->opts.colours + 1;
    pal = true;
  }
  else if (pic->opts.colours == 4)
  {
    size += 2 * sizeof(int) << 4;
    pal = true;
  }
  */
  if (!flex_alloc((flex_ptr) &pic->sparea, size))
  {
    pic->sparea = 0;
    E(msgs_nomem());
    return false;
  }
  pic->sparea->size = size;
  pic->sparea->first = 16;
  if (E(xosspriteop_clear_sprites(256, pic->sparea)))
  {
    flex_free((flex_ptr) &pic->sparea);
    return false;
  }
  if (E(xosspriteop_create_sprite(256, pic->sparea, SNAME, pal,
  	xpix, ypix, smode)))
  {
    flex_free((flex_ptr) &pic->sparea);
    return false;
  }
  if (pic->opts.mask && E(xosspriteop_create_mask(256, pic->sparea, SPRID)))
  {
    flex_free((flex_ptr) &pic->sparea);
    return false;
  }
  if (pic->opts.mask &&
      E(xosspriteop_create_sprite(256, pic->sparea, MNAME, pal,
  	xpix, ypix, smode)))
  {
    flex_free((flex_ptr) &pic->sparea);
    return false;
  }
  if (E(xosspriteop_switch_output_to_sprite(256,
  	pic->sparea, SPRID, 0,
  	&context0, &context1, &context2, &context3)))
  {
    flex_free((flex_ptr) &pic->sparea);
    return false;
  }
  FE(xcolourtrans_set_gcol((os_colour) pic->opts.bg_colour,
                           (1<<7) + (1<<8) * !pic->opts.mask, 0, 0, 0));
  	/* Set background colour (overwrite, use ECF if not masked) */
  xos_clg();
  e = pic->pic.render(&pic->pic, &trfm, &extent);
  FE(xosspriteop_unswitch_output(context0, context1, context2, context3));
  if (E(e))
  {
    flex_free((flex_ptr) &pic->sparea);
    return false;
  }
  if (pic->opts.mask)
  {
    /* Generate 2nd image on opposite background to work out which
       pixels should be masked. This is quite tricky thanks to Acorn's
       farcical colour handling */
    unsigned long int bg1 = 0, bg2 = 0;
    os_tint tint1 = 0, tint2 = 0;
    const char *s1data, *s2data;

    xhourglass_leds(2, 2, 0);
    bg2 = 0xff00 - (pic->opts.bg_colour & 0xff00)
        + 0xff0000 - (pic->opts.bg_colour & 0xff0000)
        + 0xff000000 - (pic->opts.bg_colour & 0xff000000);
    if (E(xosspriteop_switch_output_to_sprite(256,
    	pic->sparea, MSKID, 0,
    	&context0, &context1, &context2, &context3)))
    {
      flex_free((flex_ptr) &pic->sparea);
      return false;
    }
    FE(xcolourtrans_set_gcol((os_colour) bg2,
                             (1<<7), 0, 0, 0));
    	/* Set background colour (overwrite) */
    xos_clg();
    e = pic->pic.render(&pic->pic, &trfm, &extent);
    FE(xosspriteop_unswitch_output(context0, context1, context2, context3));
    if (E(e))
    {
      flex_free((flex_ptr) &pic->sparea);
      return false;
    }
    xhourglass_leds(2, 2, 0);
    /* Convert bg1 and bg2 into values appropriate for mode then compare
       pixel by pixel to generate mask */
    switch (pic->opts.colours)
    {
      case colours_2:
      case colours_4:
      case colours_16:
        xcolourtrans_return_gcol((os_colour) pic->opts.bg_colour,
                                 (os_gcol *) &bg1);
        xcolourtrans_return_gcol((os_colour) bg2,
                                 (os_gcol *) &bg2);
        bg1 &= 0xff;
        bg2 &= 0xff;
        for (y = 0; y < ypix; ++y)
        {
          for (x = 0; x < xpix; ++x)
          {
            os_gcol pix1, pix2;
            os_tint pixt1, pixt2;

            xosspriteop_read_pixel_colour(256, pic->sparea, SPRID, x, y,
                                          &pix1, &pixt1);
            xosspriteop_read_pixel_colour(256, pic->sparea, MSKID, x, y,
                                          &pix2, &pixt2);
            xosspriteop_write_pixel_mask(256, pic->sparea, SPRID, x, y,
                        pix1 != (os_gcol) bg1 || pix2 != (os_gcol) bg2);
          }
          xhourglass_percentage((y * 100) / ypix);
        }
        break;
      case colours_256:
        xcolourtrans_return_gcol((os_colour) pic->opts.bg_colour,
                                 (os_gcol *) &bg1);
        xcolourtrans_return_gcol((os_colour) bg2,
                                 (os_gcol *) &bg2);
        bg1 &= 0xff;
        bg2 &= 0xff;
        /* Convert this into colour and tint as used by osspriteop_read_pixel */
        tint1 = (os_tint) ((bg1 & 3) * 64);
        bg1 >>= 2;
        tint2 = (os_tint) ((bg2 & 3) * 64);
        bg2 >>= 2;
        for (y = 0; y < ypix; ++y)
        {
          for (x = 0; x < xpix; ++x)
          {
            os_gcol pix1, pix2;
            os_tint pixt1, pixt2;

            xosspriteop_read_pixel_colour(256, pic->sparea, SPRID, x, y,
                                          &pix1, &pixt1);
            xosspriteop_read_pixel_colour(256, pic->sparea, MSKID, x, y,
                                          &pix2, &pixt2);
            xosspriteop_write_pixel_mask(256, pic->sparea, SPRID, x, y,
                        pix1 != (os_gcol) bg1 || pix2 != (os_gcol) bg2 ||
                        pixt1 != tint1 || pixt2 != tint2);
          }
          xhourglass_percentage((y * 100) / ypix);
        }
        break;
      case colours_32K:
        bg1 = pic->opts.bg_colour;
        bg1 = ((bg1 >> 11) & 0x1f)
            + ((bg1 >> 14) & (0x1f << 5))
            + ((bg1 >> 17) & (0x1f << 10));
        bg2 = ((bg2 >> 11) & 0x1f)
            + ((bg2 >> 14) & (0x1f << 5))
            + ((bg2 >> 17) & (0x1f << 10));
        xosspriteop_select_sprite(256, pic->sparea, SPRID,
                    (osspriteop_header **) &s1data);
        s1data += ((osspriteop_header *) s1data)->image;
        xosspriteop_select_sprite(256, pic->sparea, MSKID,
                    (osspriteop_header **) &s2data);
        s2data += ((osspriteop_header *) s2data)->image;
        for (y = 0; y < ypix; ++y)
        {
          int yoffset;

          yoffset = ((xpix * 2 + 3) & ~3) * (ypix - 1 - y);
          for (x = 0; x < xpix; ++x)
          {
            unsigned long int pix1, pix2;
            int pixoffset;

            pixoffset = yoffset + 2 * x;
            pix1 = (unsigned long int) s1data[pixoffset] +
                   ((unsigned long int) s1data[pixoffset + 1] << 8);
            pix2 = (unsigned long int) s2data[pixoffset] +
                   ((unsigned long int) s2data[pixoffset + 1] << 8);
            xosspriteop_write_pixel_mask(256, pic->sparea, SPRID, x, y,
                        pix1 != bg1 || pix2 != bg2);
          }
          xhourglass_percentage((y * 100) / ypix);
        }
        break;
      case colours_16M:
        bg1 = pic->opts.bg_colour >> 8;
        bg2 >>= 8;
        xosspriteop_select_sprite(256, pic->sparea, SPRID,
                    (osspriteop_header **) &s1data);
        s1data += ((osspriteop_header *) s1data)->image;
        xosspriteop_select_sprite(256, pic->sparea, MSKID,
                    (osspriteop_header **) &s2data);
        s2data += ((osspriteop_header *) s2data)->image;
        for (y = 0; y < ypix; ++y)
        {
          int yoffset;

          yoffset = xpix * 4 * (ypix - 1 - y);
          for (x = 0; x < xpix; ++x)
          {
            unsigned long int pix1, pix2;
            int pixoffset;

            pixoffset = yoffset + 4 * x;
            pix1 = *((unsigned long int *) (s1data + pixoffset));
            pix2 = *((unsigned long int *) (s2data + pixoffset));
            xosspriteop_write_pixel_mask(256, pic->sparea, SPRID, x, y,
                        pix1 != bg1 || pix2 != bg2);
          }
          xhourglass_percentage((y * 100) / ypix);
        }
        break;
    }
    if (!E(xosspriteop_delete_sprite(256, pic->sparea, MSKID)))
    {
      pic->sparea->size -= msize;
      flex_extend((flex_ptr) &pic->sparea,
                  flex_size((flex_ptr) &pic->sparea) - msize);
    }
  }

  /* Make window right size */
  pic->width = extent.xmax;
  pic->height = extent.ymax;
  extent.ymin = - extent.ymax;
  extent.ymax = 0;
  if (err_is_task)
    E(window_set_extent(0, pic->mainwin, &extent));
  return true;
}

static bool check_colour_depth(int opts_colours)
{
  int log2bpp;

  E(xos_read_mode_variable((os_mode) -1, 9, &log2bpp, 0));
/*fprintf(stderr, "Current colour depth = %d\n", log2bpp);
fprintf(stderr, "Output colour depth = %d\n", opts_colours);*/
  if (log2bpp != opts_colours)
  {
    err_report(0, msgs_qlookup("Colours"));
    return false;
  }
  return true;
}

static bool picwin_reload(picwin *pic)
{
  ObjectId attached, mainmen;
  char *leaf;
  int ftype;
  int size;
  char utc[5];
  char title[64];

  xhourglass_on();

  if (!check_colour_depth(pic->opts.colours))
    return false;

  picture_free(&pic->pic);
  if (!picture_load(&pic->pic, pic->filename, &ftype, &size, utc))
    return false;

  leaf = strrchr(pic->filename, '.');
  if (!leaf++)
  {
    leaf = strrchr(pic->filename, ':');
    if (!leaf++)
      leaf = pic->filename;
  }

  if (err_is_task)
  {
    CRstrcpy(title, msgs_qlookup("DispTitle"));
    strcat(title, leaf);
    strcat(title, " *");
    E(window_set_title(0, pic->mainwin, title));
    if (!E(window_get_menu(0, pic->mainwin, &mainmen)) &&
      !E(menu_get_sub_menu_show(0, mainmen, displaymenu_SHOWSOURCE, &attached)))
    {
      E(fileinfo_set_modified(0, attached, false));
      E(fileinfo_set_file_type(0, attached, ftype));
      E(fileinfo_set_file_name(0, attached, pic->filename));
      E(fileinfo_set_file_size(0, attached, size));
      E(fileinfo_set_date(0, attached, (int *) utc));
    }
  }

  if (pic->sparea)
  {
    flex_free((flex_ptr) &pic->sparea);
    pic->sparea = 0;
  }
  if (!picwin_make_sprite(pic))
  {
    picture_free(&pic->pic);
    return false;
  }

  pic->unsaved = true;

  if (err_is_task &&
      !E(menu_get_sub_menu_show(0, mainmen, displaymenu_SPRITEINFO, &attached)))
  {
    char buffer[12];
    int mode;

    switch (pic->opts.colours)
    {
      case 0:
        strcpy(buffer, "2");
        break;
      case 1:
        strcpy(buffer, "4");
        break;
      case 2:
        strcpy(buffer, "16");
        break;
      case 3:
        strcpy(buffer, "256");
        break;
      case 4:
        strcpy(buffer, "32K");
        break;
      case 5:
        strcpy(buffer, "16M");
        break;
    }
    E(displayfield_set_value(0, attached, igadget_COLOURS, buffer));
    mode = opts_calculate_mode(pic->opts.colours, pic->opts.resolution);
    if (mode == -1)
      E(button_set_value(0, attached, igadget_MODE, ""));
    else
    {
      sprintf(buffer, "%d", mode);
      E(button_set_value(0, attached, igadget_MODE, buffer));
    }
    switch (pic->opts.resolution)
    {
      case dpi_22x22:
        strcpy(buffer, "22x22");
        break;
      case dpi_45x45:
        strcpy(buffer, "45x45");
        break;
      case dpi_90x45:
        strcpy(buffer, "90x45");
        break;
      case dpi_90x90:
        strcpy(buffer, "90x90");
        break;
      case dpi_180x180:
        strcpy(buffer, "180x180");
        break;
    }
    E(displayfield_set_value(0, attached, igadget_RESOLUTION, buffer));
    E(numberrange_set_value(0, attached, igadget_WIDTH,
    	pic->width + (1<<pic->xeig) - 1 >> pic->xeig));
    E(numberrange_set_value(0, attached, igadget_HEIGHT,
    	pic->height + (1<<pic->yeig) - 1 >> pic->yeig));
    size = flex_size((flex_ptr) &pic->sparea);
    if (size >= 10000 * 1024)
      sprintf(buffer, "%dM", size / (1024 * 1024));
    else if (size >= 10000)
      sprintf(buffer, "%dK", size / 1024);
    else
      sprintf(buffer, "%d", size);
    E(displayfield_set_value(0, attached, igadget_FILESIZE, buffer));
  }

  picture_free(&pic->pic);

  if (err_is_task)
    picwin_generate_factors(pic);

  xhourglass_off();

  return true;
}

bool picwin_load(const char *filename, const char *outfile)
{
  ObjectId mainmen;

  picwin *pic = malloc(sizeof(picwin));
  if (!pic)
  {
    E(msgs_nomem());
    return false;
  }
  pic->filename = CRstrdup(filename);
  if (!pic->filename)
  {
    E(msgs_nomem());
    goto load_fail5;
  }

  picture_init(&pic->pic);
  pic->sparea = 0;
  pic->opts.master = false;
  opts_reset(&pic->opts);

  if (!outfile)
  {
    if (E(toolbox_create_object(0, "Display", &pic->mainwin)))
      goto load_fail1;
    if (E(event_register_wimp_handler(pic->mainwin, Wimp_ERedrawWindow,
    	(WimpEventHandler *) picwin_redraw, pic)))
      goto load_fail2;
    if (E(event_register_wimp_handler(pic->mainwin, Wimp_ECloseWindow,
    	(WimpEventHandler *) picwin_close_handler, pic)))
      goto load_fail3;

    toolbox_set_client_handle(0, pic->mainwin, pic);
    if (!E(window_get_menu(0, pic->mainwin, &mainmen)))
    {
      E(menu_get_click_show(0, mainmen, displaymenu_SHOWOPTS,
                            &pic->optswin, 0));
      toolbox_set_client_handle(0, pic->optswin, &pic->opts);
    }
  }

  pic->zoom = 100;
  pic->save_discard = false;

  pic->pixtrans = 0;

  pic->pic.opts = &pic->opts;

  if (!picwin_reload(pic))
  {
    free(pic->pixtrans);
    if (outfile)
      goto load_fail5;
    else
      goto load_fail4;
  }

  if (outfile)
  {
    bool result = !E(xosfile_save_stamped(outfile, 0xff9,
                                   (byte *) pic->sparea + sizeof(int),
                                   (byte *) pic->sparea
                                   + flex_size((flex_ptr) &pic->sparea)));
    /* End address = (byte *) pic->sparea + sizeof(int)
                     + flex_size((flex_ptr) &pic->sparea) - sizeof(int);
       So sizeof(int)'s cancel each other out */
    picwin_destroy(pic, false);
    return result;
  }
  else
  {
    LinkList_AddToTail(&picwin_list, &pic->node);

    E(toolbox_show_object(0, pic->mainwin,
    	Toolbox_ShowObject_Default, 0, NULL_ObjectId, NULL_ComponentId));
  }

  return true;

load_fail4:
  event_deregister_wimp_handler(pic->mainwin, Wimp_ECloseWindow,
  	(WimpEventHandler *) picwin_close_handler, pic);
load_fail3:
  event_deregister_wimp_handler(pic->mainwin, Wimp_ERedrawWindow,
  	(WimpEventHandler *) picwin_redraw, pic);
load_fail2:
  toolbox_delete_object(0, pic->mainwin);
load_fail1:
  free(pic->filename);
load_fail5:
  free(pic);
  return false;
}

bool picwin_ok_to_quit()
{
  picwin *pic;

  for (pic = LinkList_FirstItem(&picwin_list); pic;
  	pic = LinkList_NextItem(pic))
  {
    if (pic->unsaved)
      return false;
  }
  return true;
}

static bool picwin_reload_handler(int c, ToolboxEvent *e, IdBlock *id, void *h)
{
  picwin *pic;

  c=c; e=e; h=h;

  if (E(toolbox_get_client_handle(0, id->ancestor_id, (void **) &pic)))
    return false;

  if (picwin_reload(pic))
  {
    BBox extent;

    E(toolbox_show_object(0, id->ancestor_id, Toolbox_ShowObject_Default, 0,
    	NULL_ObjectId, NULL_ComponentId));
    E(window_get_extent(0, id->ancestor_id, &extent));
    E(window_force_redraw(0, id->ancestor_id, &extent));
  }
  else if (!pic->sparea)
    picwin_destroy(pic, true);

  return true;
}

void picwin_initialise()
{
  EF(toolbox_create_object(0, "DCS", &dcs_id));
  EF(event_register_toolbox_handler(-1, Scale_ApplyFactor,
  	picwin_scale, 0));
  EF(event_register_toolbox_handler(-1, SaveAs_AboutToBeShown,
  	picwin_showsave, 0));
  EF(event_register_toolbox_handler(-1, SaveAs_SaveCompleted,
  	picwin_save, 0));
  EF(event_register_toolbox_handler(-1, DCS_Discard,
  	picwin_discard, 0));
  EF(event_register_toolbox_handler(-1, DCS_Save,
  	picwin_dcs_save, 0));
  EF(event_register_toolbox_handler(-1, DCS_DialogueCompleted,
  	picwin_dcs_cancel, 0));
  EF(event_register_toolbox_handler(-1, event_RELOAD,
  	picwin_reload_handler, 0));
  EF(event_register_message_handler(Wimp_MModeChange, picwin_mode_change, 0));
}
