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

#include "akbd.h"
#include "bbc.h"
#include "swis.h"
#include "debug.h"

#include "tboxlibs/toolbox.h"
#include "tboxlibs/window.h"
#include "tboxlibs/wimp.h"
#include "tboxlibs/flex.h"
#include "tboxlibs/wimplib.h"
#include "tboxlibs/event.h"
#include "tboxlibs/iconbar.h"
#include "tboxlibs/menu.h"
#include "tboxlibs/saveas.h"
#include "tboxlibs/TextArea.h"
#include "tboxlibs/colourdbox.h"
#include "tboxlibs/fontdbox.h"

#include "base.h"
#include "mystring.h"
#include "pinboard.h"
#include "indirected.h"
#include "iconize.h"
#include "drag.h"
#include "tinydirs.h"
#include "sticky.h"
#include "fs.h"
#include "options.h"
#include "msgtrans.h"
#include "outline.h"
#include "events.h"
#include "gadgetlist.h"
#include "filermenu.h"
#include "fileraction.h"
#include "confirm.h"
#include "doublesize.h"
#include "toggle.h"
#include "watermark.h"
#include "font.h"
#include "carousel.h"

#define SELECTALL_MAX 3

#define plot(a,b,c) _swix(OS_Plot,_INR(0,2),(a),(b),(c))

_backdrop_state backdrop_state;

pins *pinlist=NULL;

extern int filer_taskhandle;
static int default_text_style=BD_TEXT_THEME;
static int default_rubout_box=1;

int filer_base_column=-1,filer_base_row=-1;
static ObjectId menu_selectionmenu,settype_obj,menu_displaymenu;
ObjectId menu_mainmenu,saveas_win;

typedef struct {
  BBox start_location;
  BBox test_location;
  int dx,dy;
  int grid_width,grid_height;

  // used when we get to the end of a row/column
  int reset_x,reset_y;  // if >0 set this value on a row/col overflow, if <0 then use the adjustment below instead.
  // reflects the bottom left of bbox
  int adjust_x,adjust_y;

  IconizeSetting corner;
  IconizeStack stack;
} grid_iterator;

static int backdrop_selection_show(int event_code,ToolboxEvent *event,IdBlock *id,void *handle);


static void gi_init(_backdrop_state *state,grid_iterator *gi,IconizeSetting corner,IconizeStack stack)
{
  gi->corner=corner;
  gi->stack=stack;

  grid_size(state,&gi->grid_width,&gi->grid_height);

  // initial location
  switch (corner) {
    case ICONIZE_TO_TOPLEFT:
      gi->start_location.xmin=0;
      gi->start_location.xmax=gi->grid_width;
      gi->start_location.ymax=state->window_height+iconbar_height;
      gi->start_location.ymin=gi->start_location.ymax-gi->grid_height;
      break;

    case ICONIZE_TO_BOTTOMLEFT:
      gi->start_location.xmin=0;
      gi->start_location.xmax=gi->grid_width;
      gi->start_location.ymin=iconbar_height;
      gi->start_location.ymax=gi->start_location.ymin+gi->grid_height;
      break;

    case ICONIZE_TO_TOPRIGHT:
      gi->start_location.xmax=state->window_width;
      gi->start_location.xmin=gi->start_location.xmax-gi->grid_width;
      gi->start_location.ymax=state->window_height+iconbar_height;
      gi->start_location.ymin=gi->start_location.ymax-gi->grid_height;
      break;

    case ICONIZE_TO_BOTTOMRIGHT:
      gi->start_location.xmax=state->window_width;
      gi->start_location.xmin=gi->start_location.xmax-gi->grid_width;
      gi->start_location.ymin=iconbar_height;
      gi->start_location.ymax=gi->start_location.ymin+gi->grid_height;
      break;
  }

  // basic shift for iteration
  switch (corner) {
    case ICONIZE_TO_TOPLEFT:
      if (stack==ICONIZE_HORIZONTAL) {
        gi->dx=gi->grid_width;
        gi->dy=0;
        gi->reset_x=0;
        gi->reset_y=-1;
        gi->adjust_x=0;
        gi->adjust_y=-gi->grid_height;
      } else {
        gi->dx=0;
        gi->dy=-gi->grid_height;
        gi->reset_x=-1;
        gi->reset_y=gi->start_location.ymin;
        gi->adjust_x=gi->grid_width;
        gi->adjust_y=0;
      }
      break;

    case ICONIZE_TO_BOTTOMLEFT:
      if (stack==ICONIZE_HORIZONTAL) {
        gi->dx=gi->grid_width;
        gi->dy=0;
        gi->reset_x=0;
        gi->reset_y=-1;
        gi->adjust_x=0;
        gi->adjust_y=gi->grid_height;
      } else {
        gi->dx=0;
        gi->dy=gi->grid_height;
        gi->reset_x=-1;
        gi->reset_y=gi->start_location.ymin;
        gi->adjust_x=gi->grid_width;
        gi->adjust_y=0;
      }
      break;

    case ICONIZE_TO_TOPRIGHT:
      if (stack==ICONIZE_HORIZONTAL) {
        gi->dx=-gi->grid_width;
        gi->dy=0;
        gi->reset_x=gi->start_location.xmin;
        gi->reset_y=-1;
        gi->adjust_x=0;
        gi->adjust_y=-gi->grid_height;
      } else {
        gi->dx=0;
        gi->dy=-gi->grid_height;
        gi->reset_x=-1;
        gi->reset_y=gi->start_location.ymin;
        gi->adjust_x=-gi->grid_width;
        gi->adjust_y=0;
      }
      break;

    case ICONIZE_TO_BOTTOMRIGHT:
    default:
      if (stack==ICONIZE_HORIZONTAL) {
        gi->dx=-gi->grid_width;
        gi->dy=0;
        gi->reset_x=gi->start_location.xmin;
        gi->reset_y=-1;
        gi->adjust_x=0;
        gi->adjust_y=gi->grid_height;
      } else {
        gi->dx=0;
        gi->dy=gi->grid_height;
        gi->reset_x=-1;
        gi->reset_y=gi->start_location.ymin;
        gi->adjust_x=-gi->grid_width;
        gi->adjust_y=0;
      }
      break;

  }

  gi->start_location.xmax-=1;
  gi->start_location.ymax-=1;

  gi->test_location=gi->start_location;

  debugf("Grid width %d, height %d\n",gi->grid_width,gi->grid_height);
  debugf("dx=%d, dy=%d\n",gi->dx,gi->dy);
  debugf("reset_x=%d, reset_y=%d\n",gi->reset_x,gi->reset_y);
  debugf("adjust_x=%d, adjust_y=%d\n",gi->adjust_x,gi->adjust_y);
  debugf("start location: %d,%d,%d,%d\n",gi->start_location.xmin,gi->start_location.ymin,gi->start_location.xmax,gi->start_location.ymax);
}

static int gi_next_gridspace(_backdrop_state *state,grid_iterator *gi)
{
  // move the bbox to the next gridspace
  gi->test_location.xmin+=gi->dx;
  gi->test_location.xmax+=gi->dx;
  gi->test_location.ymin+=gi->dy;
  gi->test_location.ymax+=gi->dy;

  // out of range?
  if (gi->test_location.xmin<0 || (gi->test_location.xmax+1)>state->window_width || (gi->test_location.ymax+1)>state->window_height+iconbar_height || gi->test_location.ymin<iconbar_height) {
    // move to next col/row
    // sort bottom left corner of bbox
    if (gi->reset_x>=0) gi->test_location.xmin=gi->reset_x; else gi->test_location.xmin+=gi->adjust_x;
    if (gi->reset_y>=0) gi->test_location.ymin=gi->reset_y; else gi->test_location.ymin+=gi->adjust_y;
    // now topright
    gi->test_location.xmax=gi->test_location.xmin+gi->grid_width-1;
    gi->test_location.ymax=gi->test_location.ymin+gi->grid_height-1;
  }

  // out of screen?
  if (gi->test_location.xmin<0 || (gi->test_location.xmax+1)>state->window_width || (gi->test_location.ymax+1)>state->window_height+iconbar_height || gi->test_location.ymin<iconbar_height) return 1;

  // all ok
  return 0;
}

static void check_cfsi(void)
{
  /* make sure CFSI is known.  If not, have a go at finding it... */
  if (getenv("ChangeFSI$Dir")) return; /* it's there */

  /* hopefully it's on the current drive... */
  char *pathname;
  if (fs_canon("$.Utilities.!ChangeFSI",&pathname)) {
    int found;
    /* we need to make sure it's a valid location */
    _swix(OS_Find,_INR(0,1)|_OUT(0),5,pathname,&found);
    if (found==2) _swix(OS_SetVarVal,_INR(0,4),"ChangeFSI$Dir",pathname,strlen(pathname),0,0);
    flex_free((flex_ptr)&pathname);
  }
}

static void filer_show_parent(int index)
{
  struct
  {
    struct
    {
        int    size;
        int    sender;
        int    my_ref;
        int    your_ref;
        int    action_code;
    } hdr;

    int fsnumber;
    int flags;
    char dirname[256-28];
  } filer_openmessage;

  char *path;

  memset(&filer_openmessage,0,sizeof(filer_openmessage));

  if (index==-1) {
    path=getenv("Pinboard$Path");
    if (path==NULL) return;

    strcpy(&filer_openmessage.dirname[0],path);
    *(filer_openmessage.dirname+strlen(filer_openmessage.dirname)-1)=0; // remove trailing .
  } else {
    strcpy(filer_openmessage.dirname,indirected(pinlist->info[index].data.filename_offset,char *));
    if ((path=strrchr(filer_openmessage.dirname,'.'))==NULL) {
      if (strncmp(filer_openmessage.dirname,"Pinboard:",9)==0) {
        // try this expansion instead
        path=getenv("Pinboard$Path");
        if (path==NULL) return;

        strcpy(&filer_openmessage.dirname[0],path);
        *(filer_openmessage.dirname+strlen(filer_openmessage.dirname)-1)=0; // remove trailing .
      } else {
        // can't get a parent
        return;
      }
    } else {
      *path=0; // remove leafname
    }
  }

  _swix(OS_FSControl,_INR(0,2)|_OUT(1),13,filer_openmessage.dirname,0,&filer_openmessage.fsnumber);

  filer_openmessage.hdr.size=(28+1+strlen(filer_openmessage.dirname)+3) &~3;
  filer_openmessage.hdr.action_code=0x400; //Message_FilerOpen

  wimp_send_message(17,(WimpMessage *)&filer_openmessage,filer_taskhandle,0,NULL);
}

void grid_size(_backdrop_state *state,int *grid_width,int *grid_height)
{
  // determine the grid in use based on icon size
  switch (state->iconsize) {
    case ICONSIZE_DOUBLE:
      *grid_width=GRID_WIDTH_DOUBLE;
      *grid_height=GRID_HEIGHT_DOUBLE;
      break;

    case ICONSIZE_SMALL:
      *grid_width=GRID_WIDTH_SMALL;
      *grid_height=GRID_HEIGHT_SMALL;
      break;

    case ICONSIZE_NORMAL:
    default:
      *grid_width=GRID_WIDTH_NORMAL;
      *grid_height=GRID_HEIGHT_NORMAL;
      break;
    
  }
}

static void grid_snap(_backdrop_state *state,BBox *bbox)
{
  int grid_x,grid_y,w,h;
  int centre_x,centre_y;
  int grid_width,grid_height;

  if (state->snap_to_grid==0) return; // leave alone

  // update the coords as per grid
  grid_size(state,&grid_width,&grid_height);

  w=bbox->xmax-bbox->xmin;
  h=bbox->ymax-bbox->ymin;

  centre_x=bbox->xmin+(w/2); // centre coord of the pin
  centre_y=bbox->ymin+(h/2);

  debugf("centre coord is %d,%d\n",centre_x,centre_y);
  grid_x=grid_width*(centre_x/grid_width); // now aligned to a grid line
  debugf("aligns to grid line at %d\n",grid_x);
  if (state->iconsize==ICONSIZE_SMALL) {
    grid_x+=4;
  } else {
    grid_x+=(grid_width-w)/2; // adjust to the left margin of the pin
  }

//  grid_x=(grid_width*(int)(bbox->xmin/grid_width)) + ((grid_width-w)/2);

  // vertically depends if we're tidying to top or bottom
  if (state->setting_tidy==ICONIZE_TO_TOPLEFT || state->setting_tidy==ICONIZE_TO_TOPRIGHT) {
    int inv_y=(state->window_height+iconbar_height)-centre_y;
    grid_y=(inv_y/grid_height)*grid_height;
    grid_y=(state->window_height+iconbar_height)-grid_y-grid_height;
  } else {
    grid_y=grid_height*(centre_y/grid_height);
  }

  // grid_x, grid_y are the new centre point
debugf("new centrepoint %d,%d\n",grid_x,grid_y);
  bbox->xmin=grid_x;
  bbox->xmax=bbox->xmin+w;
  bbox->ymin=grid_y;
  bbox->ymax=bbox->ymin+h;
}

void pin_set_spritesize(pin *p,char *sprite)
{
  // set the sprite dimensions
  int mode;
  _swix(Wimp_SpriteOp,_IN(0)|_IN(2)|_OUT(3)|_OUT(4)|_OUT(6),40,sprite,&p->sprite_x,&p->sprite_y,&mode);
  p->sprite_x<<=bbc_modevar(mode,bbc_XEigFactor);
  p->sprite_y<<=bbc_modevar(mode,bbc_YEigFactor);
}

int truncate(char **text,char **truncated)
{
  if (!flex_alloc((flex_ptr)truncated,4+strlen(*text))) {
    msgtrans_report_error("NoMem");
    return 0;
  }
  _swix(Wimp_TextOp,_INR(0,4),4,*text,*truncated,4+strlen(*text),MAX_ICON_WIDTH);

  return 1;
}

/* two pass append_printf.  If format contains pointers to flex data then it may move when
   we extend the save store which leads to unpleasantness.
   Flags bit 0 - allocate memory
   Flags bit 1 - do the writing */

static int pinboard_savedata_append_printf(int flags,char **store,char *format,...)
{
  va_list ap;
  int blocksize;
    
  va_start(ap,format);
  blocksize=vsnprintf(NULL,0,format,ap);
  va_end(ap);

  if (flags & 1) {  
    if (!flex_extend((flex_ptr)store,flex_size((flex_ptr)store)+blocksize)) {
      msgtrans_report_error("NoMem");
      return 0;
    }
  }

  if (flags & 2) {
    int append_loc=flex_size((flex_ptr)store)-blocksize-1;
    va_start(ap,format);
    vsnprintf(*store+append_loc,blocksize+1,format,ap);
    va_end(ap);
  }

  return 1;
}

static int pinboard_create_savedata(_backdrop_state *state,char **store,int *size)
{
  int i,reld;
  char *relname;

  if (!sticky_save(state)) return 0; // write the stickies to their own choices file
  
  if (!flex_alloc((flex_ptr)store,1)) {
    msgtrans_report_error("NoMem");
    return 0;
  }
  *(*store)=0;

  if (!pinboard_savedata_append_printf(3,store,"Pinboard %s\n",state->snap_to_grid?"-Grid":"")) {
    *size=0;
    flex_free((flex_ptr)store);
    return 0;
  }

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anyfile(&pinlist->info[i])) {
      reld=fs_make_boot_relative(indirected(pinlist->info[i].data.filename_offset,char *),&relname);
      if (pin_is_tiny(&pinlist->info[i])) {
        if (!pinboard_savedata_append_printf(1,store,"X XAddTinyDir %s\n",relname)) {
          flex_free((flex_ptr)store);
          *size=0;
          if (reld) flex_free((flex_ptr)&relname);
          return 0;
        }
        pinboard_savedata_append_printf(2,store,"X XAddTinyDir %s\n",relname);

      } else {
        if (!pinboard_savedata_append_printf(1,store,"X XPin %s %s%d %s%d %s\n",
                                             relname,
                                             pinlist->info[i].icondata.bbox.xmin<0?"0":"",pinlist->info[i].icondata.bbox.xmin,
                                             pinlist->info[i].icondata.bbox.ymin<0?"0":"",pinlist->info[i].icondata.bbox.ymin,
                                             pin_is_locked(&pinlist->info[i])?"-L":"")) {
          flex_free((flex_ptr)store);
          *size=0;
          if (reld) flex_free((flex_ptr)&relname);
          return 0;
        }
        pinboard_savedata_append_printf(2,store,"X XPin %s %s%d %s%d %s\n",
                                             relname,
                                             pinlist->info[i].icondata.bbox.xmin<0?"0":"",pinlist->info[i].icondata.bbox.xmin,
                                             pinlist->info[i].icondata.bbox.ymin<0?"0":"",pinlist->info[i].icondata.bbox.ymin,
                                             pin_is_locked(&pinlist->info[i])?"-L":"");
      }
      if (reld) flex_free((flex_ptr)&relname);
    }
  }

  *size=flex_size((flex_ptr)store)-1;
  return 1;
}

void pinboard_save(_backdrop_state *state,char *path)
{
  char *store;
  _kernel_oserror *e;
  int size;
  
  if (pinboard_create_savedata(state,&store,&size)) {
    if ((e=_swix(OS_File,_INR(0,5),10,path,0xfeb,0,store,store+size))!=NULL) {
      wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    }
    flex_free((flex_ptr)&store);
  }

}

static char *pinboard_save_buffer=NULL;
static int pinboard_save_length=0;

static int pinboard_save_tobeshown(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  IGNORE(event_code);
  IGNORE(event);
  
  pinboard_create_savedata((_backdrop_state *)handle,&pinboard_save_buffer,&pinboard_save_length);
  saveas_set_file_size(0,id->self_id,pinboard_save_length);
  return 1;
}

static int pinboard_save_fill(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  SaveAsFillBufferEvent *e=(SaveAsFillBufferEvent *)event;
  IGNORE(event_code);
  IGNORE(handle);
  
  saveas_buffer_filled(0,id->self_id,pinboard_save_buffer+e->no_bytes,pinboard_save_length-e->no_bytes);
  return 1;
}

static int pinboard_save_to_file(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  SaveAsSaveToFileEvent *e=(SaveAsSaveToFileEvent *)event;
  IGNORE(event_code);
  IGNORE(handle);
  
  _swix(OS_File,_INR(0,5),10,e->filename,0xfeb,0,pinboard_save_buffer,pinboard_save_buffer+pinboard_save_length);
  saveas_file_save_completed(1,id->self_id,e->filename);
  return 1;
}

static int pinboard_save_completed(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(id);
  IGNORE(handle);
  
  if (pinboard_save_buffer) {
    flex_free((flex_ptr)&pinboard_save_buffer);
    pinboard_save_buffer=NULL;
    pinboard_save_length=0;
  }
  return 1;
}

void pins_init(void)
{
  int type,length;
  char *data;
  int offset;

  if (!flex_alloc((flex_ptr)&pinlist,sizeof(pins))) {
  // fatal
    msgtrans_report_error("MemFatal");
    exit (1);
  }

  pinlist->count=0;

  // is there an Adjust-style WinSetup file?
  if (_swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(4),5,"Choices:Boot.PreDesk.WinSetup",&type,&length)) return;
  if (type!=1) return;

  // yes - let's see if we can find a visualflags option
  if (!flex_alloc((flex_ptr)&data,length+1)) return;

  if (_swix(OS_File,_INR(0,3),255,"Choices:Boot.PreDesk.WinSetup",data,0)) {
    flex_free((flex_ptr)&data);
    return;
  }
  *(data+length)=0;

  for (offset=0;offset<length;offset++) {
    if (*(data+offset)<32) *(data+offset)=0; else *(data+offset)=toupper(*(data+offset));
  }

  default_text_style=BD_TEXT_BLEND;

  for (offset=0;offset<length;offset+=strlen(data+offset)+1) {
    if (strstr(data+offset,"WIMPVISUALFLAGS")) {
      if (strstr(data+offset,"-NOFONTBLENDING") || strstr(data+offset,"-NFB")) {
        default_text_style=BD_TEXT_THEME;
      }
      if (strstr(data+offset,"-NOICONBOXESINTRANSWINDOWS") || strstr(data+offset,"-NIBITW")) {
        default_rubout_box=0;
      }
    }
  }
  flex_free((flex_ptr)&data);

}

void pin_update_offsets(int offset,int adjustment)
{
  // if the indirected stuff moves, we need to alter pin data as well

  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pinlist->info[i].icondata.buffer!=INDIRECT_FREEI) {
      if (pinlist->info[i].icondata.buffer>offset) pinlist->info[i].icondata.buffer+=adjustment;
    }
    if (pinlist->info[i].icondata.sprite!=INDIRECT_FREEI) {
      if (pinlist->info[i].icondata.sprite>offset) pinlist->info[i].icondata.sprite+=adjustment;
    }
    
    if (pin_is_anyfile(&pinlist->info[i])) {
      if (pinlist->info[i].data.filename_offset!=INDIRECT_FREEI) {
        if (pinlist->info[i].data.filename_offset>offset) pinlist->info[i].data.filename_offset+=adjustment;
      }
    }

    if (pin_is_sticky(&pinlist->info[i])) {
      if (pinlist->info[i].data.sticky.font_name!=INDIRECT_FREEI) {
        if (pinlist->info[i].data.sticky.font_name>offset) pinlist->info[i].data.sticky.font_name+=adjustment;
      }
      if (pinlist->info[i].data.sticky.title!=INDIRECT_FREEI) {
        if (pinlist->info[i].data.sticky.title>offset) pinlist->info[i].data.sticky.title+=adjustment;
      }
    }
  }
}

static void pin_free_store_static(pin *p)
{
  if (pin_is_fsfile(p) || pin_is_localfile(p)) {
    if (p->data.filename_offset!=INDIRECT_FREEI) {
      indirected_remove(p->data.filename_offset);
      p->data.filename_offset=INDIRECT_FREEI;
    }
  }

  if (!(pin_is_tiny(p)) && !(pin_is_sticky(p))) {
    if (p->icondata.buffer!=INDIRECT_FREEI) {
      indirected_remove(p->icondata.buffer);
      p->icondata.buffer=INDIRECT_FREEI;
    }
    if (p->icondata.sprite!=INDIRECT_FREEI) {
      indirected_remove(p->icondata.sprite);
      p->icondata.buffer=INDIRECT_FREEI;
    }
  }
}

static void pin_free_store(int p)
{
  if (pin_is_fsfile(&pinlist->info[p]) || pin_is_localfile(&pinlist->info[p])) {
    if (pinlist->info[p].data.filename_offset!=INDIRECT_FREEI) {
      indirected_remove(pinlist->info[p].data.filename_offset);
      pinlist->info[p].data.filename_offset=INDIRECT_FREEI;
    }
  }

  if (!(pin_is_tiny(&pinlist->info[p])) && !(pin_is_sticky(&pinlist->info[p]))) {
    if (pinlist->info[p].icondata.buffer!=INDIRECT_FREEI) {
      indirected_remove(pinlist->info[p].icondata.buffer);
      pinlist->info[p].icondata.buffer=INDIRECT_FREEI;
    }
    if (pinlist->info[p].icondata.sprite!=INDIRECT_FREEI) {
      indirected_remove(pinlist->info[p].icondata.sprite);
      pinlist->info[p].icondata.sprite=INDIRECT_FREEI;
    }
  }
}

int pin_remove(_backdrop_state *state,int p)
{
  if (p>=pinlist->count) return 0;

  pin_free_store(p);
  
  // free the pin &
  // shrink store
  flex_midextend((flex_ptr)&pinlist,(char *)&pinlist->info[p+1] - (char *)pinlist, -(int)sizeof(pin));

  pinlist->count--;
  outline_clear_cache(state);
  return 1;
}

int pin_add(pin *newpin)
{
  if (!flex_extend((flex_ptr)&pinlist,sizeof(pins)+(1+pinlist->count)*sizeof(pin))) {
    return -1;
  }

  memcpy(&pinlist->info[pinlist->count],newpin,sizeof(pin));
  pinlist->count++;
  return pinlist->count-1;
}

#define box_clicked(r,x,y) ((x)>=(r)->xmin && (x)<=(r)->xmax && (y)>=(r)->ymin && (y)<=(r)->ymax)

static int pin_check_point(int x,int y,pin *icon,_iconsize iconsize)
{
  int textwidth,centre,w;
  BBox subbox;

  switch (iconsize) {
    case ICONSIZE_SMALL:
      // small icons - just simple bbox
      return box_clicked(&icon->icondata.bbox,x,y);

    case ICONSIZE_NORMAL:
      // text rectangle first
      _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(icon->icondata.buffer,char *),0,&textwidth);
      textwidth+=8;

      subbox.ymin=icon->icondata.bbox.ymin;
      subbox.ymax=subbox.ymin+ICON_TEXT_HEIGHT;
      
      w=icon->icondata.bbox.xmax-icon->icondata.bbox.xmin;
      subbox.xmin=(w-textwidth)/2+icon->icondata.bbox.xmin;
      subbox.xmax=subbox.xmin+textwidth;
     
      if (box_clicked(&subbox,x,y)) return 1;

      // and now the sprite
      subbox.ymax=icon->icondata.bbox.ymax;
      subbox.ymin=subbox.ymax-icon->sprite_y;
      centre=(icon->icondata.bbox.xmax-icon->icondata.bbox.xmin-icon->sprite_x)/2;
      subbox.xmin=icon->icondata.bbox.xmin+centre;
      subbox.xmax=icon->icondata.bbox.xmax-centre;
      return box_clicked(&subbox,x,y);

    case ICONSIZE_DOUBLE:
      // text rectangle first
      _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(icon->icondata.buffer,char *),0,&textwidth);
      textwidth+=8;

      subbox.ymin=icon->icondata.bbox.ymin;
      subbox.ymax=subbox.ymin+ICON_TEXT_HEIGHT;
      
      w=icon->icondata.bbox.xmax-icon->icondata.bbox.xmin;
      subbox.xmin=(w-textwidth)/2+icon->icondata.bbox.xmin;
      subbox.xmax=subbox.xmin+textwidth;
     
      if (box_clicked(&subbox,x,y)) return 1;

      // and now the sprite
      subbox.ymax=icon->icondata.bbox.ymax;
      subbox.ymin=subbox.ymax-icon->sprite_y*2;
      centre=(icon->icondata.bbox.xmax-icon->icondata.bbox.xmin-icon->sprite_x*2)/2;
      subbox.xmin=icon->icondata.bbox.xmin+centre;
      subbox.xmax=icon->icondata.bbox.xmax-centre;
      return box_clicked(&subbox,x,y);
  }

  return 0;  
}

static int pin_check_click(int x,int y,_iconsize iconsize)
{
  int i;

  for (i=pinlist->count-1;i>=0;i--) { // run the stack backwards
    if (!pin_is_tiny(&pinlist->info[i]) && !pin_is_sticky(&pinlist->info[i])) {
      if (pin_check_point(x,y,&pinlist->info[i],iconsize)) return i;
    }
  }
  return -1; // unknown icon
}

static int pin_intersect2(BBox *a,BBox *b)
{
  // test for intersection of two simple rectangles
  if (a->xmax<b->xmin) return 0;
  if (a->xmin>b->xmax) return 0;
  if (a->ymax<b->ymin) return 0;
  if (a->ymin>b->ymax) return 0;

  return 1;
}

static int pin_intersect(BBox *rect,pin *icon,_iconsize iconsize)
{
  // check if the icon and rect intersect
  BBox subbox;
  int centre;
  int textwidth,w;

  if (pin_is_sticky(icon)) {
    return pin_intersect2(rect,&icon->icondata.bbox);
  }

  switch (iconsize) {
    case ICONSIZE_SMALL:
      // small icons - just simple bbox
      return pin_intersect2(rect,&icon->icondata.bbox);

    case ICONSIZE_NORMAL:
      // text rectangle first
      _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(icon->icondata.buffer,char *),0,&textwidth);
      textwidth+=8;

      subbox.ymin=icon->icondata.bbox.ymin;
      subbox.ymax=subbox.ymin+ICON_TEXT_HEIGHT;
      
      w=icon->icondata.bbox.xmax-icon->icondata.bbox.xmin;
      subbox.xmin=(w-textwidth)/2+icon->icondata.bbox.xmin;
      subbox.xmax=subbox.xmin+textwidth;
     
      if (pin_intersect2(rect,&subbox)) return 1;

      // and now the sprite
      subbox.ymax=icon->icondata.bbox.ymax;
      subbox.ymin=subbox.ymax-icon->sprite_y;
      centre=(icon->icondata.bbox.xmax-icon->icondata.bbox.xmin-icon->sprite_x)/2;
      subbox.xmin=icon->icondata.bbox.xmin+centre;
      subbox.xmax=icon->icondata.bbox.xmax-centre;
      return pin_intersect2(rect,&subbox);

    case ICONSIZE_DOUBLE:
      // text rectangle first
      _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(icon->icondata.buffer,char *),0,&textwidth);
      textwidth+=8;

      subbox.ymin=icon->icondata.bbox.ymin;
      subbox.ymax=subbox.ymin+ICON_TEXT_HEIGHT;
      
      w=icon->icondata.bbox.xmax-icon->icondata.bbox.xmin;
      subbox.xmin=(w-textwidth)/2+icon->icondata.bbox.xmin;
      subbox.xmax=subbox.xmin+textwidth;
     
      if (pin_intersect2(rect,&subbox)) return 1;

      // and now the sprite
      subbox.ymax=icon->icondata.bbox.ymax;
      subbox.ymin=subbox.ymax-icon->sprite_y*2;
      centre=(icon->icondata.bbox.xmax-icon->icondata.bbox.xmin-icon->sprite_x*2)/2;
      subbox.xmin=icon->icondata.bbox.xmin+centre;
      subbox.xmax=icon->icondata.bbox.xmax-centre;
      return pin_intersect2(rect,&subbox);
  }

  return 0;
}

static int pin_space_free(BBox *test,_iconsize iconsize)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_intersect(test,&pinlist->info[i],iconsize)) return 0;
  }
  
  return 1;
}

void pin_find_gap(_backdrop_state *state,IconizeSetting corner,IconizeStack stack_direction,BBox *gap)
{
  // locate a gap on the pinboard for a single new pin instead of a batch
  grid_iterator gi;

  gi_init(state,&gi,corner,stack_direction);

  do {
    if (pin_space_free(&gi.test_location,state->iconsize)) {
      // space here, return the location box
      *gap=gi.test_location;
      return;
    }
  } while (!gi_next_gridspace(state,&gi));

  // we overflowed.
  // set to current mouse coordinates instead
  WimpGetPointerInfoBlock mouse;
  wimp_get_pointer_info(&mouse);
  gap->xmin=mouse.x-gi.grid_width/2;
  gap->ymin=mouse.y-gi.grid_height/2;
  gap->xmax=gap->xmin+gi.grid_width;
  gap->ymax=gap->ymin+gi.grid_height;
}

static void pin_draw(_backdrop_state *state,WimpRedrawWindowBlock *block,pin *p)
{
  int textwidth,x,y,yoffset,textheight,free_area=0;
  int rtl;
  WimpSysInfo info;
  int text_style,rubout=0;
  int *spritearea=NULL;
  char spritename[13];

  // plot the pin

  // sprite
  // plot as an icon so we get an appropriate selected image if need be
  WimpIconBlock icon;

  icon.flags=0x102 | (p->icondata.flags & WimpIcon_Selected);
  icon.data.is.sprite=indirected(p->icondata.sprite,char *);

  switch (state->iconsize) {
    case ICONSIZE_SMALL:
      {
        int hs_flag;
        icon.bbox.ymin=(p->icondata.bbox.ymax-p->icondata.bbox.ymin-(p->sprite_y/2))/2+p->icondata.bbox.ymin-block->visible_area.ymax;
        icon.bbox.ymax=icon.bbox.ymin+p->sprite_y/2;
        icon.bbox.xmin=p->icondata.bbox.xmin;
        icon.bbox.xmax=icon.bbox.xmin+p->sprite_x/2;
        sprite_halfsize(icon.data.is.sprite,spritename,&hs_flag);
        icon.flags|=(hs_flag<<11); // half size sprite please if we can't find a better option
        icon.data.is.sprite_area=(char *)1;
        if (!hs_flag) icon.data.is.sprite=spritename;
      }
      break;

    case ICONSIZE_NORMAL:
      icon.bbox.ymax=p->icondata.bbox.ymax-block->visible_area.ymax;
      icon.bbox.ymin=icon.bbox.ymax-p->sprite_y;
      icon.bbox.xmin=(p->icondata.bbox.xmax-p->icondata.bbox.xmin)/2-p->sprite_x/2+p->icondata.bbox.xmin;
      icon.bbox.xmax=icon.bbox.xmin+p->sprite_x;
      icon.data.is.sprite_area=(char *)1;
      break;

    case ICONSIZE_DOUBLE:
      if (sprite_doublesize(state,icon.data.is.sprite,&spritearea,&free_area)) {
        icon.data.is.sprite_area=(char *)spritearea;
      } else {
        icon.data.is.sprite_area=(char *)1;
      }

      icon.bbox.ymax=p->icondata.bbox.ymax-block->visible_area.ymax;
      icon.bbox.ymin=icon.bbox.ymax-p->sprite_y*2;
      icon.bbox.xmin=(p->icondata.bbox.xmax-p->icondata.bbox.xmin)/2-p->sprite_x+p->icondata.bbox.xmin;
      icon.bbox.xmax=icon.bbox.xmin+p->sprite_x*2;
      break;
  }
  icon.data.is.sprite_name_length=strlen(icon.data.is.sprite)+1;
  wimp_plot_icon(&icon);

  if (free_area && spritearea) flex_free((flex_ptr)&spritearea);

  // text...
  wimp_read_sys_info(4,&info);
  rtl=info.r0;

  // theme
  if (BD_TEXT_STYLE(state->bd_flags)==BD_TEXT_THEME) {
    // default to wimp theme
    if (wimp_read_sys_info(28,&info)==NULL) { // get wimpvisualflags
      if (/*(info.r0 & (1<<3)) || */(info.r0 & (1<<5))) {
        rubout=0;
      } else {
        rubout=1;
      }

      if ((info.r0 & (1<<6))) {
        text_style=BD_TEXT_THEME;
      } else {
        text_style=BD_TEXT_BLEND;
      }
    } else {
      // can't get a theme, so basic option only
      rubout=default_rubout_box;
      text_style=default_text_style;
    }

  } else {
    text_style=BD_TEXT_STYLE(state->bd_flags);
    rubout=0;
  }


  // font
  wimp_read_sys_info(8,&info);
  if (info.r0==0) {
    yoffset=0;
    textheight=32;
  } else {
    int ymin,ymax;
    _swix(Font_ReadInfo,_IN(0)|_OUT(2)|_OUT(4),info.r0,&ymin,&ymax);
    yoffset=ymin<0?-ymin:0;
    textheight=ymax;
  }
  _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(p->icondata.buffer,char *),0,&textwidth);
  textwidth+=8;

  switch (state->iconsize) {
    case ICONSIZE_SMALL:
      // text goes to right of sprite
      y=p->icondata.bbox.ymin+2;
      x=p->icondata.bbox.xmin+p->sprite_x/2+8;
      break;

    case ICONSIZE_NORMAL:
    case ICONSIZE_DOUBLE:
    default:
      x=(p->icondata.bbox.xmax-p->icondata.bbox.xmin)/2-textwidth/2+p->icondata.bbox.xmin;
      y=p->icondata.bbox.ymin+2;
      break;
  }

  if (rubout) {
    _swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),p->icondata.flags & WimpIcon_Selected?state->fg_colour:state->bd_colour,(1<<8),0);
    plot(4,x,y);
    plot(96+5,x+textwidth-state->pixel_x,y+yoffset+textheight+2*state->pixel_y);
  }

  switch (text_style) {
  case BD_TEXT_OUTLINE:
    if (state->text_sprite==NULL) {
      outline_init(state);
      outline_clear_cache(state);
    }
    if (state->text_sprite) {
      outline_render(state,p,p-&pinlist->info[0],x,y,yoffset);
      return;
    }
    // no area for rendering, so fall through to font blending option instead
    
  case BD_TEXT_BLEND:
    if ((p->icondata.flags & WimpIcon_Selected)==0) {
      _swix(ColourTrans_SetFontColours,_INR(0,3),info.r0,state->bd_colour,state->fg_colour,14);
      _swix(Font_Paint,_INR(0,4),info.r0,indirected(p->icondata.buffer,char *),(1<<4)|(1<<8)|(1<<9)|(rtl<<10)|(1<<11),x+rtl*textwidth,y+state->pixel_y+yoffset);
      return;
    } else {
      int inv_fg;
//      _swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),0x00000000,(1<<8),0);
//      plot(4,x,y);
//      plot(96+5,x+textwidth-state->pixel_x,y+yoffset+textheight+2*state->pixel_y);
      _swix(ColourTrans_ReturnOppColourNumber,_IN(0)|_OUT(0),state->fg_colour,&inv_fg);
      _swix(ColourTrans_SetFontColours,_INR(0,3),info.r0,state->bd_colour,inv_fg,14);
      _swix(Font_Paint,_INR(0,4),info.r0,indirected(p->icondata.buffer,char *),(1<<4)|(1<<8)|(1<<9)|(rtl<<10)|(1<<11),x+rtl*textwidth,y+state->pixel_y+yoffset);
//      _swix(Wimp_TextOp,_INR(0,2),0,0xeeeeee00,0x00000000);
//      _swix(Wimp_TextOp,_INR(0,5),2,indirected(p->icondata.buffer,char *),-1,-1,x,y+state->pixel_y+yoffset);
      return;
    }
    break;
    
  case BD_TEXT_THEME:
  default:
    break;
  }

  // we weren't able to render it with a fancy means, so...
  // write text on the top of it
  _swix(Wimp_TextOp,_INR(0,2),0,p->icondata.flags & WimpIcon_Selected?state->bd_colour:state->fg_colour,p->icondata.flags & WimpIcon_Selected?state->fg_colour:state->bd_colour);
  _swix(Wimp_TextOp,_INR(0,5),2,indirected(p->icondata.buffer,char *),-1,-1,x,y+state->pixel_y+yoffset);



}
/*
static void apply_clip_rect(BBox *rect)
{
  char vdubuf[9]={
  24,
  rect->xmin &0xff,
  rect->xmin>>8,
  rect->ymin &0xff,
  rect->ymin>>8,
  rect->xmax &0xff,
  rect->xmax>>8,
  rect->ymax &0xff,
  rect->ymax>>8};

  _swix(OS_WriteN,_INR(0,1),vdubuf,9);
}

static int intersect_clip_rect(BBox *new,BBox *old)
{
  // work out intersection of clip rectangles and apply.

  // work out clip intersection
  if (old->xmin > new->xmin) new->xmin=old->xmin;
  if (old->xmax < new->xmax) new->xmax=old->xmax;
  if (old->ymin > new->ymin) new->ymin=old->ymin;
  if (old->ymax < new->ymax) new->ymax=old->ymax;

  if (new->xmax <= new->xmin || new->ymax <= new->ymin) return 0;

  // apply
  apply_clip_rect(new);
  return 1;
}
*/
static void backdrop_redraw_loop(_backdrop_state *state,WimpRedrawWindowBlock *block,int more)
{
  int i;
  char *suffix;
  
  _swix(Wimp_ReadSysInfo,_IN(0)|_OUT(0),2,&suffix);
  if (strcmp(suffix,"24")==0) suffix="";
  
  while (more) {
    switch (BD_IMAGE_TYPE(state->bd_flags)) {
    case BD_IMAGE_PLAIN:
      _swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),state->bd_colour,(1<<8),0);
      plot(4,block->redraw_area.xmin,block->redraw_area.ymin);
      plot(96+5,block->redraw_area.xmax,block->redraw_area.ymax);
      break;
      
    case BD_IMAGE_SPRITE:
      {
        int origin_x=0+block->visible_area.xmin-block->xscroll;
        int origin_y=0+block->visible_area.ymax-block->yscroll-state->window_height;

        int *area=(int *)state->bd_image;
        switch (BD_PAINT(state->bd_flags)) {
        case BD_SCALED:
          {
            int scale[4];
            scale[0]=state->window_width;
            scale[1]=state->window_height;
            scale[2]=state->image_width*state->pixel_x;
            scale[3]=state->image_height*state->pixel_y;
            _swix(OS_SpriteOp,_INR(0,7),52+512,area,((char *)area)+*(area+2),origin_x,origin_y,state->colourtrans?(1<<5):0,&scale,state->colourtrans);
          }
          break;
          
        case BD_CENTRED:
          {
            int spritex=(state->window_width-state->image_width*state->pixel_x)/2;
            int spritey=(state->window_height-state->image_height*state->pixel_y)/2;

            // border
            _swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),state->bd_colour,(1<<8),0);
            plot(4,origin_x,origin_y);
            plot(96+5,origin_x+spritex-state->pixel_x,origin_y+state->window_height);
            plot(96+5,origin_x+state->window_width,origin_y+spritey+state->image_height*state->pixel_y);
            plot(96+5,origin_x+spritex+state->image_width*state->pixel_x,origin_y);
            plot(96+5,origin_x+spritex,origin_y+spritey);

            _swix(OS_SpriteOp,_INR(0,7),52+512,area,((char *)area)+*(area+2),origin_x+spritex,origin_y+spritey,state->colourtrans?(1<<5):0,0,state->colourtrans);
          }
          break;

        case BD_TILED:
          if (filer_is_adjust) {
            // spriteop not supported by Adjust, manual tile needed
            int x,y,barheight=iconbar_height;
            if (state->image_width!=0 && state->image_height!=0 && state->pixel_x!=0 && state->pixel_y!=0) {
              for (x=block->redraw_area.xmin/(state->image_width*state->pixel_x);x<=block->redraw_area.xmax/(state->image_width*state->pixel_x);x++) {
                for (y=barheight/(state->image_height*state->pixel_y);y<=block->redraw_area.ymax/(state->image_height*state->pixel_y);y++) {
                  if ((y+1)*state->image_height*state->pixel_y>=block->redraw_area.ymin) {
                    _swix(OS_SpriteOp,_INR(0,7),52+512,area,((char *)area)+*(area+2),origin_x+x*state->image_width*state->pixel_x,origin_y-barheight+y*state->image_height*state->pixel_y,state->colourtrans?(1<<5):0,0,state->colourtrans);
                  }
                }
              }
            }

          } else {
            _swix(OS_SpriteOp,_INR(0,7),65+512,area,((char *)area)+*(area+2),origin_x,origin_y,state->colourtrans?(1<<5):0,0,state->colourtrans);
          }
          break;

        case BD_FULLSCREEN:
          {
            int scale[4];
            int barheight=iconbar_height;
            scale[0]=state->window_width;
            scale[1]=state->window_height+barheight;
            scale[2]=state->image_width*state->pixel_x;
            scale[3]=state->image_height*state->pixel_y;
            _swix(OS_SpriteOp,_INR(0,7),52+512,area,((char *)area)+*(area+2),origin_x,origin_y-barheight,state->colourtrans?(1<<5):0,&scale,state->colourtrans);
          }
          break;
        }
      }                           
      break;
      
    case BD_IMAGE_JPEG:
      {
        int origin_x=0+block->visible_area.xmin-block->xscroll;
        int origin_y=0+block->visible_area.ymax-block->yscroll-state->window_height;
        switch (BD_PAINT(state->bd_flags)) {
        case BD_SCALED:
          {
            int scale[4];
            scale[0]=state->window_width;
            scale[1]=state->window_height;
            scale[2]=state->image_width*state->pixel_x;
            scale[3]=state->image_height*state->pixel_y;
            
            _swix(JPEG_PlotScaled,_INR(0,6),state->bd_image,origin_x,origin_y,&scale,state->image_length,3,0);
          }
          break;
          
        case BD_TILED:
          {
            int x,y,barheight=iconbar_height;

            if (state->image_width!=0 && state->image_height!=0 && state->pixel_x!=0 && state->pixel_y!=0) {
              for (x=block->redraw_area.xmin/(state->image_width*state->pixel_x);x<=block->redraw_area.xmax/(state->image_width*state->pixel_x);x++) {
                for (y=iconbar_height/(state->image_height*state->pixel_y);y<=block->redraw_area.ymax/(state->image_height*state->pixel_y);y++) {
                  if ((y+1)*state->image_height*state->pixel_y>=block->redraw_area.ymin) {
                    _swix(JPEG_PlotScaled,_INR(0,6),state->bd_image,origin_x+x*state->image_width*state->pixel_x,origin_y-barheight+y*state->image_height*state->pixel_y,0,state->image_length,3,0);
                  }
                }
              }
            }
          }
          break;
          
        case BD_CENTRED:
          {
            int spritex=(state->window_width-state->image_width*state->pixel_x)/2;
            int spritey=(state->window_height-state->image_height*state->pixel_y)/2;
            // border
            _swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),state->bd_colour,(1<<8),0);
            plot(4,origin_x,origin_y);
            plot(96+5,origin_x+spritex-state->pixel_x,origin_y+state->window_height);
            plot(96+5,origin_x+state->window_width,origin_y+spritey+state->image_height*state->pixel_y);
            plot(96+5,origin_x+spritex+state->image_width*state->pixel_x,origin_y);
            plot(96+5,origin_x+spritex,origin_y+spritey);

            _swix(JPEG_PlotScaled,_INR(0,6),state->bd_image,origin_x+spritex,origin_y+spritey,0,state->image_length,3,0);

          }
          break;

        case BD_FULLSCREEN:
          {
            int scale[4];
            int barheight=iconbar_height;
            scale[0]=state->window_width;
            scale[1]=state->window_height+barheight;
            scale[2]=state->image_width*state->pixel_x;
            scale[3]=state->image_height*state->pixel_y;
            
            _swix(JPEG_PlotScaled,_INR(0,6),state->bd_image,origin_x,origin_y-barheight,&scale,state->image_length,3,0);
          }
          break;
        }
        break;
      }

    }
/*
// grid overlay
int gx,gy;
grid_size(state,&gx,&gy);
int ox,oy;
_swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),0,0,0);
for (ox=0;ox<=state->window_width;ox+=gx) {
  _swix(OS_Plot,_INR(0,2),4,ox,iconbar_height);
  _swix(OS_Plot,_INR(0,2),5,ox,iconbar_height+state->window_height);
}
for (oy=state->window_height;oy>=0;oy-=gy) {
  _swix(OS_Plot,_INR(0,2),4,0,oy+iconbar_height);
  _swix(OS_Plot,_INR(0,2),5,state->window_width,oy+iconbar_height);
}
// ends
*/
    // watermark?
    if (state->watermark_data) {
      // intersection?
      if (pin_intersect2(&state->watermark_plot_area,&block->redraw_area)) {
        printf("intersect, so redraw\n");
        watermark_redraw(state,block);
      }
    }

 #ifdef BUILD
 _swix(Wimp_TextOp,_INR(0,2),0,0xffffff00,0x0);
 _swix(Wimp_TextOp,_INR(0,5),2,"Alpha build " BUILD,-1,-1,0,160);
 #endif

    // draw any pins that are relevant

    for (i=0;i<pinlist->count;i++) {
      if (!pin_is_sticky(&pinlist->info[i])) {
        if (pinlist->info[i].icontype!=pin_type_empty && !(pin_is_sticky(&pinlist->info[i])) && !(pin_is_tiny(&pinlist->info[i]))) {
          // do we need to draw this one?
          if (block->redraw_area.xmin<pinlist->info[i].icondata.bbox.xmax &&
              block->redraw_area.xmax>pinlist->info[i].icondata.bbox.xmin &&
              block->redraw_area.ymin<pinlist->info[i].icondata.bbox.ymax &&
              block->redraw_area.ymax>pinlist->info[i].icondata.bbox.ymin) {
            pin_draw(state,block,&pinlist->info[i]);
            
            // add a link sprite if it's not local to pinboard
            // put on top-right of the icon sprite
            if (pin_is_fsfile(&pinlist->info[i])) {
              switch (state->iconsize) {
              case ICONSIZE_DOUBLE:
                if (*suffix==0) {
                  // use 'dbl' sprite
                  plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin -28,
                                  pinlist->info[i].icondata.bbox.ymax-56,
                                  "link",
                                  "dbl");
                } else {
                  if (strcmp(suffix,"22")==0) {
                    // use 11 sprite, no scale
                    plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin -28,
                                  pinlist->info[i].icondata.bbox.ymax-56,
                                  "link",
                                  "11");
                  } else {
                    // use 00 sprite, no scale
                    plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin -28,
                                  pinlist->info[i].icondata.bbox.ymax-56,
                                  "link",
                                  "00");
                  }
                }
                break;

              case ICONSIZE_NORMAL:
                plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin -28,
                                  pinlist->info[i].icondata.bbox.ymax-28,
                                  "link",
                                  suffix);
                break;

              case ICONSIZE_SMALL:
                plot_trans_sprite(SA_LOCAL,
                                  pinlist->info[i].icondata.bbox.xmin+8,
                                  pinlist->info[i].icondata.bbox.ymax-28,
                                  "link",
                                  suffix);
                break;
              }
            }
            if (pin_is_locked(&pinlist->info[i])) {
              switch (state->iconsize) {
              case ICONSIZE_DOUBLE:
                if (*suffix==0) {
                  // use 'dbl' sprite
                  plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin-112,
                                  pinlist->info[i].icondata.bbox.ymax-56,
                                  "lock",
                                  "dbl");
                } else {
                  if (strcmp(suffix,"22")==0) {
                    // use 11 sprite, no scale
                    plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin-112,
                                  pinlist->info[i].icondata.bbox.ymax-56,
                                  "lock",
                                  "11");
                  } else {
                    // use 00 sprite, no scale
                    plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 + pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin-112,
                                  pinlist->info[i].icondata.bbox.ymax-56,
                                  "lock",
                                  "00");
                  }
                }
                break;

              case ICONSIZE_NORMAL:
                plot_trans_sprite(SA_LOCAL,
                                  (pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2 - pinlist->info[i].sprite_x/2 + pinlist->info[i].icondata.bbox.xmin,
                                  pinlist->info[i].icondata.bbox.ymax-28,
                                  "lock",
                                  suffix);
                break;

              case ICONSIZE_SMALL:
                plot_trans_sprite(SA_LOCAL,
                                  pinlist->info[i].icondata.bbox.xmin,
                                  pinlist->info[i].icondata.bbox.ymax-28,
                                  "lock",
                                  suffix);
                break;
              }
            }
          }
        }
      }
    }
    
    wimp_get_rectangle(block,&more);
  }

}
/*
int pin_get_selected(int mask)
{
  // returns first selection
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      if ((pinlist->info[i].icontype &mask)!=0) {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) return i;
      }
    }
  }

  return -1;
}

static int pin_get_selected_unlocked(int mask)
{
  // returns first selection
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i]) && !pin_is_locked(&pinlist->info[i])) {
      if ((pinlist->info[i].icontype &mask)!=0) {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) return i;
      }
    }
  }

  return -1;
}

int pin_get_selected_nodir(int mask)
{
  // returns first selection
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      if ((pinlist->info[i].icontype &mask)!=0) {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
          char *spr=indirected(pinlist->info[i].icondata.sprite,char *);
          if (strncmp(spr,"file_",5)==0) return i;
        }
      }
    }
  }

  return -1;
}
*/
static int pin_count(int selected,int max,int mask)
{
  int i,count=0;

  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      if (pinlist->info[i].icontype & mask) {
        if (selected) {
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            count++;
          }
        } else {
          count++;
        }
        if (count==max) return max;
      }
    }
  }
  return count;
}

static int pin_count_unlocked(int selected,int max,int mask)
{
  int i,count=0;

  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i]) && !pin_is_locked(&pinlist->info[i])) {
      if (pinlist->info[i].icontype & mask) {
        if (selected) {
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            count++;
          }
        } else {
          count++;
        }
        if (count==max) return max;
      }
    }
  }
  return count;
}

int pin_count_nodir(int selected,int max,int mask)
{
  int i,count=0;

  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      if (pinlist->info[i].icontype & mask) {
        // exclude folders/applications
        char *spr=indirected(pinlist->info[i].icondata.sprite,char *);
        if (strncmp(spr,"file_",5)==0) {
          if (selected) {
            if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
              count++;
            }
          } else {
            count++;
          }
          if (count==max) return max;
        }
      }
    }
  }
  return count;
}

void backdrop_redraw_iconarea(_backdrop_state *state,BBox *area)
{
  /* redraw screen area described by icon bounding box (ie screen coords, not window coords) */
  WimpRedrawWindowBlock block;
  WimpUpdateWindowBlock *up=(WimpUpdateWindowBlock *)&block;
  int more;

  up->update_area.xmin=area->xmin-4;
  up->update_area.xmax=area->xmax+4;
  up->update_area.ymin=area->ymin-state->window_height-iconbar_height-4;
  up->update_area.ymax=area->ymax-state->window_height-iconbar_height+4;

  window_get_wimp_handle(0,state->obj,&block.window_handle);

  wimp_update_window((WimpRedrawWindowBlock *)&block,&more);

  backdrop_redraw_loop(state,&block,more);
}

void pin_redraw(_backdrop_state *state,int icon)
{
  backdrop_redraw_iconarea(state,&pinlist->info[icon].icondata.bbox);
}

void pin_remove_empties(_backdrop_state *state)
{
  // tidy up any empty pins.  Indirected store should already have been freed.
  int i;
  for (i=pinlist->count-1;i>=0;i--) {
    if (pinlist->info[i].icontype==pin_type_empty) {
      // remove from list
      flex_midextend((flex_ptr)&pinlist,(char *)&pinlist->info[i+1]-(char *)pinlist,-(int)sizeof(pin));
      pinlist->count--;
      outline_clear_cache(state);
    }
  }
}

void pin_delete(_backdrop_state *state,int i)
{
  // delete a pin; free up most bits, but not the pin itself.
  if (pin_is_tiny(&pinlist->info[i])) {
    if ((pinlist->info[i].icontype & 0xff)== pin_type_tinydir) {
      // empty tinydir, just remove
      toolbox_delete_object(0,pinlist->info[i].obj);
      pinlist->info[i].icontype=pin_type_empty;
      return;
    }  
  
    // if we have 1+ file icons, just do a delete  
    if (tinydir_count(0,2)>1) {
      toolbox_delete_object(0,pinlist->info[i].obj);
      indirected_remove(pinlist->info[i].data.filename_offset);
      pinlist->info[i].data.filename_offset=INDIRECT_FREEI;
      pinlist->info[i].icontype=pin_type_empty;
    } else {
      // repurpose as an empty icon
      indirected_remove(pinlist->info[i].data.filename_offset);
      pinlist->info[i].data.filename_offset=INDIRECT_FREEI;
      pinlist->info[i].icontype=pin_type_tinydir;
      iconbar_set_text(0,pinlist->info[i].obj,"");
      iconbar_set_sprite(0,pinlist->info[i].obj,"directory");
      iconbar_set_help_message(0,pinlist->info[i].obj,msgtrans_lookup("IcT"));
    }
  } else {
    if (pin_is_sticky(&pinlist->info[i])) {
//      toolbox_delete_object(0,pinlist->info[i].data.sticky.furniture);
      window_remove_gadget(0,state->obj,pinlist->info[i].data.sticky.textarea);
      pinlist->info[i].icontype=pin_type_empty;
    } else {
      if (pin_is_anyfile(&pinlist->info[i])) {
        // free store
        pin_free_store(i);
        pinlist->info[i].icontype=pin_type_empty;
        pin_redraw(state,i);
      }
    }
  }
}

void pin_remove_window(_backdrop_state *state,int window)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anywin(&pinlist->info[i])) {
      if (pinlist->info[i].data.window.handle==window) {
        if (!pin_is_tinywin(&pinlist->info[i])) {
          pin_free_store(i);
          pinlist->info[i].icontype=pin_type_empty;
          pin_redraw(state,i);
        } else {
          toolbox_delete_object(0,pinlist->info[i].obj);
          pinlist->info[i].icontype=pin_type_empty;
        }
        pin_remove_empties(state);
        return; // a window only ever happens once
      }
    }
  }
}

// restore all iconized windows (eg on task exit)
void pin_restore_all_windows(_backdrop_state *state)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anywin(&pinlist->info[i])) {
      iconize_restore(pinlist->info[i].data.window.handle);
      if (!pin_is_tinywin(&pinlist->info[i])) {
        pinlist->info[i].icontype=pin_type_empty;
        pin_redraw(state,i);
      } else {
        pinlist->info[i].icontype=pin_type_empty;  
        toolbox_delete_object(0,pinlist->info[i].obj);
      }
    }
  }

  pin_remove_empties(state);
}

void pin_remove_all_task(_backdrop_state *state,int task_handle)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anywin(&pinlist->info[i])) {
      if (pinlist->info[i].data.window.task_handle==task_handle) {
        if (!pin_is_tinywin(&pinlist->info[i])) {
          pinlist->info[i].icontype=pin_type_empty;
          pin_free_store(i);
          pin_redraw(state,i);
        } else {
          pinlist->info[i].icontype=pin_type_empty;
          toolbox_delete_object(0,pinlist->info[i].obj);
        }
      }
    }
  }

  pin_remove_empties(state);
}

// return pin index or -1 if not found
int pin_find_filename(char *filename,int tiny)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (tiny==0 || (tiny==1 && pin_is_tiny(&pinlist->info[i]))) {
      if (pin_is_anyfile(&pinlist->info[i])) {
        if (strcasecmp(indirected(pinlist->info[i].data.filename_offset,char *),filename)==0) return i;
      }
    }
  }

  return -1;
}

int pin_find_object(ObjectId id)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_tiny(&pinlist->info[i])) {
      if (pinlist->info[i].obj==id) return i;
    }
  }

  return -1;
}

static int pin_set_sprite(_backdrop_state *state,int filetype,char **leafname,pin *source,int alloc)
{
  // update the pin's sprite
  char *spritename;
  IGNORE(state);
  
  if (alloc) {
    if ((source->icondata.sprite=indirected_add(NULL,13))==INDIRECT_FREEI) {
      return 0;
    }
  }

  spritename=indirected(source->icondata.sprite,char *);
  
  //  char validation[32];
  //  sprintf(validation,"C%06x/%06x;S",state->fg_colour>>8,state->bd_colour>>8);
  //  spritename=validation+strlen(validation);
  
  // determine sprite name
  switch (filetype) {
  case -1:
    strcpy(spritename,"file_lxa");
    break;
    
  case -2:
    strcpy(spritename,*leafname);
    break;
    
  case 0x1000:
    strcpy(spritename,"directory");
    break;

  case 0x2000:
    if (wimp_sprite_exists(*leafname)) {
      if (strlen(*leafname)>11) {
        memcpy(spritename,*leafname,12);
        spritename[12]=0;
      } else {
        strcpy(spritename,*leafname);
      }
    } else {
      strcpy(spritename,"application");
    }
    break;
    
  default:
    sprintf(spritename,"file_%03x",filetype);
    if (!wimp_sprite_exists(spritename)) {
      // use a placeholder for now
      strcpy(spritename,"file_xxx");
    }
    break;
  }

  return 1;
}

static void pin_set_bbox(_backdrop_state *state,pin *p,int textwidth,int x,int y,pin_add_coord_type add_type)
{
  // work out the bbox for a pin being added at (x,y) with an add_type
  // textwidth is passed to us precalculated

  switch (state->iconsize) {
  case ICONSIZE_NORMAL:
    if (textwidth<p->sprite_x) textwidth=p->sprite_x;

    if (add_type==PIN_ADD_CENTRED) {
      p->icondata.bbox.xmin=x-textwidth/2;
      p->icondata.bbox.xmax=x+textwidth/2;
      p->icondata.bbox.ymin=y-(p->sprite_y+4+ICON_TEXT_HEIGHT)/2;
      p->icondata.bbox.ymax=y+(p->sprite_y+4+ICON_TEXT_HEIGHT)/2;
    } else {
      p->icondata.bbox.xmin=x;
      p->icondata.bbox.xmax=x+textwidth;
      p->icondata.bbox.ymin=y;
      p->icondata.bbox.ymax=y+p->sprite_y+4+ICON_TEXT_HEIGHT;
    }
    break;

  case ICONSIZE_SMALL:
    {
      int height;

      textwidth=textwidth+8+p->sprite_x/2;
      if (p->sprite_y/2>ICON_TEXT_HEIGHT) height=p->sprite_y/2; else height=ICON_TEXT_HEIGHT;

      if (add_type==PIN_ADD_CENTRED) {
        p->icondata.bbox.xmin=x-textwidth/2;
        p->icondata.bbox.xmax=x+textwidth/2;
        p->icondata.bbox.ymin=y-height/2;
        p->icondata.bbox.ymax=y+height/2;
      } else {
        p->icondata.bbox.xmin=x;
        p->icondata.bbox.xmax=x+textwidth;
        p->icondata.bbox.ymin=y;
        p->icondata.bbox.ymax=y+height;
      }
    }
    break;

  case ICONSIZE_DOUBLE:
    if (textwidth<p->sprite_x*2) textwidth=p->sprite_x*2;

    if (add_type==PIN_ADD_CENTRED) {
      p->icondata.bbox.xmin=x-textwidth/2;
      p->icondata.bbox.xmax=x+textwidth/2;
      p->icondata.bbox.ymin=y-(p->sprite_y*2+4+ICON_TEXT_HEIGHT)/2;
      p->icondata.bbox.ymax=y+(p->sprite_y*2+4+ICON_TEXT_HEIGHT)/2;
    } else {
      p->icondata.bbox.xmin=x;
      p->icondata.bbox.xmax=x+textwidth;
      p->icondata.bbox.ymin=y;
      p->icondata.bbox.ymax=y+p->sprite_y*2+4+ICON_TEXT_HEIGHT;
    }
    break;
  }

}

int backdrop_add_pin(_backdrop_state *state,int pin_type,int x,int y,pin_add_coord_type add_type,int filetype,char **leafname,char **text,char **path,int window,int task)
{
  pin newpin;
  char *truncated;
  int offset;
  int textwidth;
  int no_snap=(pin_type & pin_nosnap)!=0;
  pin_type &= ~pin_nosnap;
 
  // see if we need to truncate the text
  truncate(text,&truncated);
  memset(&newpin,0,sizeof(pin));
  newpin.icondata.buffer=INDIRECT_FREEI;
  newpin.icondata.sprite=INDIRECT_FREEI;
  newpin.data.filename_offset=INDIRECT_FREEI;
  
  if ((pin_type & pin_type_window)!=0) {
    pin_set_sprite(state,-2,leafname,&newpin,1);
  } else {
    pin_set_sprite(state,filetype,leafname,&newpin,1);
  }
  pin_set_spritesize(&newpin,indirected(newpin.icondata.sprite,char *));
  
  // work out the text width for the icon
  _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,truncated,0,&textwidth);
  textwidth+=8; // allow for outlines
//  if (textwidth<newpin.sprite_x) textwidth=newpin.sprite_x;

  // does the file already exist...?
  if ((pin_type & (pin_type_fsfile | pin_type_localfile))!=0) {
    int p=pin_find_filename(*path,0);
    if (p!=-1) {
      // tinydir?
      if (pin_is_tiny(&pinlist->info[p])) {
        // move to the pinboard instead
        // delete the tinydir (but leave the pin details)
        
        // move to pinboard
        pinlist->info[p].sprite_x=newpin.sprite_x;
        pinlist->info[p].sprite_y=newpin.sprite_y;
        pin_set_bbox(state,&pinlist->info[p],textwidth,x,y,add_type);

        if (!no_snap) grid_snap(state,&pinlist->info[p].icondata.bbox);
        
        // fix up the icon data
        pinlist->info[p].icondata.flags=0;

        pinlist->info[p].icondata.buffer=indirected_add_fromflex((flex_ptr)&truncated,1+strlen(truncated));
        flex_free((flex_ptr)&truncated);
        if (pinlist->info[p].icondata.buffer==INDIRECT_FREEI) {
          indirected_remove(pinlist->info[p].icondata.sprite);
          pinlist->info[p].icondata.sprite=INDIRECT_FREEI;
          return 0;
        }
        
        tinydir_remove(state,p,0);
        pinlist->info[p].icontype&=~pin_type_tinydir;
      } else {
        BBox old_coords;

        flex_free((flex_ptr)&truncated);
        // update validation string
        strcpy(indirected(pinlist->info[p].icondata.sprite,char *),indirected(newpin.icondata.sprite,char *));
        indirected_remove(newpin.icondata.sprite);
        
        old_coords=pinlist->info[p].icondata.bbox; // set redraw to current coords
      
        // move it...
        pinlist->info[p].sprite_x=newpin.sprite_x;
        pinlist->info[p].sprite_y=newpin.sprite_y;
        pin_set_bbox(state,&pinlist->info[p],textwidth,x,y,add_type);

        if (!no_snap); grid_snap(state,&pinlist->info[p].icondata.bbox);
        
        backdrop_redraw_iconarea(state,&old_coords); // redraw old location
      }
      backdrop_redraw_iconarea(state,&pinlist->info[p].icondata.bbox); // set redraw to new coords
   
      return 1;
    }
  }
  
  // max width 256osu;
  // sprite is 68x68osu;
  // text height is 44osu

  newpin.icontype=pin_type;
  pin_set_bbox(state,&newpin,textwidth,x,y,add_type);
  newpin.icondata.flags=0;
  if (!no_snap) grid_snap(state,&newpin.icondata.bbox);

  offset=indirected_add_fromflex((flex_ptr)&truncated,1+strlen(truncated));
  flex_free((flex_ptr)&truncated);
  if (offset==INDIRECT_FREEI) {
    pin_free_store_static(&newpin);
    return 0;
  }
  newpin.icondata.buffer=offset;

  if (!pin_is_anywin(&newpin)) {
    if ((offset=indirected_add_fromflex((flex_ptr)path,1+strlen(*path)))==INDIRECT_FREEI) {
      pin_free_store_static(&newpin);
      return 0;
    }
    newpin.data.filename_offset=offset;
  }

  if (pin_is_anywin(&newpin)) {
    newpin.data.window.handle=window;
    newpin.data.window.task_handle=task;
  }
  
  if (pin_add(&newpin)==-1) {
    pin_free_store_static(&newpin);
    return 0;
  }

  backdrop_redraw_iconarea(state,&newpin.icondata.bbox);

  return 1; // all ok
}

static _kernel_oserror *backdrop_import(_backdrop_state *state,int filetype,int length,char *pathname)
{
  _kernel_oserror *e;
  int w,h;

  if (state->colourtrans) {
    flex_free((flex_ptr)&state->colourtrans);
    state->colourtrans=NULL;
  }

  switch (filetype) {
  case (0x1000 | 0xff9): // jpeg cached as sprite, internal id
  case 0xff9:
    {
      int *area=(int *)state->bd_image;
      int offset,mode,ctsize;
      if ((filetype & 0x1000) ==0) {
        // if cached jpeg, the area has already been sorted out for us
        *area=length;
        *(area+1)=0;
        *(area+2)=16;
        *(area+3)=16;
        
        if ((e=_swix(OS_SpriteOp,_INR(0,2),10+256,area,pathname!=(char *)-1?pathname:state->bd_filename))!=NULL) return e;
      }
      
      offset=*(area+2); // we always use the first sprite
      if ((e=_swix(OS_SpriteOp,_INR(0,2)|_OUT(3)|_OUT(4)|_OUT(6),40+512,area,offset+state->bd_image,&w,&h,&mode))!=NULL) return e; // get size
      state->image_width=w;//<<bbc_modevar(mode,bbc_XEigFactor);
      state->image_height=h;//<<bbc_modevar(mode,bbc_YEigFactor);
      state->bd_flags=(state->bd_flags & ~BD_IMAGE_MASK) | BD_IMAGE_SPRITE;
      if (filetype & 0x1000) {
        state->bd_flags|=BD_IMAGE_CACHE; // mark as a cached/converted file
      }
      
      // let's sort the colours out
      e=_swix(ColourTrans_GenerateTable,_INR(0,7)|_OUT(4),area,offset+state->bd_image,-1,-1,0,3|(1<<4),0,0,&ctsize);
      if (e) return e;

      if (ctsize) {
        if (!flex_alloc((flex_ptr)&state->colourtrans,ctsize)) {
          return msgtrans_errorlookup("BDCTMem");
        }
        e=_swix(ColourTrans_GenerateTable,_INR(0,7),area,offset+state->bd_image,-1,-1,state->colourtrans,1|(1<<4),0,0);
        if (e) {
          flex_free((flex_ptr)&state->colourtrans);
          state->colourtrans=NULL;
          return e;
        }
      } else {
        if (state->colourtrans) {
          flex_free((flex_ptr)&state->colourtrans);
          state->colourtrans=NULL;
        }
      }
    }
    break;
    
  case 0xc85:
    if ((e=_swix(OS_File,_INR(0,3),16,pathname!=(char *)-1?pathname:state->bd_filename,state->bd_image,0))!=NULL) {
      return e;
    }
    
    _swix(JPEG_Info,_INR(0,2)|_OUT(2)|_OUT(3),1,state->bd_image,length,&w,&h);
    state->image_width=w;//<<bbc_modevar(-1,bbc_XEigFactor);
    state->image_height=h;//<<bbc_modevar(-1,bbc_YEigFactor);
    state->bd_flags=(state->bd_flags &~ BD_IMAGE_MASK) | BD_IMAGE_JPEG;
    break;
  }
  
  return NULL;
}

static int backdrop_cache_jpeg(_backdrop_state *state,char *pathname,unsigned int *length)
{
  // load jpeg file and convert into a sprite for the current mode
  // return 1 if OK, 0 if not (having reported an error)
  // return length of new sprite area in *length
  // return *state set up for the sprite area

  _kernel_oserror *e;
  char *jpegdata=NULL;
  int jpeglength;
  int type;
  int w,h,xdpi,ydpi;
  int *spritearea=NULL,areasize;
  int *save_area=NULL,save_size;
  int v;
  _kernel_swi_regs sa_r;

  // clear anything already present
  flex_extend((flex_ptr)&state->bd_image,0);
  state->image_length=0;
  
  // load JPEG into memory
  e=_swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(4),5,pathname!=(char *)-1?pathname:state->bd_filename,&type,&jpeglength);
  if (type!=1) {
    msgtrans_report_error("BDNF");
    goto abort;
  }
  
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    goto abort;
  }

  if (!flex_alloc((flex_ptr)&jpegdata,jpeglength)) {
    msgtrans_report_error("BDNomem");
    goto abort;
  }

  e=_swix(OS_File,_INR(0,3),255,pathname!=(char *)-1?pathname:state->bd_filename,jpegdata,0);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    goto abort;
  }
  
  // determine dimensions of the image
  e=_swix(JPEG_Info,_INR(0,2)|_OUTR(2,5),1,jpegdata,jpeglength,&w,&h,&xdpi,&ydpi);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    goto abort;
  }    

  // create a suitably sized sprite area
  v=1<<bbc_modevar(-1,9);
  if (v==15) v=16; // as 15bpp images take 16bpp in storage
  areasize=16 + 44 + (((w*v/8)+3)&~3) * h;
  if (!flex_extend((flex_ptr)&state->bd_image,areasize)) {
    msgtrans_report_error("BDNomem");
    goto abort;
  }

  spritearea=(int *)state->bd_image;
  
  // create a matching sprite based on the jpeg detail and current mode
  spritearea[0]=areasize;
  spritearea[1]=0;
  spritearea[2]=16;
  spritearea[3]=16;

  _swix(OS_Byte,_IN(0)|_OUT(2),135,&v);
  _swix(OS_SpriteOp,_INR(0,6),256+15,spritearea,"backdrop",0,w,h,v);
  
  // decompress JPEG into sprite...
  // switch output to sprite
  e=_swix(OS_SpriteOp,_INR(0,2)|_OUT(3),62+256,spritearea,"backdrop",&save_size);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    goto abort;
  }

  if (!flex_alloc((flex_ptr)&save_area,save_size)) {
    msgtrans_report_error("BDNomem");
    goto abort;
  }

  *save_area=0;
  sa_r.r[0]=256+60;
  sa_r.r[1]=(int)spritearea;
  sa_r.r[2]=(int)"backdrop";
  sa_r.r[3]=(int)save_area;
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    goto abort;
  }
  
  // render JPEG to sprite
  e=_swix(JPEG_PlotScaled,_INR(0,5),jpegdata,0,0,NULL,jpeglength,3);
  
  // switch back
  _kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    goto abort;
  }

  // tidy up
  flex_free((flex_ptr)&save_area);
  flex_free((flex_ptr)&jpegdata);
  state->image_length=areasize;
  *length=areasize;
  
  return 1;

 abort:
  flex_extend((flex_ptr)&state->bd_image,0);
  state->image_length=0;
  if (save_area) {
    flex_free((flex_ptr)&save_area);
    save_area=NULL;
  }
  if (jpegdata) {
    flex_free((flex_ptr)&jpegdata);
    jpegdata=NULL;
  }
  
  return 0;
}

static int backdrop_perform_load(_backdrop_state *state,int orientation,char *pathname,int cache_jpeg,int *store_filename)
{
  // returns 0 if fail, 1 if success
  _kernel_oserror *e;
  unsigned int length;
  int filetype;

  // check it's a sprite or JPEG file first
  if ((e=_swix(OS_File,_INR(0,1)|_OUT(4)|_OUT(6),23,pathname!=(char *)-1?pathname:state->bd_filename,&length,&filetype))!=NULL) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    return 0;
  }      

  switch (filetype) {
  case 0xfff:
    // text file - load a carousel if valid
    if ((e=carousel_load(pathname))!=NULL) {
      wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);          
      return 0;
    }

    carousel_next(state);

    // we don't want to store the carousel filename - carousel_next will have sorted it
    *store_filename=0;
    return 1;

  case 0xff9:
    length+=4; // sprite files need extra header word
  case 0xc85:
    if (filetype==0xc85 && cache_jpeg) {
      // see if there's room...
      if (backdrop_cache_jpeg(state,pathname,&length)) {
        filetype=0x1000 | 0xff9;
      } else {
        return 0;
      }
    } else {
      if (!flex_extend((flex_ptr)&state->bd_image,length)) {
        msgtrans_report_error("BDNomem");
        return 0;
      }
    }

    if ((e=backdrop_import(state,filetype,length,pathname))!=NULL) {
      wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
      return 0;
    } else {
      state->bd_flags=(state->bd_flags &~BD_PAINT_MASK) | orientation;  
      state->image_length=length;
      return 1;
    }
    break;

  default:
    {
      // non-native filetype, see if we can convert this
      char path[strlen(pathname!=(char *)-1?pathname:state->bd_filename)+80];
      char modenumber[12];
      int type,bpp;
      _swix(OS_File,_INR(0,1),6,"<Wimp$ScrapDir>.PinboardBD");
      
      // generate a mode number
      bpp=1<<bbc_modevar(-1,9);
      if (bpp>8) {
        int xf=1<<bbc_modevar(-1,4);
        int yf=1<<bbc_modevar(-1,5);
        if (xf==0) xf=1;
        if (yf==0) yf=1;
        sprintf(modenumber,"S%d,%d,%d",bpp==32?32:16,180/xf,180/yf);
      } else {
        // get current mode settings
        int mode;
        _swix(OS_Byte,_IN(0)|_OUT(2),135,&mode);
        sprintf(modenumber,"%d",mode);
      }

      check_cfsi();
      
      sprintf(path,"<ChangeFSI$Dir>.ChangeFSI %s <Wimp$ScrapDir>.PinboardBD %s -nomode",pathname!=(char *)-1?pathname:state->bd_filename,modenumber);
      wimp_start_task(path,NULL);
      _swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(4),5,"<Wimp$ScrapDir>.PinboardBD",&type,&length);
      if (type==1) {
        type=backdrop_load_image(state,orientation,"<Wimp$ScrapDir>.PinboardBD",0,cache_jpeg);
        state->bd_flags |= BD_IMAGE_CACHE;
        _swix(OS_File,_INR(0,1),6,"<Wimp$ScrapDir>.PinboardBD");
        
        return type; // in case the reload fails
      } else {
        msgtrans_report_error("BDBadImg");
        return 0;
      }
      break;
    }
  }

  return 0;

}

int backdrop_load_image(_backdrop_state *state,int orientation,char *pathname,int store_filename,int cache_jpeg)
{
  _kernel_oserror *e;
  int more, success;
  WimpRedrawWindowBlock redraw;

  // install a new image; if not possible, reverts to a plain colour
  if (pathname) {
    success=backdrop_perform_load(state,orientation,pathname,cache_jpeg,&store_filename);
    if (success) {

        if (store_filename && pathname!=(char *)-1) {
          if (state->bd_filename) {
            flex_free((flex_ptr)&state->bd_filename);
            state->bd_filename=NULL;
          }

          if (flex_alloc((flex_ptr)&state->bd_filename,strlen(pathname)+1)) {
            strcpy(state->bd_filename,pathname);
          } else {
            state->bd_filename=NULL;
          }
        }
    } else {
      // failed to load for some reason
        state->bd_flags=(state->bd_flags &~BD_IMAGE_MASK) | BD_IMAGE_PLAIN;
        flex_extend((flex_ptr)&state->bd_image,0);
        state->image_length=0;

      if (store_filename) {
          if (state->bd_filename) {
            flex_free((flex_ptr)&state->bd_filename);
            state->bd_filename=NULL;
          }
      }
    }
    
  } else {
    // remove backdrop
    if (state->image_length) {
      flex_extend((flex_ptr)&state->bd_image,0);
      state->image_length=0;
      state->bd_flags=(state->bd_flags & ~BD_IMAGE_MASK) | BD_IMAGE_PLAIN;
    }
    if (store_filename) {
      if (state->bd_filename) {
        flex_free((flex_ptr)&state->bd_filename);
        state->bd_filename=NULL;
      }
    }
    success=1;
  }

  window_get_wimp_handle(0,state->obj,&redraw.window_handle);
//  wimp_get_window_state(&win);
  ((WimpUpdateWindowBlock *)&redraw)->update_area.xmin=0;
  ((WimpUpdateWindowBlock *)&redraw)->update_area.xmax=state->window_width;
  ((WimpUpdateWindowBlock *)&redraw)->update_area.ymax=0;
  ((WimpUpdateWindowBlock *)&redraw)->update_area.ymin=-state->window_height;
  e=wimp_update_window(&redraw,&more);
  if (e==NULL && more) backdrop_redraw_loop(state,&redraw,more);
  
  return 1;
}



static int backdrop_redraw(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  int more;
  WimpRedrawWindowBlock block;
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(id);
  
  block.window_handle=event->redraw_window_request.window_handle;
  
  wimp_redraw_window(&block,&more);
  backdrop_redraw_loop(state,&block,more);
  
  return 1;
}

static int backdrop_drop_pin(WimpMessage *message,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  char *leaf_name=NULL,*canon_name=NULL;
  int base_size,x_offset=0,y_offset=0,win;
  unsigned int old_mask;
  int grid_width,grid_height;
  grid_size(state,&grid_width,&grid_height);

  if (message->data.data_load.destination_window==-2) {
    // going to an icon, so add as tinydir
    tinydir_add(state,message->data.data_load.leaf_name,message->data.data_load.destination_icon);
    return 1;
  }

  window_get_wimp_handle(0,state->obj,&win);
  if (sticky_find_from_handle(message->data.data_load.destination_window)!=-1) {
    sticky_dataload(message,state);
    return 1;
  }

  // canonicalise the name
  if (!fs_canon(message->data.data_load.leaf_name,&canon_name)) return 0; // failed
  
  if (!fs_leafname_from_path(&leaf_name,canon_name)) {
    flex_free((flex_ptr)&canon_name);
    return 0;
  }
 
  // is it a Filer-type message with a column and row?
 
  base_size=(((char *)&message->data.data_load.leaf_name-(char *)message)+strlen(message->data.data_load.leaf_name)+1)+3&~3;
  if (base_size!=message->hdr.size) {
    // possibly...
    if (base_size<248) {
      int new_col,new_row;
      new_col=*((int *)message+base_size/4);
      new_row=*((int *)message+base_size/4+1);
      if (filer_base_column==-1) {
        filer_base_column=new_col;
        filer_base_row=new_row;
      } else {
        x_offset=(new_col-filer_base_column)*grid_width;
        y_offset=(filer_base_row-new_row)*grid_height;
      }
    }
  }
  
  // add the pin
  backdrop_add_pin(state,
                   strncmp(canon_name,"Pinboard:",9)==0?pin_type_localfile:pin_type_fsfile,
                   message->data.data_load.destination_x+x_offset,
                   message->data.data_load.destination_y+y_offset,
                   state->iconsize==ICONSIZE_SMALL ? PIN_ADD_BOTTOM_LEFT:PIN_ADD_CENTRED,
                   message->data.data_load.file_type,
                   &leaf_name,
                   &leaf_name,
                   &canon_name,
                   0,0);

  flex_free((flex_ptr)&canon_name);
  flex_free((flex_ptr)&leaf_name);
  
  message->hdr.size=20;
  message->hdr.your_ref=message->hdr.my_ref;
  message->hdr.action_code=Wimp_MDataLoadAck;
  wimp_send_message(17,message,message->hdr.sender,0,0);

  null_events_enable(); // catch next null to reset the insert point
  return 1;
}

void backdrop_add_pin_command(_backdrop_state *state,char *path,int x,int y,int locked)
{
  char *leaf_name=NULL;
  int filetype,objtype;
  _kernel_oserror *e;
  
  // already canonicalised
  e=_swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(6),23,path,&objtype,&filetype);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    return;
  }
  if (objtype==0) {
    msgtrans_report_error("PinNF");
    return;
  }

  if (filetype==0x2000) {
    fs_boot_application(path);
  }
  
  // extract the leafname
  if (!fs_leafname_from_path(&leaf_name,path)) return;

  // add the pin
  backdrop_add_pin(state,
                   ((strncmp(path,"Pinboard:",9)==0?pin_type_localfile:pin_type_fsfile) | (locked*pin_locked)) | pin_nosnap,
                   x,
                   y,
                   PIN_ADD_BOTTOM_LEFT, // to match previous Pinboard incarnation
                   filetype,
                   &leaf_name,
                   &leaf_name,
                   &path,
                   0,0);

  flex_free((flex_ptr)&leaf_name);
  
}

void backdrop_add_xpin_command(_backdrop_state *state,char *path,int x,int y,int locked)
{
  // add a pin which may not refer to an accessible entity
  char *leaf_name=NULL;
  char *canon_name=NULL;
  int type;

  // try to canonicalise the path, but don't moan if we can't
  if (!fs_canon_nomoan(path,&canon_name)) {
    return;
  }

  // look up file details, in case
  if (_swix(OS_File,_INR(0,1)|_OUT(0),5,canon_name,&type)==NULL) {
    if (type!=0) {
      // it works!  So add a conventional pin
      char newpath[1+strlen(canon_name)];
      strcpy(newpath,canon_name);
      flex_free((flex_ptr)&canon_name);
      backdrop_add_pin_command(state,newpath,x,y,locked);
      return;
    }
  }

  // local files must always be valid
  if (strncmp(path,"Pinboard:",9)==0) {
    flex_free((flex_ptr)&canon_name);
    return;
  }

  // extract the leafname
  if (!fs_leafname_from_path(&leaf_name,canon_name)) {
    flex_free((flex_ptr)&canon_name);
    return;
  }

  // add the pin
  backdrop_add_pin(state,
                   ((strncmp(path,"Pinboard:",9)==0?pin_type_localfile|pin_type_recheck:pin_type_fsfile|pin_type_recheck) | (locked*pin_locked)) | pin_nosnap,
                   x,
                   y,
                   PIN_ADD_BOTTOM_LEFT, // to match previous Pinboard incarnation
                   0x000,
                   &leaf_name,
                   &leaf_name,
                   &canon_name,
                   0,0);

  flex_free((flex_ptr)&leaf_name);
  flex_free((flex_ptr)&canon_name);
}

static void backdrop_select_all(_backdrop_state *state,int mask)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      if (pinlist->info[i].icontype & mask) {
        if (!(pinlist->info[i].icondata.flags & WimpIcon_Selected)) {
          if (pin_is_sticky(&pinlist->info[i])) {
//            sticky_set_state(state,i,1);
          } else {
            pinlist->info[i].icondata.flags |= WimpIcon_Selected;
            pin_redraw(state,i);
          }
        }
      }
    }
  }
}

void backdrop_select_icon(_backdrop_state *state,int icon,bd_icon_action action)
{
  int i;
  
  if (action==BD_SELECT_CLEAR_ALL) {
    if (icon!=-1) {
      if (pinlist->info[icon].icondata.flags & WimpIcon_Selected) return;
    }
    for (i=0;i<pinlist->count;i++) {
      if (i!=icon) {
        if (!pin_is_tiny(&pinlist->info[i])) {
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            if (pin_is_sticky(&pinlist->info[i])) {
//              sticky_set_state(state,i,0);
            } else {
              pinlist->info[i].icondata.flags&=~WimpIcon_Selected;
              pin_redraw(state,i);
            }
          }
        }
      }
    }
  }

  if (icon==-1) return;

  switch (action) {
  case BD_SELECT_CLEAR_ALL:
    if (!(pinlist->info[icon].icondata.flags & WimpIcon_Selected)) {
      if (pin_is_sticky(&pinlist->info[icon])) {
//        sticky_set_state(state,icon,1);
      } else {
        pinlist->info[icon].icondata.flags|=WimpIcon_Selected;
        pin_redraw(state,icon);
      }
    }
    break;
    
  case BD_SELECT_TOGGLE:
    pinlist->info[icon].icondata.flags^=WimpIcon_Selected;
    pin_redraw(state,icon);
    break;
  }
}

static int backdrop_drag_progress(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;

  // update selection state
  BBox current;
  WimpGetPointerInfoBlock mouse;
  WimpGetWindowStateBlock win;
  int i;
  IGNORE(event_code);
  IGNORE(id);
  
  wimp_get_pointer_info(&mouse);

  // make work area coords
  win.window_handle=event->mouse_click.window_handle;
  wimp_get_window_state(&win);
/*  mouse.x+=win.xscroll-win.visible_area.xmin;
  mouse.y+=win.yscroll-win.visible_area.ymax;*/
  
  if (mouse.x>state->drag_x) {
    current.xmin=state->drag_x;
    current.xmax=mouse.x;
  } else {
    current.xmin=mouse.x;
    current.xmax=state->drag_x;
  }

  if (mouse.y>state->drag_y) {
    current.ymin=state->drag_y;
    current.ymax=mouse.y;
  } else {
    current.ymin=mouse.y;
    current.ymax=state->drag_y;
  }

  // check each icon for intersection
  for (i=0;i<pinlist->count;i++) {
    if (pin_intersect(&current,&pinlist->info[i],state->iconsize)) {
      // this one is to be selected
      if (!(pinlist->info[i].icondata.flags & WimpIcon_Selected)) {
        if (pin_is_sticky(&pinlist->info[i])) {
//          sticky_set_state(state,i,1);
        } else {
          pinlist->info[i].icondata.flags|=WimpIcon_Selected;
          pin_redraw(state,i);
        }
      }
    } else {
      // this one is to be deselected, or returned to original state (adjust drag)
      if (state->predrag_state) {
        if (state->predrag_state[i]) {
          // do nothing as already selected, don't clear
        } else {
          // clear selection
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            if (pin_is_sticky(&pinlist->info[i])) {
//              sticky_set_state(state,i,0);
            } else {
              pinlist->info[i].icondata.flags&=~WimpIcon_Selected;
              pin_redraw(state,i);
            }
          }
        }
      } else {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
          if (pin_is_sticky(&pinlist->info[i])) {
//            sticky_set_state(state,i,0);
          } else {
            pinlist->info[i].icondata.flags&=~WimpIcon_Selected;
            pin_redraw(state,i);
          }
        }
      }
    }
  }

  return 0; // allow other null event handlers
}

static int backdrop_select_drag_end(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  unsigned int mask;
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(id);
  
  event_deregister_wimp_handler(-1,Wimp_EUserDrag,backdrop_select_drag_end,state);
  event_deregister_wimp_handler(-1,Wimp_ENull,backdrop_drag_progress,state);

  null_events_disable();
  dragging_selection=0;

  if (state->predrag_state) {
    flex_free((flex_ptr)&state->predrag_state);
    state->predrag_state=NULL;
  }
  return 1;
}


static void pin_remove_pins(_backdrop_state *state,int min,int max)
{
  int i;
  for (i=min;i<max;i++) {
    if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
      if (!pin_is_tiny(&pinlist->info[i]) && pin_is_fsfile(&pinlist->info[i])) {
        pin_free_store(i);
          
        pinlist->info[i].icontype=pin_type_empty;
          
        pin_redraw(state,i);
      }
    }
  }
  pin_remove_empties(state);
}

void pin_remove_stickies(_backdrop_state *state,int min,int max,int delete_all)
{
  int i;
  for (i=min;i<max;i++) {
    if (pinlist->info[i].icondata.flags & WimpIcon_Selected || delete_all) {
      if (pin_is_sticky(&pinlist->info[i])) {
        sticky_delete(state,i,0);
        pin_free_store(i);
          
        pinlist->info[i].icontype=pin_type_empty;
          
        pin_redraw(state,i);
      }
    }
  }
  pin_remove_empties(state);
}

static int backdrop_click(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  int icon;
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(id);

  // is the settype window open?
  if (settype_showing!=0) {
    toolbox_hide_object(0,settype_showing);
  }

  // see if we clicked on an icon or not...
  icon=pin_check_click(event->mouse_click.mouse_x,event->mouse_click.mouse_y,state->iconsize);
  
  if (icon!=-1) {
    // is over an icon
    // do we need to check the information for this icon?
    if (pin_needs_recheck(&pinlist->info[icon])) {
      pin_recheck(state,icon);
    }

    switch (event->mouse_click.buttons) {
    case Wimp_MouseButtonMenu:
      if (akbd_pollsh() && pin_is_window(&pinlist->info[icon])) {
        // open menu for task
        WimpMouseClickEvent e;
        e.mouse_x=event->mouse_click.mouse_x;
        e.mouse_y=event->mouse_click.mouse_y;
        e.buttons=2;
        e.window_handle=pinlist->info[icon].data.window.handle;
        e.icon_handle=-1;
        wimp_send_message(Wimp_EMouseClick,&e,e.window_handle,0,0);
      } else {
        // select icon under menu
        backdrop_select_icon(state,icon,BD_SELECT_CLEAR_ALL);
        // and show our menu
        toolbox_show_object(1,menu_mainmenu,4,0,id->self_id,-1);
      }
      break;
      
    case Wimp_MouseButtonSelect:
      if (pin_is_fsfile(&pinlist->info[icon]) || pin_is_localfile(&pinlist->info[icon])) {
        filer_run(icon);
        backdrop_select_icon(state,icon,BD_SELECT_TOGGLE);
      }
      if (pin_is_anywin(&pinlist->info[icon])) {
        iconize_restore(pinlist->info[icon].data.window.handle);
        pin_remove_window(state,pinlist->info[icon].data.window.handle);
      }
      break;

    case Wimp_MouseButtonAdjust:
      if (pin_is_fsfile(&pinlist->info[icon]) || pin_is_localfile(&pinlist->info[icon])) {
        filer_run(icon);
        pinlist->info[icon].icondata.flags|=WimpIcon_Selected;
        if (pin_is_fsfile(&pinlist->info[icon])) pin_remove_pins(state,icon,icon+1);
      }
      if (pin_is_anywin(&pinlist->info[icon])) {
        iconize_restore(pinlist->info[icon].data.window.handle);
        pin_remove_window(state,pinlist->info[icon].data.window.handle);
      }
      break;
      
    case Wimp_MouseButtonSelect*256:
      backdrop_select_icon(state,icon,BD_SELECT_CLEAR_ALL);
      break;

    case Wimp_MouseButtonAdjust*256:
      backdrop_select_icon(state,icon,BD_SELECT_TOGGLE);
      break;
      
    case Wimp_MouseButtonAdjust*16:
    case Wimp_MouseButtonSelect*16:
      // drag an icon
      {
        int window_handle;
        char sprite_name[13];
        int counted=pin_count_unlocked(1,2,pin_type_fsfile | pin_type_localfile | pin_type_window);
        if (counted) {
          if (counted>1) {
            strcpy(sprite_name,"package");
          } else {
            icon=pin_get_selected_unlocked(pin_type_fsfile | pin_type_localfile | pin_type_window); // else we'll show a drag for the locked pin
            strcpy(sprite_name,indirected(pinlist->info[icon].icondata.sprite,char *));
          }
          window_get_wimp_handle(0,state->obj,&window_handle);
          drag_box_init(window_handle,&pinlist->info[icon].icondata.bbox,sprite_name,state);
        }
      }
      break;
    }
  } else {
    // isn't over an icon, but may still need to do something (eg dragbox)
    switch (event->mouse_click.buttons) {
    case Wimp_MouseButtonMenu:
        toolbox_show_object(1,menu_mainmenu,4,0,id->self_id,-1);
        break;

    case Wimp_MouseButtonSelect*16:
    case Wimp_MouseButtonAdjust*16:
      // drag box to select icons
      {
        WimpDragBox drag;
        unsigned int mask;
        WimpGetWindowStateBlock win;

        win.window_handle=event->mouse_click.window_handle;
        wimp_get_window_state(&win);

        // ensure sticky bboxes are correct
        sticky_update_bboxes(state);
        
        drag.wimp_window=event->mouse_click.window_handle;
        drag.drag_type=Wimp_DragBox_DragRubberDash;
        drag.dragging_box.xmin=drag.dragging_box.xmax=event->mouse_click.mouse_x;
        state->drag_x=event->mouse_click.mouse_x;
        drag.dragging_box.ymin=drag.dragging_box.ymax=event->mouse_click.mouse_y;
        state->drag_y=event->mouse_click.mouse_y;
        drag.parent_box=win.visible_area;
        drag.parent_box.xmin+=(1<<bbc_modevar(-1,bbc_XEigFactor));
        drag.parent_box.xmax-=(1<<bbc_modevar(-1,bbc_XEigFactor));
        drag.parent_box.ymax-=(1<<bbc_modevar(-1,bbc_YEigFactor));

        _swix(Wimp_DragBox,_INR(1,3),&drag,0x4b534154,3);
        if (event->mouse_click.buttons==Wimp_MouseButtonSelect*16) {
          state->predrag_state=NULL;
        } else {
          if (flex_alloc((flex_ptr)&state->predrag_state,pinlist->count)) {
            //adjust: needs to collect predrag state
            int i;
            for (i=0;i<pinlist->count;i++) {
              state->predrag_state[i]=pinlist->info[i].icondata.flags & WimpIcon_Selected?1:0;
            }
          } else {
            msgtrans_report_error("NoMem");
          }
        }
        event_register_wimp_handler(-1,Wimp_EUserDrag,backdrop_select_drag_end,state);
        event_register_wimp_handler(-1,Wimp_ENull,backdrop_drag_progress,state);
        null_events_enable();
        dragging_selection=1;
      }
      break;
      
    case Wimp_MouseButtonSelect*256:
      backdrop_select_icon(state,-1,BD_SELECT_CLEAR_ALL);
      break;
    }
  }
  
  return 1;
}

static int displaymenu_show(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(event);

  menu_set_tick(0,id->self_id,DisplayMenu_Large,state->iconsize==ICONSIZE_DOUBLE);
  menu_set_tick(0,id->self_id,DisplayMenu_Normal,state->iconsize==ICONSIZE_NORMAL);
  menu_set_tick(0,id->self_id,DisplayMenu_Small,state->iconsize==ICONSIZE_SMALL);
  return 1;
}

static void update_icon_bbox(pin *p,_iconsize newsize)
{
  // work out a new set of coords for this icon as the display option has changed
  // keep the lower left coord and adjust based on that.
  int w,h,textwidth;

  _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(p->icondata.buffer,char *),0,&textwidth);
  textwidth+=8;

  switch (newsize) {
    case ICONSIZE_NORMAL:
      // width is textwidth or sprite width, whichever largest
      if (textwidth<p->sprite_x) textwidth=p->sprite_x;
      p->icondata.bbox.xmax=p->icondata.bbox.xmin+textwidth;

      p->icondata.bbox.ymax=p->icondata.bbox.ymin+p->sprite_y+4+ICON_TEXT_HEIGHT;
      break;

    case ICONSIZE_SMALL:
      // height is now text height or sprite height
      if (p->sprite_y/2>ICON_TEXT_HEIGHT) h=p->sprite_y/2; else h=ICON_TEXT_HEIGHT;
      p->icondata.bbox.ymax=p->icondata.bbox.ymin+h;

      w=p->sprite_x/2+8+textwidth;
      p->icondata.bbox.xmax=p->icondata.bbox.xmin+w;

      break;

    case ICONSIZE_DOUBLE:
      // width is textwidth or double sprite width, whichever largest
      if (textwidth<p->sprite_x*2) textwidth=p->sprite_x*2;
      p->icondata.bbox.xmax=p->icondata.bbox.xmin+textwidth;

      p->icondata.bbox.ymax=p->icondata.bbox.ymin+p->sprite_y*2+4+ICON_TEXT_HEIGHT;
      break;
  }
}

int pin_update_iconsize(_backdrop_state *state,_iconsize size)
{
  int i,more;
  _kernel_oserror *e;
  WimpRedrawWindowBlock redraw;

  if (size==state->iconsize) return 0; // nothing to do
  state->iconsize=size;

  // recalc bounding boxes
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anyfile(&pinlist->info[i])) {
      update_icon_bbox(&pinlist->info[i],state->iconsize);
    }
  }

  // redraw whole pinboard
  window_get_wimp_handle(0,state->obj,&redraw.window_handle);
  ((WimpUpdateWindowBlock *)&redraw)->update_area.xmin=0;
  ((WimpUpdateWindowBlock *)&redraw)->update_area.xmax=state->window_width;
  ((WimpUpdateWindowBlock *)&redraw)->update_area.ymax=0;
  ((WimpUpdateWindowBlock *)&redraw)->update_area.ymin=-state->window_height;
  e=wimp_update_window(&redraw,&more);
  if (e==NULL && more) backdrop_redraw_loop(state,&redraw,more);

  return 1;
}

static int displaymenu_selection(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  int changed=0;

  IGNORE(event);
  IGNORE(event_code);

  // change setting
  switch (id->self_component) {
    case DisplayMenu_Large:
      changed=pin_update_iconsize(state,ICONSIZE_DOUBLE);
      break;

    case DisplayMenu_Normal:
      changed=pin_update_iconsize(state,ICONSIZE_NORMAL);
      break;

    case DisplayMenu_Small:
      changed=pin_update_iconsize(state,ICONSIZE_SMALL);
      break;
  }

  // adjust menu settings
  if (changed) displaymenu_show(0,NULL,id,handle);

  return 1;
}

static void pins_count_lockable(int *lockable,int *unlockable)
{
  int i;
  *lockable=0;
  *unlockable=0;

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anyfile(&pinlist->info[i]) && (pinlist->info[i].icondata.flags & WimpIcon_Selected)) {
      if (pin_is_locked(&pinlist->info[i])) {
        (*unlockable)++;
      } else {
        (*lockable)++;
      }
    }
  }    
}

static int backdrop_mainmenu_show(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  int count=pin_count(1,0x7ffffff,pin_type_fsfile | pin_type_localfile | pin_type_window | pin_type_sticky);
  int lockable,unlockable;
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(handle);
  
  // shade icons that aren't relevant
  // selection icon
  switch (count) {
  case 0:
    menu_set_fade(0,id->self_id,Menu_Selected,1);
    menu_set_entry_text(0,id->self_id,Menu_Selected,msgtrans_lookup("MenuSel"));
    break;
    
  case 1:
    {
      char *text;
      int index=pin_get_selected(pin_type_localfile | pin_type_window | pin_type_fsfile | pin_type_sticky);
      
      menu_set_fade(0,id->self_id,Menu_Selected,0);
      if (pin_is_sticky(&pinlist->info[index])) {
        menu_set_entry_text(0,id->self_id,1,"Sticky");
      } else {     
        if (flex_alloc((flex_ptr)&text,strlen(indirected(pinlist->info[index].icondata.buffer,char *))+10)) {
          if (pin_is_window(&pinlist->info[index])) {
            sprintf(text,msgtrans_lookup("MenuWin"),indirected(pinlist->info[index].icondata.buffer,char *));
          } else {
            if (strcmp("directory",indirected(pinlist->info[index].icondata.sprite,char *))==0) {
              sprintf(text,msgtrans_lookup("MenuDir"),indirected(pinlist->info[index].icondata.buffer,char *));
              menu_set_entry_text(0,menu_selectionmenu,SelMenu_Copy,msgtrans_lookup("MenuCD"));
              menu_set_entry_text(0,menu_selectionmenu,SelMenu_Rename,msgtrans_lookup("MenuRD"));
              menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup("MenuDD"));
            } else {
              if (strncmp(indirected(pinlist->info[index].icondata.sprite,char *),"file",4)==0) {
                sprintf(text,msgtrans_lookup("MenuFile"),indirected(pinlist->info[index].icondata.buffer,char *));  
                menu_set_entry_text(0,menu_selectionmenu,SelMenu_Copy,msgtrans_lookup("MenuCF"));
                menu_set_entry_text(0,menu_selectionmenu,SelMenu_Rename,msgtrans_lookup("MenuRF"));
                menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup("MenuDF"));        
              } else {
                sprintf(text,msgtrans_lookup("MenuApp"),indirected(pinlist->info[index].icondata.buffer,char *));          
                menu_set_entry_text(0,menu_selectionmenu,SelMenu_Copy,msgtrans_lookup("MenuCA"));
                menu_set_entry_text(0,menu_selectionmenu,SelMenu_Rename,msgtrans_lookup("MenuRA"));
                menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup("MenuDA"));
              }
            }
          }
        
          menu_set_entry_text(0,id->self_id,Menu_Selected,text);
          flex_free((flex_ptr)&text);
        } else {
          menu_set_entry_text(0,id->self_id,Menu_Selected,msgtrans_lookup("MenuSel"));
        }
      }
    }
    break;
    
  default:
    menu_set_fade(0,id->self_id,1,0);
    menu_set_entry_text(0,id->self_id,Menu_Selected,msgtrans_lookup("MenuSel"));

    int dir=0,file=0,app=0,index=-1;
    while ((index=pin_iterate_selection(index,pin_type_localfile | pin_type_window | pin_type_fsfile,0,1))!=-1) {
      if (strcmp("directory",indirected(pinlist->info[index].icondata.sprite,char *))==0) {
        dir++;
      } else {
        if (*(indirected(pinlist->info[index].icondata.sprite,char *))=='!' || strcmp("application",indirected(pinlist->info[index].icondata.sprite,char *))==0) {
          app++;
        } else {
          if (strncmp(indirected(pinlist->info[index].icondata.sprite,char *),"file",4)==0) file++;
        }
      }
    }

    if (dir==0 && app==0 && file!=0) {
      menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup(file==1?"MenuDF":"MenuDF+"));
    } else {
      if (dir==0 && app!=0 && file==0) {
        menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup(app==1?"MenuDA":"MenuDA+"));
      } else {
        if (dir!=0 && app==0 && file==0) {
          menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup(dir==1?"MenuDD":"MenuDD+"));
        } else {
          menu_set_entry_text(0,menu_selectionmenu,SelMenu_DelFile,msgtrans_lookup("MenuD++"));
        }
      }
    }


 debugf("selected %d files %d apps %d dirs\n",file,app,dir);

    break;
  }
  
  // select all
  // ... present if there are any icons
  menu_set_fade(0,id->self_id,Menu_SelectAll,pin_count(0,1,pin_type_fsfile | pin_type_localfile | pin_type_window | pin_type_sticky)==0);
  
  // clear selection
  // ... only if there's a selection
  menu_set_fade(0,id->self_id,Menu_Clear,count<1);
  
  // configure
  // save
  // ... always present

  // lock pins
  // .. only if there are unlocked pins and files
  pins_count_lockable(&lockable,&unlockable);
  if (lockable==0 && unlockable==0) {
    menu_set_fade(0,menu_selectionmenu,SelMenu_Lock,1);
    menu_set_fade(0,menu_selectionmenu,SelMenu_Unlock,1);
  } else {
    menu_set_fade(0,menu_selectionmenu,SelMenu_Lock,0);
    menu_set_fade(0,menu_selectionmenu,SelMenu_Unlock,0);
    if (lockable==0 && unlockable!=0) {
      menu_set_tick(0,menu_selectionmenu,SelMenu_Lock,1);
      menu_set_tick(0,menu_selectionmenu,SelMenu_Unlock,0);
    } else {
      if (lockable!=0 && unlockable==0) {
        menu_set_tick(0,menu_selectionmenu,SelMenu_Lock,0);
        menu_set_tick(0,menu_selectionmenu,SelMenu_Unlock,1);
      } else {
        menu_set_tick(0,menu_selectionmenu,SelMenu_Lock,0);
        menu_set_tick(0,menu_selectionmenu,SelMenu_Unlock,0);
      }
    }
  }

  return 1;
}

static void menu_shading_redo(_backdrop_state *state)
{
  IdBlock myid={0,0,0,0,0,0};
  myid.self_id=menu_mainmenu;
  backdrop_mainmenu_show(Menu_AboutToBeShown,NULL,&myid,state);

  myid.self_id=menu_selectionmenu;
  backdrop_selection_show(Menu_AboutToBeShown,NULL,&myid,state);
}

static void reset_action(confirm_action action, ObjectId parent, void *handle)
{
  IGNORE(parent);
  _backdrop_state *state=(_backdrop_state *)handle;

  if (action==CONFIRM_ACTION_YES) {
    pin_remove_stickies(state,0,pinlist->count,1);
    _swix(OS_CLI,_IN(0),"Filer_Run Choices:Boot.Tasks.Pinboard");
    _swix(OS_CLI,_IN(0),"Filer_Run Choices:Boot.Tasks.PinSetup");
    sticky_load(state); // previous files may not have triggered a sticky reload
  }

}

static int backdrop_mainmenu_choice(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(event);
  
  switch (id->self_component) {
  case Menu_Clear: // Clear selection
    backdrop_select_icon(state,-1,BD_SELECT_CLEAR_ALL);
    break;
    
  case Menu_Selected: // Selection->
    // do nothing
    break;
    
  case Menu_SelectAll: // Select all
    // no submenu choice, so select everything
    backdrop_select_all(state,pin_type_fsfile | pin_type_localfile | pin_type_window);
    break;
    
  case Menu_Configure: // Configure
    // run external configure task
    _swix(OS_CLI,_IN(0),"Filer_Run BootResources:Configure.!PinSetup");
    break;
    
  case Menu_Save: // Save
    // do it
    pinboard_save(state,PINBOARD_DEFAULT_FILENAME);
    break;

  case Menu_NewSticky: // New sticky
    {
      int p;
      WimpGetPointerInfoBlock mouse;
      wimp_get_pointer_info(&mouse);
      if ((p=sticky_add(state,mouse.x-256,mouse.y-128,512,256,""))!=-1) {
        // gadget_set_focus(0,state->obj,pinlist->info[p].data.sticky.textarea);
        // doesn't work, probably because the parent window isn't officially writable.
        // so do it directly:
        wimp_set_caret_position(pinlist->info[p].data.sticky.textarea_handle,-1,0,-34,34,-1);
      }
    }
    break;

  case Menu_Open: // Open local folder
    filer_show_parent(-1);
    break;

  case Menu_Reset: // reset settings by reloading
    confirm(msgtrans_lookup("RSTmsg"),msgtrans_lookup("RSTYes"),msgtrans_lookup("RSTNo"),reset_action,0,handle);
    break;
  }

  menu_shading_redo(state);
    
  return 1;
}


int backdrop_selection_show(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  // if it's an application directory, then we need to consider offering Help (if present)
  int fade;
  int count_files;
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(handle);
  
  // top part of the menu
  // Tidy - always present
  // Open - only applies to files
  count_files=pin_count(1,2,pin_type_fsfile | pin_type_localfile);

  menu_set_fade(0,id->self_id,SelMenu_Open,count_files==0);

  // Remove - only for shortcuts
  switch (pin_count(1,2,pin_type_fsfile)) {
  case 0:
    menu_set_fade(0,id->self_id,SelMenu_Remove,1);
    menu_set_entry_text(0,id->self_id,SelMenu_Remove,msgtrans_lookup("SMenuRSn"));
    break;

  case 1:
    menu_set_fade(0,id->self_id,SelMenu_Remove,0);
    menu_set_entry_text(0,id->self_id,SelMenu_Remove,msgtrans_lookup("SMenuRS1"));
    break;

  case 2:
    menu_set_fade(0,id->self_id,SelMenu_Remove,0);
    menu_set_entry_text(0,id->self_id,SelMenu_Remove,msgtrans_lookup("SMenuRSn"));
    break;
  }

  // Help - only if a single application icon is selected
  fade=1;
  if (count_files==1) {
    int index=pin_get_selected(pin_type_localfile | pin_type_fsfile);
    if (index!=-1) {
      if (pin_is_anyfile(&pinlist->info[index])) {
        int filetype;
        _swix(OS_File,_INR(0,1)|_OUT(6),23,indirected(pinlist->info[index].data.filename_offset,char *),&filetype);
        if (filetype==0x2000) {
          // look for !Help file
          char *path;
          if (flex_alloc((flex_ptr)&path,strlen(indirected(pinlist->info[index].data.filename_offset,char *))+2)) {
            int objtype;
            sprintf(path,"%s.",indirected(pinlist->info[index].data.filename_offset,char *));
            _swix(OS_File,_INR(0,1)|_IN(4)|_OUT(0),21,"!Help",path,&objtype);
            flex_free((flex_ptr)&path);
            fade=(objtype==0);
          }
        }
      }
    }
  }
  
  menu_set_fade(0,id->self_id,SelMenu_Help,fade);
  
  // filer-type choices
  menu_set_fade(0,id->self_id,SelMenu_Copy,count_files!=1);
  menu_set_fade(0,id->self_id,SelMenu_Rename,count_files!=1);
  menu_set_fade(0,id->self_id,SelMenu_DelFile,count_files==0);
  menu_set_fade(0,id->self_id,SelMenu_Count,count_files==0);
  menu_set_fade(0,id->self_id,SelMenu_Info,count_files!=1);

  if (pin_count_nodir(1,2,pin_type_localfile | pin_type_fsfile)) {
    menu_set_fade(0,id->self_id,SelMenu_SetType,0);
  } else {
    menu_set_fade(0,id->self_id,SelMenu_SetType,1);
  }

  menu_set_fade(0,id->self_id,SelMenu_Stamp,count_files==0);
  menu_set_fade(0,id->self_id,SelMenu_OpenParent,count_files!=1);

  return 1;
}
/*
static void pin_tidy(_backdrop_state *state,int icon)
{
  int w,h;
  int grid_width,grid_height;
  grid_size(state,&grid_width,&grid_height);
  
  w=pinlist->info[icon].icondata.bbox.xmax-pinlist->info[icon].icondata.bbox.xmin;
  h=pinlist->info[icon].icondata.bbox.ymax-pinlist->info[icon].icondata.bbox.ymin;
  
  pin_find_gap(state,state->setting_tidy,state->setting_tidy_stack,&pinlist->info[icon].icondata.bbox);

  // recentre the icon in its new home
  pinlist->info[icon].icondata.bbox.xmin+=(grid_width-w)/2;
//  pinlist->info[icon].icondata.bbox.ymin+=(GRID_HEIGHT-h)/2;

  pinlist->info[icon].icondata.bbox.xmax=pinlist->info[icon].icondata.bbox.xmin+w;
  pinlist->info[icon].icondata.bbox.ymax=pinlist->info[icon].icondata.bbox.ymin+h;

  //  pin_redraw(state,icon); // and redraw

}*/

static void pins_set_lock(_backdrop_state *state,int lock)
{
  int i;
  lock*=pin_locked;

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_anyfile(&pinlist->info[i])) {
      if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
        if ((pinlist->info[i].icontype & pin_locked)!=lock) {
          pinlist->info[i].icontype=(pinlist->info[i].icontype & ~pin_locked) | lock;
          pin_redraw(state,i);
        }
      }
    }
   }
}

int pin_iterate_selection(int index,int mask,int skip_locked,int no_tiny)
{
  int i;
  for (i=index+1;i<pinlist->count;i++) {
    if (!no_tiny || (no_tiny && !pin_is_tiny(&pinlist->info[i]))) {
      if ((pinlist->info[i].icontype &mask)!=0) {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
          if (!skip_locked) return i;
          if (!pin_is_locked(&pinlist->info[i])) return i;
        }
      }
    }
  }
  return -1;
}


static void pin_tidy_selection(_backdrop_state *state)
{
  grid_iterator gi;
  int index=pin_iterate_selection(-1,pin_type_localfile | pin_type_window | pin_type_fsfile,1,1); // index under test
  int overflow=0; // have we run out of room yet?
  int shunt=0;
  int w,h,xoffset;

  // set initial bbox locations and increments etc
  gi_init(state,&gi,state->setting_tidy,state->setting_tidy_stack);

  while (index<pinlist->count && !overflow) {
    // is there a hole here?
    debugf("test location: %d,%d,%d,%d\n",gi.test_location.xmin,gi.test_location.ymin,gi.test_location.xmax,gi.test_location.ymax);
    if (pin_space_free(&gi.test_location,state->iconsize)) {
      // yes - relocate the pin to the centre of the test bbox if appropriate
      w=pinlist->info[index].icondata.bbox.xmax-pinlist->info[index].icondata.bbox.xmin;
      h=pinlist->info[index].icondata.bbox.ymax-pinlist->info[index].icondata.bbox.ymin;
      if (state->iconsize==ICONSIZE_SMALL) {
        pinlist->info[index].icondata.bbox.xmin=gi.test_location.xmin+4;
      } else {
        xoffset=(gi.grid_width-w)/2;  // adjustment to centre it
        pinlist->info[index].icondata.bbox.xmin=gi.test_location.xmin+xoffset;
      }
      pinlist->info[index].icondata.bbox.xmax=pinlist->info[index].icondata.bbox.xmin+w;
      pinlist->info[index].icondata.bbox.ymin=gi.test_location.ymin;
      pinlist->info[index].icondata.bbox.ymax=pinlist->info[index].icondata.bbox.ymin+h;

      // move to next selection
      debugf("next pin\n");
      index=pin_iterate_selection(index,pin_type_localfile | pin_type_window | pin_type_fsfile,1,1);
    }

    // move to next grid point
    overflow=gi_next_gridspace(state,&gi);
  }

  if (!overflow) return; // we finished tidying :)

  // we overflowed.  Just stick the rest on the screen and don't panic about overwriting.

  while (index<pinlist->count) {
    if (overflow) {
      // redo with a slight offset
      shunt+=16;
      overflow=0;
      gi.test_location=gi.start_location;
    }

    // relocate the pin to the centre of the test bbox
    w=pinlist->info[index].icondata.bbox.xmax-pinlist->info[index].icondata.bbox.xmin;
    h=pinlist->info[index].icondata.bbox.ymax-pinlist->info[index].icondata.bbox.ymin;
    if (state->iconsize==ICONSIZE_SMALL) {
      pinlist->info[index].icondata.bbox.xmin=gi.test_location.xmin+4;
    } else {
      xoffset=(gi.grid_width-w)/2;  // adjustment to centre it
      pinlist->info[index].icondata.bbox.xmin=gi.test_location.xmin+xoffset+shunt;
    }
    pinlist->info[index].icondata.bbox.xmax=pinlist->info[index].icondata.bbox.xmin+w;
    pinlist->info[index].icondata.bbox.ymin=gi.test_location.ymin-shunt;
    pinlist->info[index].icondata.bbox.ymax=pinlist->info[index].icondata.bbox.ymin+h;

    // move to next selection
    index=pin_iterate_selection(index,pin_type_localfile | pin_type_window | pin_type_fsfile,1,1);

    // move to next grid point
    overflow=gi_next_gridspace(state,&gi);
  }
}

static int backdrop_selection_choice(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  int i;
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(event);
  
  switch (id->self_component) {
  case SelMenu_Tidy: // tidy
    {
      WimpRedrawWindowBlock block;
      int more;
      sticky_update_bboxes(state); // ensure stickies have up-to-date boxes
      
      // pass 1 - move all of the icons out of the way
      // so that there are gaps to tidy them back into again
      for (i=0;i<pinlist->count;i++) {
        if (!pin_is_tiny(&pinlist->info[i])) {
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            // move it off screen for the moment
            pinlist->info[i].icondata.bbox.xmin-=262144;
            pinlist->info[i].icondata.bbox.xmax-=262144;
          }
        }
      }
      
      pin_tidy_selection(state);
/*
      for (i=0;i<pinlist->count;i++) {
        if (!pin_is_tiny(&pinlist->info[i])) {
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            // tidy this one
            pin_tidy(state,i);
          }
        }
      }*/
      
      // redraw whole screen
      window_get_wimp_handle(0,state->obj,&block.window_handle);
      ((WimpUpdateWindowBlock *)&block)->update_area.xmin=0;
      ((WimpUpdateWindowBlock *)&block)->update_area.xmax=state->window_width;
      ((WimpUpdateWindowBlock *)&block)->update_area.ymax=0;
      ((WimpUpdateWindowBlock *)&block)->update_area.ymin=-state->window_height;
      wimp_update_window(&block,&more);
      backdrop_redraw_loop(state,&block,more);
    }
    break;
    
  case SelMenu_Open: // open
    for (i=0;i<pinlist->count;i++) {
      if (!pin_is_tiny(&pinlist->info[i])) {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
          if (pin_is_window(&pinlist->info[i])) {
            iconize_restore(pinlist->info[i].data.window.handle);
            pin_free_store(i);
            pinlist->info[i].icontype=pin_type_empty;
          } else {
            if (pin_is_anyfile(&pinlist->info[i])) filer_run(i);
          }
          pinlist->info[i].icondata.flags&=~WimpIcon_Selected;
          pin_redraw(state,i);
        }
      }
    }
    pin_remove_empties(state);
    break;
    
  case SelMenu_Remove: // remove
    pin_remove_pins(state,0,pinlist->count);
    break;
    
  case SelMenu_Help: // help
    {
      // run the help file
      // we know that the selection was valid already
      char *cmd;
      int index=pin_get_selected(pin_type_localfile | pin_type_fsfile);
      if (index!=-1) {
        if (flex_alloc((flex_ptr)&cmd,strlen(indirected(pinlist->info[index].data.filename_offset,char *))+20)) {
          sprintf(cmd,"Filer_Run %s.!Help",indirected(pinlist->info[index].data.filename_offset,char *));
          _swix(OS_CLI,_IN(0),cmd);
          flex_free((flex_ptr)&cmd);
        } else {
          msgtrans_report_error("NoMem");
        }
      }
    }
    break;

  case SelMenu_DelFile:
    fileraction_run(SOURCE_PINBOARD_SELDRAG,FILERACTION_DELETE,0,NULL,state);
    break;

  case SelMenu_Count:
    fileraction_run(SOURCE_PINBOARD_SELDRAG,FILERACTION_COUNT,0,NULL,state);
    break;

  case SelMenu_SetType:
    // handled automatically
    break;

  case SelMenu_Stamp:
    fileraction_run(SOURCE_PINBOARD_SELDRAG,FILERACTION_STAMP,0,NULL,state);
    break;

  case SelMenu_OpenParent:
    {
      int index=pin_get_selected(pin_type_localfile | pin_type_fsfile);
      if (index!=-1) {
        filer_show_parent(index);
      }
    }
    break;

   case SelMenu_Lock:
     pins_set_lock(state,1);
     break;

   case SelMenu_Unlock:
     pins_set_lock(state,0);
     break;
  }

  menu_shading_redo(state);
  
  return 1;
}

static int backdrop_selectall_show(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  int i;
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(handle);

  // clear all buttons
  for (i=0;i<=SELECTALL_MAX;i++) optionbutton_set_state(0,id->self_id,i,0);
  
  // select all files
  gadget_set_flags(0,id->self_id,0,pin_count(0,1,pin_type_localfile)!=0?0:Gadget_Faded);
  gadget_set_flags(0,id->self_id,1,pin_count(0,1,pin_type_fsfile)!=0?0:Gadget_Faded);
  //  menu_set_fade(0,id->self_id,0,(f=pin_count(0,1,pin_type_fsfile | pin_type_localfile))==0);
                
  // select all windows
  gadget_set_flags(0,id->self_id,2,pin_count(0,1,pin_type_window)!=0?0:Gadget_Faded);
  //menu_set_fade(0,id->self_id,1,(w=pin_count(0,1,pin_type_window))==0);

  // select all stickies
  gadget_set_flags(0,id->self_id,3,pin_count(0,1,pin_type_sticky)!=0?0:Gadget_Faded);
    
  // select files and windows
  // menu_set_fade(0,id->self_id,2,!(w!=0 && f!=0));
  
  return 1;
}

static int backdrop_selectall_action(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  int i;
  unsigned int flags;
  int onoff;
  IGNORE(event_code);
  IGNORE(event);
  _backdrop_state *state=(_backdrop_state *)handle;

  switch (id->self_component) {
  case 5:  // tick all unshaded components
  case 6: // clear all unshaded components
    for (i=0;i<=SELECTALL_MAX;i++) {
      gadget_get_flags(0,id->self_id,i,&flags);
      if (flags==0) optionbutton_set_state(0,id->self_id,i,id->self_component==5?1:0);
    }
    break;
    
  case 4: // do the selection
    optionbutton_get_state(0,id->self_id,0,&onoff); // local files
    if (onoff) backdrop_select_all(state,pin_type_localfile);
    optionbutton_get_state(0,id->self_id,1,&onoff); // general files
    if (onoff) backdrop_select_all(state,pin_type_fsfile);
    optionbutton_get_state(0,id->self_id,2,&onoff); // windows
    if (onoff) backdrop_select_all(state,pin_type_window);
    optionbutton_get_state(0,id->self_id,3,&onoff); // stickies
    if (onoff) backdrop_select_all(state,pin_type_sticky);

    //    menu_shading_redo(state);

    if ((event->hdr.flags & 4)!=0) {
      // close menu
      toolbox_hide_object(0,id->self_id);
      toolbox_hide_object(0,menu_mainmenu);
    }
    
    break;
  }


  return 1;
}

static void backdrop_show(_backdrop_state *state,int already_running)
{
  WindowShowObjectBlock coords;

  BBox extent;

  if (already_running) {
    WimpGetWindowStateBlock win;
    window_get_wimp_handle(0,state->obj,&win.window_handle);
    wimp_get_window_state(&win);

    coords.behind=win.behind; // maintain position
  } else {
    coords.behind=-2; // backdrop
    state->toggle_position=TOGGLE_BACK;
  }
  coords.visible_area.xmin=0;
  coords.xscroll=0;
  coords.yscroll=0;
  
  state->pixel_x=1<<bbc_modevar(-1,bbc_XEigFactor);
  coords.visible_area.xmax=(2+bbc_modevar(-1,bbc_XWindLimit))*state->pixel_x; /* hide the window border */
  coords.visible_area.ymin=iconbar_height;
  state->pixel_y=1<<bbc_modevar(-1,bbc_YEigFactor);
  coords.visible_area.ymax=(1+bbc_modevar(-1,bbc_YWindLimit))*state->pixel_y; /* hide the window border */

  state->window_width=coords.visible_area.xmax;
  state->window_height=coords.visible_area.ymax-coords.visible_area.ymin;

  extent.xmin=0;
  extent.xmax=state->window_width;
  extent.ymax=0;
  extent.ymin=-state->window_height;
  window_set_extent(0,state->obj,&extent);

  toolbox_show_object(0,state->obj,Toolbox_ShowObject_FullSpec,&coords,0,0);
}

static int backdrop_auto_created(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  ToolboxObjectAutoCreatedEvent *e=(ToolboxObjectAutoCreatedEvent *)event;
  IGNORE(event_code);

  if (strcmp(e->template_name,"selectallW")==0) {
    event_register_toolbox_handler(id->self_id,Window_AboutToBeShown,backdrop_selectall_show,handle);
    event_register_toolbox_handler(id->self_id,ActionButton_Selected,backdrop_selectall_action,handle);
  }
  
  if (strcmp(e->template_name,"selection")==0) {
    event_register_toolbox_handler(id->self_id,Menu_AboutToBeShown,backdrop_selection_show,handle);
    event_register_toolbox_handler(id->self_id,Menu_Selection,backdrop_selection_choice,handle);
    menu_selectionmenu=id->self_id;
  }

  if (strcmp(e->template_name,"SaveAs")==0) {
    event_register_toolbox_handler(id->self_id,SaveAs_AboutToBeShown,pinboard_save_tobeshown,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_FillBuffer,pinboard_save_fill,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_SaveToFile,pinboard_save_to_file,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_DialogueCompleted,pinboard_save_completed,handle);
    saveas_win=id->self_id;
  }

  if (strcmp(e->template_name,"SaveSticky")==0) {
    event_register_toolbox_handler(id->self_id,SaveAs_AboutToBeShown,sticky_save_tobeshown,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_FillBuffer,sticky_save_fill,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_SaveToFile,sticky_save_to_file,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_DialogueCompleted,sticky_save_completed,handle);
  }
  
  if (strcmp(e->template_name,"FileInfo")==0) {
    event_register_toolbox_handler(id->self_id,Window_AboutToBeShown,fileinfo_show,handle);
  }

  if (strcmp(e->template_name,"Rename")==0) {
    event_register_toolbox_handler(id->self_id,Window_AboutToBeShown,rename_show,handle);
    event_register_toolbox_handler(id->self_id,ActionButton_Selected,rename_action,handle);
  }

  if (strcmp(e->template_name,"cdir")==0) {
    event_register_toolbox_handler(id->self_id,Window_AboutToBeShown,cdir_show,handle);
    event_register_toolbox_handler(id->self_id,ActionButton_Selected,cdir_action,handle);
  }

  if (strcmp(e->template_name,"CopyAs")==0) {
    event_register_toolbox_handler(id->self_id,SaveAs_AboutToBeShown,copyas_action,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_SaveToFile,copyas_action,handle);
    event_register_toolbox_handler(id->self_id,SaveAs_DialogueCompleted,copyas_action,handle);
  }

  if (strcmp(e->template_name,"SetType")==0) {
    settype_obj=id->self_id;
    event_register_toolbox_handler(id->self_id,Window_AboutToBeShown,settype_show,handle);
    event_register_toolbox_handler(id->self_id,Window_HasBeenHidden,settype_hide,handle);
    event_register_toolbox_handler(id->self_id,ActionButton_Selected,settype_action,handle);
    event_register_toolbox_handler(id->self_id,WritableField_ValueChanged,settype_valuechanged,handle);
  }

  if (strcmp(e->template_name,"StickyFG")==0 || strcmp(e->template_name,"StickyBG")==0) {
    event_register_toolbox_handler(id->self_id,ColourDbox_ColourSelected,sticky_update_colour,handle);
  }

  if (strcmp(e->template_name,"StickyFont")==0) {
    event_register_toolbox_handler(id->self_id,FontDbox_AboutToBeShown,sticky_font_shown,handle);
    event_register_toolbox_handler(id->self_id,FontDbox_ApplyFont,sticky_update_font,handle);
  }

  if (strcmp(e->template_name,"DisplayMenu")==0) {
    menu_displaymenu=id->self_id;
    event_register_toolbox_handler(id->self_id,Menu_AboutToBeShown,displaymenu_show,handle);
    event_register_toolbox_handler(id->self_id,Menu_Selection,displaymenu_selection,handle);
  }

  if (strcmp(e->template_name,"StickyTitle")==0) {
    event_register_toolbox_handler(id->self_id,ActionButton_Selected,sticky_title_action,handle);
  }

  return 0; // allow other handlers to check
}

static int modechange_null(int event_code,WimpPollBlock *poll,IdBlock *id,void *handle)
{
  int i;
  unsigned int old_mask;
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(poll);
  IGNORE(id);

  // adjust backdrop size
  backdrop_show(state,1);

  null_events_disable();

  event_deregister_wimp_handler(-1,Wimp_ENull,modechange_null,handle);

  if (state->watermark_data) watermark_recalc_plot(state);

  // try to get the textarea gadgets to update their font settings
  // do this by resetting it to something else (forces it to
  // free resource) then setting it back again - ugh

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_sticky(&pinlist->info[i])) {
      textarea_set_font(0,state->obj,pinlist->info[i].data.sticky.textarea,"Homerton.Medium",16,16);
      // note if you set font size of 1,1 then TextArea dies horribly with division by zero error and takes out Pinboard
      // we're trying to pick something that won't be in use to force a change in case TextArea is clever and doesn't
      // update when there's no actual change
      if (pinlist->info[i].data.sticky.font_name!=INDIRECT_FREEI) {
        debugf("Set %s, %d, %d\n",indirected(pinlist->info[i].data.sticky.font_name,char *),pinlist->info[i].data.sticky.font_x,pinlist->info[i].data.sticky.font_y);
        if (font_exists(indirected(pinlist->info[i].data.sticky.font_name,char *),pinlist->info[i].data.sticky.font_x,pinlist->info[i].data.sticky.font_y)) {
          textarea_set_font(0,state->obj,pinlist->info[i].data.sticky.textarea,
                            indirected(pinlist->info[i].data.sticky.font_name,char *),
                            pinlist->info[i].data.sticky.font_x,
                            pinlist->info[i].data.sticky.font_y);
        } else {
          textarea_set_font(0,state->obj,pinlist->info[i].data.sticky.textarea,
                            "Homerton.Medium",
                            pinlist->info[i].data.sticky.font_x,
                            pinlist->info[i].data.sticky.font_y);
          }
      } else {
        // set to Desktop default font
        int fonth,x,y;
        _swix(Wimp_ReadSysInfo,_IN(0)|_OUT(0),8,&fonth);
        if (fonth!=0) {
          int namelen;
          char *fontname;
          _swix(Font_ReadDefn,_INR(0,1)|_IN(3)|_OUT(2),fonth,0,0x4c4c5546,&namelen);
          if (flex_alloc((flex_ptr)&fontname,1+namelen)) {
            _swix(Font_ReadDefn,_INR(0,1)|_OUTR(2,3),fonth,fontname,&x,&y);
                    textarea_set_font(0,state->obj,pinlist->info[i].data.sticky.textarea,
                                      fontname,x,y);
                    flex_free((flex_ptr)&fontname);
          } else {
            textarea_set_font(0,state->obj,pinlist->info[i].data.sticky.textarea,"Homerton.Medium",192,192);
          }
        } else {
          textarea_set_font(0,state->obj,pinlist->info[i].data.sticky.textarea,"Homerton.Medium",192,192);
        }
      }
    }
  }
  

  // reload the backdrop if need be
  if (state->bd_flags & BD_IMAGE_CACHE) {
    backdrop_load_image(state,state->bd_flags & BD_PAINT_MASK,(char *)-1,0,1); // no need to re-store filename
    // this causes a redraw anyway
  } else {
    WimpRedrawWindowBlock redraw;
    int more;
    _kernel_oserror *e;

    window_get_wimp_handle(0,state->obj,&redraw.window_handle);
//  wimp_get_window_state(&win);
    ((WimpUpdateWindowBlock *)&redraw)->update_area.xmin=0;
    ((WimpUpdateWindowBlock *)&redraw)->update_area.xmax=state->window_width;
    ((WimpUpdateWindowBlock *)&redraw)->update_area.ymax=0;
    ((WimpUpdateWindowBlock *)&redraw)->update_area.ymin=-state->window_height;
    e=wimp_update_window(&redraw,&more);
    if (e==NULL && more) backdrop_redraw_loop(state,&redraw,more);
  }
  return 1;
}

static int message_modechange(WimpMessage *m,void *handle)
{
  unsigned int old_mask;
  _backdrop_state *state=(_backdrop_state *)handle;
  
  // adjust colour tables
  int *area=(int *)state->bd_image;
  int offset=*(area+2);
  int ctsize;
  IGNORE(m);
  
  if (BD_IS_SPRITE(state->bd_flags)) {
    if (state->colourtrans) {
      flex_free((flex_ptr)&state->colourtrans);
      state->colourtrans=NULL;
    }
    _swix(ColourTrans_GenerateTable,_INR(0,7)|_OUT(4),area,offset+state->bd_image,-1,-1,0,3|(1<<4),0,0,&ctsize);
    
    if (ctsize) {
      if (flex_alloc((flex_ptr)&state->colourtrans,ctsize)) {
        _swix(ColourTrans_GenerateTable,_INR(0,7),area,offset+state->bd_image,-1,-1,state->colourtrans,1|(1<<4),0,0);
      }
    } else {
      if (state->colourtrans) {
        flex_free((flex_ptr)&state->colourtrans);
        state->colourtrans=NULL;
      }
    }
  }

  state->pixel_x=1<<bbc_modevar(-1,bbc_XEigFactor);
  state->pixel_y=1<<bbc_modevar(-1,bbc_YEigFactor);

  if (BD_TEXT_STYLE(state->bd_flags)==BD_TEXT_OUTLINE || BD_TEXT_STYLE(state->bd_flags)==BD_TEXT_BLEND) {
    outline_init(state);
    outline_clear_cache(state);
  }
  
  null_events_enable();

  event_register_wimp_handler(-1,Wimp_ENull,modechange_null,handle);
//modechange_null(0,0,0,handle);
  return 1;
}

static int backdrop_key(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  IGNORE(event_code);
  IGNORE(id);
  IGNORE(handle);
  wimp_process_key(event->key_pressed.key_code);
  return 1;
}
/*
static int backdrop_focus(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  // find out which window has lost/gained the focus and redraw the furniture for it
  WimpRedrawWindowBlock block;
  int p,more;
  IGNORE(event_code);
  IGNORE(id);
  IGNORE(handle);
  for (p=0;p<pinlist->count;p++) {
    if (pin_is_sticky(&pinlist->info[p])) {
      if (pinlist->info[p].data.sticky.textarea_handle==event->gain_caret.window_handle) { // lose_caret is the same structure
        // redraw furniture for this one
        window_get_wimp_handle(0,pinlist->info[p].data.sticky.furniture,&((WimpUpdateWindowBlock *)&block)->window_handle);
        ((WimpUpdateWindowBlock *)&block)->update_area.xmin=0;
        ((WimpUpdateWindowBlock *)&block)->update_area.xmax=128;
        ((WimpUpdateWindowBlock *)&block)->update_area.ymin=-44;
        ((WimpUpdateWindowBlock *)&block)->update_area.ymax=0;
        
        wimp_update_window(&block,&more);
        sticky_redraw_furniture_loop(pinlist->info[p].data.sticky.furniture,&block,more);
      }
    }
  }
  
  return 1;
}*/

static int backdrop_menu_click(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  // fake up a poll event and id block
  _backdrop_state *state=(_backdrop_state *)handle;
  FilterMenuEvent *fme=(FilterMenuEvent *)event;
  WimpPollBlock poll;
  int winh;

  IGNORE(event);
  IGNORE(event_code);

  poll.mouse_click=fme->mouse_click;

  window_get_wimp_handle(0,state->obj,&winh);
  if (winh==poll.mouse_click.window_handle) {
    // clicked over backdrop, so handle the menu click there
    id->self_id=state->obj;
    id->self_component=-1;
    id->parent_id=-1;
    id->parent_component=-1;
    id->ancestor_id=-1;
    id->ancestor_component=-1;

    backdrop_click(Wimp_EMouseClick,&poll,id,handle);
    return 1;
  }

  // it was a sticky.  See if we can find it.
  sticky_menu_click(state,&poll);

  return 1;
}

_backdrop_state *backdrop_init(void)
{
  // show the backdrop, set to the size of the background
  _kernel_oserror *e;
  Palette palette;
  
  event_register_toolbox_handler(-1,Toolbox_ObjectAutoCreated,backdrop_auto_created,&backdrop_state);

  if (!flex_alloc((flex_ptr)&backdrop_state.bd_image,0)) {
    msgtrans_report_error("MemFatal");
    exit(1);
  }

  wimp_read_palette(&palette);
  
  backdrop_state.fg_colour=palette.colours[0] & 0xffffff00;
  backdrop_state.bd_colour=palette.colours[4] & 0xffffff00;
  backdrop_state.bd_flags=BD_IMAGE_PLAIN | BD_TEXT_THEME;

  backdrop_state.setting_iconize=ICONIZE_TO_BUTTON;
  backdrop_state.setting_iconize_stack=ICONIZE_HORIZONTAL;

  backdrop_state.setting_tidy=ICONIZE_TO_TOPLEFT;
  backdrop_state.setting_tidy_stack=ICONIZE_HORIZONTAL;

  backdrop_state.iconsize=ICONSIZE_NORMAL;

  outline_clear_cache(&backdrop_state);

  e=toolbox_create_object(0,"backdrop",&backdrop_state.obj);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    exit(1);
  }

  e=toolbox_create_object(0,"backdropmen",&menu_mainmenu);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    exit(1);
  }

  event_register_toolbox_handler(menu_mainmenu,Menu_AboutToBeShown,backdrop_mainmenu_show,&backdrop_state);
  event_register_toolbox_handler(menu_mainmenu,Menu_Selection,backdrop_mainmenu_choice,&backdrop_state);
 
  backdrop_show(&backdrop_state,0);

  event_register_wimp_handler(backdrop_state.obj,Wimp_ERedrawWindow,backdrop_redraw,&backdrop_state);
  event_register_message_handler(Wimp_MDataLoad,backdrop_drop_pin,&backdrop_state);
  event_register_wimp_handler(backdrop_state.obj,Wimp_EMouseClick,backdrop_click,&backdrop_state);
  event_register_wimp_handler(-1,Wimp_EKeyPressed,backdrop_key,&backdrop_state); // the backdrop doesn't take keys, but stickies do
  event_register_message_handler(Wimp_MModeChange,message_modechange,&backdrop_state);

  event_register_wimp_handler(-1,Wimp_EPointerEnteringWindow,sticky_pointer_change,&backdrop_state);

  event_register_toolbox_handler(-1,TEvent_MenuClick,backdrop_menu_click,&backdrop_state);

  return &backdrop_state;
}

void pinboard_move_selected_icons(_backdrop_state *state,int dx,int dy)
{
  // move selection if possible
  int i,w,h,oldtype;
  BBox dest;
  int grid_width,grid_height;

  grid_size(state,&grid_width,&grid_height);
  
  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      if (!pin_is_locked(&pinlist->info[i])) {
        if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
          // move this one
          oldtype=pinlist->info[i].icontype;
          pinlist->info[i].icontype=pin_type_empty;
          pin_redraw(state,i);
          pinlist->info[i].icontype=oldtype;
          dest=pinlist->info[i].icondata.bbox;
          w=dest.xmax-dest.xmin;
          h=dest.ymax-dest.ymin;
          
          dest.xmin+=dx;
          if (dest.xmin<0) dest.xmin=0;
          if (dest.xmin>state->window_width-grid_width) dest.xmin=state->window_width-grid_width;
          dest.xmax=dest.xmin+w;
          
          dest.ymin+=dy;
          if (dest.ymin<iconbar_height) dest.ymin=iconbar_height;
          if (dest.ymin>state->window_height+iconbar_height-grid_height) dest.ymin=state->window_height+iconbar_height-grid_height;
          dest.ymax=dest.ymin+h;
          
          pinlist->info[i].icondata.bbox=dest;
          grid_snap(state,&pinlist->info[i].icondata.bbox);
          pin_redraw(state,i);
        }
      }    
    }
  }
}

static int pinboard_move_icon_from_tinydir(_backdrop_state *state,int index,int drop_x,int drop_y)
{
  // obtain the text and validation string bits and add them to the icon definition
  int size;
  int textwidth;

  iconbar_get_sprite(0,pinlist->info[index].obj,0,0,&size);
  pinlist->info[index].icondata.sprite=indirected_add(NULL,size);
  if (pinlist->info[index].icondata.sprite==INDIRECT_FREEI) {
    return 0;
  }
  iconbar_get_sprite(0,pinlist->info[index].obj,indirected(pinlist->info[index].icondata.sprite,char *),size,NULL);
  pin_set_spritesize(&pinlist->info[index],indirected(pinlist->info[index].icondata.sprite,char *));
  
  // get text part
  iconbar_get_text(0,pinlist->info[index].obj,0,0,&size);
  pinlist->info[index].icondata.buffer=indirected_add(NULL,size);
  if (pinlist->info[index].icondata.buffer==INDIRECT_FREEI) {
    indirected_remove(pinlist->info[index].icondata.sprite);
    pinlist->info[index].icondata.sprite=INDIRECT_FREEI;
    return 0;
  }
  iconbar_get_text(0,pinlist->info[index].obj,indirected(pinlist->info[index].icondata.buffer,char *),size,0);  

  // fill in icon definition
  // get text width
  _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(pinlist->info[index].icondata.buffer,char *),0,&textwidth);
  textwidth+=8;
  if (textwidth<pinlist->info[index].sprite_x) textwidth=pinlist->info[index].sprite_x;
  pinlist->info[index].icondata.bbox.xmin=drop_x-textwidth/2;
  pinlist->info[index].icondata.bbox.xmax=drop_x+textwidth/2;
  pinlist->info[index].icondata.bbox.ymin=drop_y-(pinlist->info[index].sprite_y+4+ICON_TEXT_HEIGHT)/2;
  pinlist->info[index].icondata.bbox.ymax=drop_y+(pinlist->info[index].sprite_y+4+ICON_TEXT_HEIGHT)/2;
    
  pinlist->info[index].icondata.flags=WimpIcon_Selected;
 
   // redraw
  pinlist->info[index].icontype&=~pin_type_tinydir;

  // remove pin from icon bar
  tinydir_remove(state,index,0);
  
  pin_redraw(state,index);

  return 1;
}    

void pinboard_move_icons_from_tinydir(_backdrop_state *state,int drop_x,int drop_y)
{
  // convert icons to pinboard icons and relocate them
  int icons=tinydir_count(1,0x70000000);
  int sort[2*icons];
  int i,j,processed=0;
  int minx,min_location;
  WimpGetIconStateBlock istate;

  istate.window_handle=-1;

  // we'll try and be nice and pull the icons in X coordinate order
  // first, obtain the info
  j=0;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_tiny(&pinlist->info[i]) && pin_is_anyfile(&pinlist->info[i])) {
      istate.icon_handle=pinlist->info[i].iconbar_icon;
      wimp_get_icon_state(&istate);
      if (istate.icon.flags & WimpIcon_Selected) {
        sort[j++]=i;
        sort[j++]=istate.icon.bbox.xmin;
      }
    }
  }

  // we now have a list of selected icons and their X offsets
  while (processed<icons) {
    minx=0x70000000;
    min_location=-1;
    for (j=0;j<icons;j++) {
      if (sort[j*2+1]!=-1) {
        if (sort[j*2+1]<minx) {
          minx=sort[j*2+1];
          min_location=j*2;
        }
      }
    }
    
    // sort[min_location]=pin index
    // sort[min_location+1]=x offset    
    sort[min_location+1]=-1; // mask from the next iteration
    
    // relocate the icon

    pinboard_move_icon_from_tinydir(state,sort[min_location],drop_x,drop_y);

    if (drop_x<state->window_width-MAX_ICON_WIDTH) {
      drop_x+=MAX_ICON_WIDTH;
    } else {
      drop_y-=ICON_SPRITE_SIZE+ICON_TEXT_HEIGHT+4;
      drop_x=MAX_ICON_WIDTH/2;
    }
    
    processed++;
  }
}

void backdrop_redraw_all(_backdrop_state *state)
{ 
  // redraw window
  WimpRedrawWindowBlock block;
  int more;
  
  window_get_wimp_handle(0,state->obj,&block.window_handle);
  ((WimpUpdateWindowBlock *)&block)->update_area.xmin=0;
  ((WimpUpdateWindowBlock *)&block)->update_area.xmax=state->window_width;
  ((WimpUpdateWindowBlock *)&block)->update_area.ymin=-state->window_height;
  ((WimpUpdateWindowBlock *)&block)->update_area.ymax=0;
  
  wimp_update_window(&block,&more);
  backdrop_redraw_loop(state,&block,more);
}

int message_fontchanged(WimpMessage *message,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  int i,textwidth,centre;
  IGNORE(message);

  // need to recalculate the widths of the texts for all icons

  for (i=0;i<pinlist->count;i++) {
    if (!pin_is_tiny(&pinlist->info[i])) {
      _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,indirected(pinlist->info[i].icondata.buffer,char *),0,&textwidth);
      textwidth+=8;
      if (textwidth>pinlist->info[i].sprite_x) {
        // actually need to change this one
        centre=pinlist->info[i].icondata.bbox.xmin+(pinlist->info[i].icondata.bbox.xmax-pinlist->info[i].icondata.bbox.xmin)/2;
        pinlist->info[i].icondata.bbox.xmin=centre-textwidth/2;
        pinlist->info[i].icondata.bbox.xmax=centre+textwidth/2;
      }
    }
  }

  if (BD_TEXT_STYLE(state->bd_flags)==BD_TEXT_OUTLINE || BD_TEXT_STYLE(state->bd_flags)==BD_TEXT_BLEND) {
    outline_init(state);
    outline_clear_cache(state);
  }
  
  // redraw the backdrop
  backdrop_redraw_all(state);

  return 1;
}

int pin_new_localfile(_backdrop_state *state,char **path)
{
  char *leaf_name;
  int filetype, type, grid_width, grid_height;
  BBox gap;
  
  if (!fs_leafname_from_path(&leaf_name,*path)) return 0;
  _swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(6),23,*path,&type,&filetype);
  if (type==0) filetype=0;
  
  grid_size(state,&grid_width,&grid_height);

  // create pin for a local file that has appeared (either at startup during check or later)
  gap.xmin=0;
  gap.xmax=grid_width;
  gap.ymin=0;
  gap.ymax=grid_height;
  pin_find_gap(state,state->setting_tidy,state->setting_tidy_stack,&gap);
  
  backdrop_add_pin(state,pin_type_localfile,gap.xmin+grid_width/2,gap.ymin+grid_height/2,PIN_ADD_CENTRED,filetype,&leaf_name,&leaf_name,path,0,0);

  flex_free((flex_ptr)&leaf_name);
  
  return -1;
}

int pin_locate_upcall(_backdrop_state *state,char *fs,char *path,int create_if_missing)
{
  // see if we know this file
  char *fullpath;
  int p;

  if (!upcall_make_filename(fs,path,&fullpath)) return -1;
  
  // find the filename
  p=pin_find_filename(fullpath,0);
  if (p==-1) p=pin_find_filename(fullpath,1);

  if (p==-1 && create_if_missing && strncasecmp(fullpath,"Pinboard:",9)==0) {
    // make a new pin for local file :)
    if (strchr(fullpath,'.')==NULL) p=pin_new_localfile(state,&fullpath);
  }
  
  flex_free((flex_ptr)&fullpath);
  
  return p;
}

int pin_info_updated(_backdrop_state *state,int p)
{
  // file info for this pin has changed
  // update the icon accordingly
  int type,filetype;
  char *leafname;
  char *truncated;
  
  // get leafname
  if (!fs_leafname_from_path(&leafname,indirected(pinlist->info[p].data.filename_offset,char *))) {
    return 0;
  }

  if (!truncate(&leafname,&truncated)) {
    flex_free((flex_ptr)&leafname);
    return 0;
  }
  
  _swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(6),23,indirected(pinlist->info[p].data.filename_offset,char *),&type,&filetype);
  if (type==0) filetype=0; // file has gone walkies


  if (strncasecmp(indirected(pinlist->info[p].data.filename_offset,char *),"Pinboard:",9)==0) {
    pinlist->info[p].icontype=(pinlist->info[p].icontype & ~pin_type_fsfile) | pin_type_localfile;
  } else {
    pinlist->info[p].icontype=(pinlist->info[p].icontype & ~pin_type_localfile) | pin_type_fsfile;
  }
  
  if (pin_is_tiny(&pinlist->info[p])) {

    tinydir_set_sprite(pinlist->info[p].obj,filetype,leafname);
    iconbar_set_text(0,pinlist->info[p].obj,truncated);
    
  } else {
    int textwidth;
    int x,y; // current centre point for the icon
    WimpRedrawWindowBlock block;
    int more;
    BBox old_box;
    
    old_box=pinlist->info[p].icondata.bbox; // bbox may change when sprite/text changes; need to redraw correctly
    x=(old_box.xmax-old_box.xmin)/2+old_box.xmin;
    y=(old_box.ymax-old_box.ymin)/2+old_box.ymin;
    
    pin_set_sprite(state,filetype,&leafname,&pinlist->info[p],0);

    // has the sprite size changed?
    pin_set_spritesize(&pinlist->info[p],indirected(pinlist->info[p].icondata.sprite,char *));
    
    // work out the text width for the icon
    _swix(Wimp_TextOp,_INR(0,2)|_OUT(0),1,truncated,0,&textwidth);
    textwidth+=8;
    if (textwidth<pinlist->info[p].sprite_x) textwidth=pinlist->info[p].sprite_x;
    
    // sort the new bounding box
    pin_set_bbox(state,&pinlist->info[p],textwidth,x,y,PIN_ADD_CENTRED);
    
    // redraw to cover the full changed area
    if (old_box.xmin>pinlist->info[p].icondata.bbox.xmin) old_box.xmin=pinlist->info[p].icondata.bbox.xmin;
    if (old_box.xmax<pinlist->info[p].icondata.bbox.xmax) old_box.xmax=pinlist->info[p].icondata.bbox.xmax;
    if (old_box.ymin>pinlist->info[p].icondata.bbox.ymin) old_box.ymin=pinlist->info[p].icondata.bbox.ymin;
    if (old_box.ymax<pinlist->info[p].icondata.bbox.ymax) old_box.ymax=pinlist->info[p].icondata.bbox.ymax;

    // transfer the new text
    if (strlen(truncated)>strlen(indirected(pinlist->info[p].icondata.buffer,char *))) {
      indirected_remove(pinlist->info[p].icondata.buffer);
      pinlist->info[p].icondata.buffer=indirected_add_fromflex((flex_ptr)&truncated,1+strlen(truncated));
      if (pinlist->info[p].icondata.buffer==INDIRECT_FREEI) {
        // failed to allocate
        msgtrans_report_error("NoMem");
      }
    } else {
      strcpy(indirected(pinlist->info[p].icondata.buffer,char *),truncated);
    }
    
    old_box.ymin=old_box.ymin-state->window_height-iconbar_height;
    old_box.ymax=old_box.ymax-state->window_height-iconbar_height;

    window_get_wimp_handle(0,state->obj,&block.window_handle);
    ((WimpUpdateWindowBlock *)&block)->update_area=old_box;
    wimp_update_window(&block,&more);
    backdrop_redraw_loop(state,&block,more);    
  }

  flex_free((flex_ptr)&truncated);
  flex_free((flex_ptr)&leafname);
  
  return 1;
}

int pin_recheck(_backdrop_state *state,int p)
{
  /* this pin was marked as a 'recheck', so we need to see if the details are valid yet */
  /* returns 1 if now OK, 0 if still invalid */
  char *canon_name=NULL;
  int type;

  if (!fs_canon_nomoan(indirected(pinlist->info[p].data.filename_offset,char *),&canon_name)) return 0;

  /* test the path */
  if (_swix(OS_File,_INR(0,1)|_OUT(0),5,canon_name,&type)!=NULL) type=0;
  if (type==0) {
    flex_free((flex_ptr)&canon_name);
    return 0;
  }

  /* the item is present, so let's refresh the information */
  indirected_remove((int)pinlist->info[p].data.filename_offset);
  pinlist->info[p].data.filename_offset=(int)indirected_add_fromflex((flex_ptr)&canon_name,1+strlen(canon_name));
  flex_free((flex_ptr)&canon_name);

  outline_clear_cache(state); // ensure we redraw the text with the new name later (case may have changed)
  pinlist->info[p].icontype &= ~pin_type_recheck;
  pin_info_updated(state,p);  // get the pin redrawn
  return 1;
}

static void pinboard_help(WimpMessage *message,_backdrop_state *state)
{
  WimpGetWindowStateBlock win;
  int p,x,y;
  IGNORE(state);
  
  win.window_handle=message->data.help_request.window_handle;
  
  // convert mouse coords to work area coords and look for a matching icon
  wimp_get_window_state(&win);
  x=message->data.help_request.mouse_x+win.xscroll-win.visible_area.xmin;
  y=message->data.help_request.mouse_y+win.yscroll-win.visible_area.ymax;

  if ((p=pin_check_click(x,y,state->iconsize))!=-1) {
    // we have an icon
    if (pin_is_window(&pinlist->info[p])) {
      msgtrans_lookup_to("PIcW",message->data.help_reply.text,236);
    } else {
      if (pin_is_anyfile(&pinlist->info[p])) {
        // reply depends on type, wihch depends on the sprite shown...
        if (pinlist->info[p].icondata.sprite==INDIRECT_FREEI) {
          msgtrans_lookup_to("PWH",message->data.help_reply.text,236);
        } else {
          switch (*indirected(pinlist->info[p].icondata.sprite,char *)) {
          case '!': // !App
            msgtrans_lookup_to("PIcA",message->data.help_reply.text,236);
            break;
            
          case 'd': // directory
            msgtrans_lookup_to("PIcD",message->data.help_reply.text,236);
            break;
            
          case 'f': // file_xxx
            msgtrans_lookup_to("PIcF",message->data.help_reply.text,236);
            break;
            
          default:
            msgtrans_lookup_to("PWH",message->data.help_reply.text,236);
            break;
          }
        }
      }
    }
  } else {
    msgtrans_lookup_to("PWH",message->data.help_reply.text,236);
  }

  message->hdr.your_ref=message->hdr.my_ref;
  message->hdr.size=(20+1+strlen(message->data.help_reply.text)+3) &~3;
  message->hdr.action_code=Wimp_MHelpReply;
 
  wimp_send_message(17,message,message->hdr.sender,0,NULL);
}

int message_help(WimpMessage *message,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  int winh,i;
  
  // for dynamic help texts we reply manually
  // essentially just the backdrop window at the moment
  window_get_wimp_handle(0,state->obj,&winh);
  
  if (message->data.help_request.window_handle==winh) {
    pinboard_help(message,state);
    return 1;
  }

  // see if we're over a sticky or sticky tool window
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_sticky(&pinlist->info[i])) {
      if (message->data.help_request.window_handle==pinlist->info[i].data.sticky.textarea_handle) {
        sticky_help(message,state);
        return 1;
      }
/*      window_get_wimp_handle(0,pinlist->info[i].data.sticky.furniture,&winh);
      if (message->data.help_request.window_handle==winh) {
        sticky_furniture_help(message,state);
        return 1;
      }*/
    }
  }
  
  return 0; // unable to handle here, pass it on 
}

void pin_update_filetypeicons(_backdrop_state *state)
{
  // if *IconSprites has happened, we may need to update the filetype icons
  // i) the icon for a file/directory/application may have changed
  // ii) we may now have an option for a file_xxx icon that we didn't have before

  int i,filetype;
  char spritename[13];
  WimpSetIconStateBlock istate;
  istate.window_handle=-1;
  istate.clear_word=0;
  istate.EOR_word=0;

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_tiny(&pinlist->info[i])) {
      // update tinydir icon
      if (pin_is_anyfile(&pinlist->info[i])) { 
        if (_swix(OS_File,_INR(0,1)|_OUT(6),23,indirected(pinlist->info[i].data.filename_offset,char *),&filetype)==NULL) {
          if (filetype>=0 && filetype<=0xfff) {
            sprintf(spritename,"file_%03x",filetype);
            if (wimp_sprite_exists(spritename)) {
              iconbar_set_sprite(0,pinlist->info[i].obj,spritename);
            }
          }
        }
      }
      // redraw the icon
      istate.icon_handle=pinlist->info[i].iconbar_icon;
      wimp_set_icon_state(&istate);
    } else if (!pin_is_sticky(&pinlist->info[i])) {
      // is the icon correct?
      if (pin_is_anyfile(&pinlist->info[i])) {
        if (pinlist->info[i].icondata.sprite!=INDIRECT_FREEI) {
          if (strcmp(indirected(pinlist->info[i].icondata.sprite,char *),"file_xxx")==0) {
            // if we have a valid icon now, update the string
            if (_swix(OS_File,_INR(0,1)|_OUT(6),23,indirected(pinlist->info[i].data.filename_offset,char *),&filetype)==NULL) {
              sprintf(spritename,"file_%03x",filetype);
              if (wimp_sprite_exists(spritename)) {
                // we now have a sprite, good.
                strcpy(indirected(pinlist->info[i].icondata.sprite,char *),spritename);
                // redraw the pin
                pin_redraw(state,i);
              }
            }
          }
          if (strcmp(indirected(pinlist->info[i].icondata.sprite,char *),"application")==0) {
            char *leafname=strrchr(indirected(pinlist->info[i].data.filename_offset,char *),'.');
            if (leafname) {
              if (wimp_sprite_exists(1+leafname)) {
                strcpy(indirected(pinlist->info[i].icondata.sprite,char *),leafname+1);
                pin_redraw(state,i);
              }
            }
          }
        }
      }
 
    }
  }
}
