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

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

#include "tboxlibs/toolbox.h"
#include "tboxlibs/event.h"
#include "tboxlibs/menu.h"
#include "tboxlibs/wimplib.h"
#include "tboxlibs/textarea.h"
#include "tboxlibs/textarea.h"
#include "tboxlibs/colourdbox.h"
#include "tboxlibs/fontdbox.h"
#include "tboxlibs/saveas.h"
#include "tboxlibs/gadgets.h"
#include "tboxlibs/flex.h"

#include "indirected.h"
#include "pinboard.h"
#include "sticky.h"
#include "confirm.h"
#include "msgtrans.h"
#include "events.h"
#include "gadgetlist.h"
#include "paths.h"

extern pins *pinlist;
extern int *tb_sprite_area;

char *sticky_filename=NULL;

#define BAR_HEIGHT 40
#define GADGET_WIDTH 512

static const char *sticky_magic="STKY";
static ObjectId sticky_title_win=0;
int sticky_title_parent=0;

int sticky_furniture_count=0; // number of stickies currently showing furniture

/* sticky file format:
   +0 magic word
   then a set of objects:
*/

typedef enum {
  OBJECT_TYPE_EOF,
  OBJECT_TYPE_STICKY,
} sticky_object_type;

typedef struct {
  int object_type; // type 1 = sticky
  int length;      // length of this block / offset to next object block
  // immediately followed by object data
} sticky_object;

typedef enum {
  STICKY_SUBTYPE_TEXT,
  STICKY_SUBTYPE_COLOUR,
  STICKY_SUBTYPE_FONT,
  STICKY_SUBTYPE_TITLE,
} sticky_subtype;


typedef struct {
  int subtype;
  int length;

  union {
    struct {
      BBox box;        // details of this sticky
      int text_length;
      char text[];
    } sticky_text;
    struct {
      unsigned int fg_colour;
      unsigned int bg_colour;
    } sticky_colour;
    struct {
      int font_x;
      int font_y;
      char font_name[];
    } sticky_font;
    struct {
      char title[1];
    } sticky_title;
  } item;
} sticky_entry;

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

static ObjectId menu_stickymenu=-1;

void plot_trans_sprite(sa_source sprite_area,int x,int y,char *prefix,char *suffix)
{
  int *table;
  int tablesize;
  _kernel_oserror *e;
  char *spriteaddr;
  char sprite[strlen(prefix)+strlen(suffix)+1];
  sprintf(sprite,"%s%s",prefix,suffix);

  switch (sprite_area) {
  case SA_LOCAL:
    _swix(OS_SpriteOp,_INR(0,2)|_OUT(2),256+24,tb_sprite_area,sprite,&spriteaddr);
    break;

  case SA_WIMP:
    {
      int *rom,*ram;
      wimp_base_of_sprites((void **)&rom,(void **)&ram);
      e=_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,ram,sprite,&spriteaddr);
      if (e) {
        e=_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,rom,sprite,&spriteaddr);
        if (e) {
          // sprite not present
          return;
        }
      } 
    }

    break;
  }

  e=_swix(ColourTrans_GenerateTable,_INR(0,7)|_OUT(4),256,spriteaddr,-1,-1,0,1 | (1<<1) | (1<<4),0,0,&tablesize);
  if (e) {
    wimp_report_error(e,1,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    return;
  }
  
  if (tablesize) {
    if (!flex_alloc((flex_ptr)&table,tablesize)) return;
    e=_swix(ColourTrans_GenerateTable,_INR(0,7),256,spriteaddr,-1,-1,table,1 | (1<<1) | (1<<4),0,0);
    
    if (e==NULL) {
      _swix(OS_SpriteOp,_INR(0,7),52+512,256,spriteaddr,x,y,(1<<3) | (1<<5),0,table);
    } else {
      wimp_report_error(e,1,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);    
    }
    flex_free((flex_ptr)&table);
  } else {
    // no table needed
    _swix(OS_SpriteOp,_INR(0,7),52+512,256,spriteaddr,x,y,(1<<3),0,0);
  }
}

int sticky_find_from_handle(int win)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_sticky(&pinlist->info[i])) {
      if (pinlist->info[i].data.sticky.textarea_handle==win) return i;
    }
  }
  return -1;
}

static int sticky_find_from_component(ComponentId c)
{
  int i;
  for (i=0;i<pinlist->count;i++) {
    if (pin_is_sticky(&pinlist->info[i])) {
      if (pinlist->info[i].data.sticky.textarea==c) return i;
    }
  }
  return -1;
}

void sticky_update_bboxes(_backdrop_state *state)
{
  // sticky windows may have moved and we won't know about it.
  // so traverse the list and update the coords
  int i;
  WimpGetWindowOutlineBlock win;
  IGNORE(state);

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_sticky(&pinlist->info[i])) {
      // get the current window bounding box
      win.window_handle=pinlist->info[i].data.sticky.textarea_handle;
      wimp_get_window_outline(&win);
      pinlist->info[i].icondata.bbox=win.outline;
    }
  }
}

#define sticky_extend(a) if (!flex_extend((flex_ptr)&sticky_buffer,(a+buffer_length+3) &~3)) { \
    flex_free((flex_ptr)&sticky_buffer);                                               \
    msgtrans_report_error("StkWrMem");                                                 \
    return 0;                                                                          \
  } else memset(sticky_buffer+buffer_length,0,(a+3)&~3);                               \
  buffer_length+=(a+3)&~3;

int sticky_save(_backdrop_state *state)
{
  // save to sticky_filename
  sticky_object *obj;
  sticky_entry *entry;
  char *sticky_buffer;
  int buffer_length;
  int sticky_object_offset;
  int sticky_subtype_offset;
  int text_length;
  int i;
  _kernel_oserror *e;
  WimpGetWindowStateBlock win;

  window_get_wimp_handle(0,state->obj,&win.window_handle);
  wimp_get_window_state(&win);
  
  sticky_update_bboxes(state);

  if (!flex_alloc((flex_ptr)&sticky_buffer,4)) {
    msgtrans_report_error("StkWrMem");
    return 0;
  }

  memcpy(sticky_buffer,sticky_magic,4);
  buffer_length=4;

  for (i=0;i<pinlist->count;i++) {
    if (pin_is_sticky(&pinlist->info[i])) {
      // make a new sticky object header
      sticky_object_offset=buffer_length;
      sticky_extend(sizeof(sticky_object));

      // set up object header when we know how big to make it

      // get text part
      textarea_get_text(0,state->obj,pinlist->info[i].data.sticky.textarea,NULL,0,&text_length); // get required buffer size
      sticky_subtype_offset=buffer_length;
      sticky_extend(text_length+sizeof(sticky_entry));
      entry=(sticky_entry *)(sticky_buffer+sticky_subtype_offset);

      entry->subtype=STICKY_SUBTYPE_TEXT;
      entry->length=buffer_length-sticky_subtype_offset;

      entry->item.sticky_text.box.xmin=pinlist->info[i].icondata.bbox.xmin;//-win.xscroll+win.visible_area.xmin;
      entry->item.sticky_text.box.xmax=pinlist->info[i].icondata.bbox.xmax;//-win.xscroll+win.visible_area.xmin;
      entry->item.sticky_text.box.ymin=pinlist->info[i].icondata.bbox.ymin;//-win.yscroll+win.visible_area.ymax;
      entry->item.sticky_text.box.ymax=pinlist->info[i].icondata.bbox.ymax;//-win.yscroll+win.visible_area.ymax;
      entry->item.sticky_text.text_length=text_length;
      textarea_get_text(0,state->obj,pinlist->info[i].data.sticky.textarea,entry->item.sticky_text.text,text_length,NULL);

      // next subobj: colour
      sticky_subtype_offset=buffer_length;
      sticky_extend(sizeof(sticky_entry));
      entry=(sticky_entry *)(sticky_buffer+sticky_subtype_offset);
      entry->subtype=STICKY_SUBTYPE_COLOUR;
      entry->length=buffer_length-sticky_subtype_offset;

      textarea_get_colour(0,state->obj,pinlist->info[i].data.sticky.textarea,&entry->item.sticky_colour.fg_colour,&entry->item.sticky_colour.bg_colour);
      
      // next subobj: font
      sticky_subtype_offset=buffer_length;
      sticky_extend(strlen(indirected(pinlist->info[i].data.sticky.font_name,char *))+1+sizeof(sticky_entry));
      entry=(sticky_entry *)(sticky_buffer+sticky_subtype_offset);
      entry->subtype=STICKY_SUBTYPE_FONT;
      entry->length=buffer_length-sticky_subtype_offset;
      entry->item.sticky_font.font_x=pinlist->info[i].data.sticky.font_x;
      entry->item.sticky_font.font_y=pinlist->info[i].data.sticky.font_y;
      strcpy(entry->item.sticky_font.font_name,indirected(pinlist->info[i].data.sticky.font_name,char *));

      // next subobj: title
      if (pinlist->info[i].data.sticky.title!=INDIRECT_FREEI) {
        sticky_extend(strlen(indirected(pinlist->info[i].data.sticky.title,char *))+1+sizeof(sticky_entry));
        entry=(sticky_entry *)(sticky_buffer+sticky_subtype_offset);
        entry->subtype=STICKY_SUBTYPE_TITLE;
        entry->length=buffer_length-sticky_subtype_offset;
        strcpy(entry->item.sticky_title.title,indirected(pinlist->info[i].data.sticky.title,char *));
      }

      // now fill in block length
      obj=(sticky_object *)(sticky_buffer+sticky_object_offset);
      obj->object_type=OBJECT_TYPE_STICKY;
      obj->length=buffer_length-sticky_object_offset;
    }
  }

  // write it all out
  e=_swix(OS_File,_INR(0,2)|_INR(4,5),10,sticky_filename,0xffd,sticky_buffer,sticky_buffer+buffer_length);
  if (e) {
    wimp_report_error(e,1,msgtrans_lookup("_TaskName"));
  }
  flex_free((flex_ptr)&sticky_buffer);

  return 1;
}

void sticky_load(_backdrop_state *state)
{
  // load stickies from the choices file (on startup, basically)
  int length,type,index,pin_index;
  char *buffer;
  sticky_object *obj;
  sticky_entry *entry;

  _kernel_oserror *e;

  _swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(4),5,sticky_filename,&type,&length);
  
  if (type!=1) return;

  if (!flex_alloc((flex_ptr)&buffer,length)) {
    // error
    msgtrans_report_error("StikLoadM");
    return;
  }

  e=_swix(OS_File,_INR(0,3),255,sticky_filename,buffer,0);
  if (e) {
    wimp_report_error(e,1,msgtrans_lookup("_TaskName"));
    flex_free((flex_ptr)&buffer);
    return;
  }
  
  if (strncmp(buffer,sticky_magic,4)!=0) {
    // quietly ignore invalid files
    flex_free((flex_ptr)&buffer);
    return;
  }

  index=4;

  while (index<length) {
    obj=(sticky_object *)(buffer+index);
    int subindex=index+sizeof(sticky_object);
    index+=obj->length;

    switch (obj->object_type) {
      case OBJECT_TYPE_EOF:
        index=length;  // force termination
        break;

      case OBJECT_TYPE_STICKY:
        pin_index=-1;
        // iterate the subobjects inside a sticky
        // we must start with the text subtype
        while (subindex<index) {
          entry=(sticky_entry *)(buffer+subindex);
          switch (entry->subtype) {
            case STICKY_SUBTYPE_TEXT:
              pin_index=sticky_add(state,
                                   entry->item.sticky_text.box.xmin,
                                   entry->item.sticky_text.box.ymin,
                                   entry->item.sticky_text.box.xmax-entry->item.sticky_text.box.xmin,
                                   entry->item.sticky_text.box.ymax-entry->item.sticky_text.box.ymin,
                                   "");
              entry=(sticky_entry *)(buffer+subindex); // as the buffer may have moved
              textarea_set_text(0,state->obj,pinlist->info[pin_index].data.sticky.textarea,entry->item.sticky_text.text);
              break;

            case STICKY_SUBTYPE_COLOUR:
              if (pin_index!=-1) 
                textarea_set_colour(0,state->obj,pinlist->info[pin_index].data.sticky.textarea,
                                    entry->item.sticky_colour.fg_colour,entry->item.sticky_colour.bg_colour);
              break;

            case STICKY_SUBTYPE_FONT:
              if (pin_index!=-1) {
                textarea_set_font(0,state->obj,pinlist->info[pin_index].data.sticky.textarea,
                                  entry->item.sticky_font.font_name,
                                  entry->item.sticky_font.font_x,entry->item.sticky_font.font_y);
                pinlist->info[pin_index].data.sticky.font_x=entry->item.sticky_font.font_x;
                pinlist->info[pin_index].data.sticky.font_y=entry->item.sticky_font.font_y;
                pinlist->info[pin_index].data.sticky.font_name=indirected_add(NULL,1+strlen(entry->item.sticky_font.font_name));
                if (pinlist->info[pin_index].data.sticky.font_name!=INDIRECT_FREEI) {
                  entry=(sticky_entry *)(buffer+subindex); // as the buffer may have moved
                  strcpy(indirected(pinlist->info[pin_index].data.sticky.font_name,char *),entry->item.sticky_font.font_name);

                }
              }
              break;

            case STICKY_SUBTYPE_TITLE:
              if (pin_index!=-1) {
                pinlist->info[pin_index].data.sticky.title=indirected_add(NULL,1+strlen(entry->item.sticky_title.title));
                if (pinlist->info[pin_index].data.sticky.title!=-1) {
                  entry=(sticky_entry *)(buffer+subindex); // as the buffer may have moved
                  strcpy(indirected(pinlist->info[pin_index].data.sticky.title,char *),entry->item.sticky_title.title);
                }
              }
              break;
                
          }
          entry=(sticky_entry *)(buffer+subindex); // as the buffer may have moved
          subindex+=entry->length;
        }
        break;
      
    }
  }
  
  flex_free((flex_ptr)&buffer);
}

void sticky_delete(_backdrop_state *state,int p,int remove_pin)
{
  // delete a sticky and its associated bits
  if (!pin_is_sticky(&pinlist->info[p])) return; // not a sticky, so skip

  if (pinlist->info[p].data.sticky.font_name!=-1) {
    indirected_remove(pinlist->info[p].data.sticky.font_name);
    pinlist->info[p].data.sticky.font_name=INDIRECT_FREEI;
  }

  if (pinlist->info[p].data.sticky.title!=-1) {
    indirected_remove(pinlist->info[p].data.sticky.title);
    pinlist->info[p].data.sticky.title=INDIRECT_FREEI;
  }

  window_remove_gadget(0,state->obj,pinlist->info[p].data.sticky.textarea); // remove the textarea gadget
  if (pinlist->info[p].icondata.flags & WimpIcon_Selected) {
    sticky_furniture_count--;
  }

  // now remove the pin itself
  if (remove_pin) pin_remove(state,p);

  // all done
}

static void sticky_delete_postconfirm(confirm_action action,ObjectId parent,void *handle)
{
  int p;
  _backdrop_state *state=(_backdrop_state *)handle;
  
  if (action==CONFIRM_ACTION_NO) return; // do nothing if cancelled

  // delete the sticky
  // parent == sticky window
  p=sticky_find_from_handle(parent);

  if (p==-1) return; // sticky missing, possibly already deleted

  sticky_delete(state,p,1);
}

static void sticky_identify(_backdrop_state *state,pin *source)
{
  // run the window stack
  WimpGetWindowStateBlock info;
  int parent,child;
  unsigned int flags;

  window_get_wimp_handle(0,state->obj,&parent);

  _swix(Wimp_Extend,_INR(0,1)|_OUT(1),8,parent,&child);
  
  // we now have the details of the first window; ascend the stack
  while (child!=-1) {
    info.window_handle=child;
    wimp_get_window_state2(&info,&parent,&flags);
    if (sticky_find_from_handle(child)==-1) {
      // this one is unadulterated.  So it must be the one we want.
      int newshow[9]; // WimpOpenWindowBlock doesn't have flags at the end
      WimpOpenWindowBlock *ns=(WimpOpenWindowBlock *)newshow;
      ns->window_handle=info.window_handle;
      ns->visible_area=info.visible_area;
      ns->xscroll=info.xscroll;
      ns->yscroll=info.yscroll;
      ns->behind=info.behind;
      newshow[8]=(info.flags &~(WimpWindow_VScroll | WimpWindow_HScroll | WimpWindow_CloseIcon|WimpWindow_BackIcon|WimpWindow_TitleIcon|WimpWindow_SizeIcon)) | WimpWindow_Moveable;
      wimp_open_window2(ns,parent,flags|1); // to update window flags.  Now it's ours.
      source->data.sticky.textarea_handle=info.window_handle;
      return;
    }

    child=info.behind; // go up a level
  }
  
  source->data.sticky.textarea_handle=0; // :(
}

int sticky_add(_backdrop_state *state,int x,int y,int w,int h,char *text)
{
  TextArea gadget;
  _kernel_oserror *e;
  pin newpin;
  int i;
  int parent;
  int fonth;
  WimpGetWindowStateBlock win;
  
  window_get_wimp_handle(0,state->obj,&parent);
  win.window_handle=parent;
  wimp_get_window_state(&win);
  
  memset(&newpin,0,sizeof(pin));
  newpin.icontype=pin_type_sticky;
  newpin.icondata.bbox.xmin=x;
  newpin.icondata.bbox.xmax=x+w;
  newpin.icondata.bbox.ymin=y;
  newpin.icondata.bbox.ymax=y+h;
  newpin.icondata.flags=0;
  newpin.icondata.buffer=INDIRECT_FREEI;
  newpin.icondata.sprite=INDIRECT_FREEI;
  newpin.data.sticky.title=INDIRECT_FREEI;

  memset(&gadget,0,sizeof(TextArea));
  
  // create a new sticky gadget and pin; return the pin index or -1 on error

  x+=win.xscroll-win.visible_area.xmin;
  y+=win.yscroll-win.visible_area.ymax;

  gadget.hdr.flags=TextArea_Scrollbar_Vertical | TextArea_WordWrap;
  gadget.hdr.type=TextArea_Type;
  gadget.hdr.box.xmin=x;
  gadget.hdr.box.xmax=x+w;//GADGET_WIDTH;
  gadget.hdr.box.ymin=y;
  gadget.hdr.box.ymax=y+h;
  gadget.hdr.component_id=-1;
  gadget.hdr.help_message=msgtrans_lookup("StkWin");
  gadget.hdr.max_help=1+strlen(gadget.hdr.help_message);
  gadget.type=0;
  gadget.text=text;
  gadget.event=0;
  gadget.foreground=0;
  gadget.background=0xbaeded00;
  
  if ((e=window_add_gadget(0,state->obj,(Gadget *)&gadget,&newpin.data.sticky.textarea))!=NULL) {
    wimp_report_error(e,0,0,0,0);
    return -1;
  }
  
  sticky_identify(state,&newpin);

  // add to the list
  i=pin_add(&newpin);

  if (i==-1) {
    // failed to add, so destroy sticky
    window_remove_gadget(0,state->obj,newpin.data.sticky.textarea);
    return -1;
  }

  if (state->stickyfont) {
    // set font to user's preference
    char *fontname=NULL;
    char *xbase,*ybase;
    int x,y;

    // get x,y sizes from preference string
    if ((xbase=strchr(state->stickyfont,','))!=NULL) {
      // we have at least 1 size
      x=atoi(xbase+1);
      if ((ybase=strchr(xbase+1,','))!=NULL) {
        // we have two sizes
        y=atoi(ybase+1);
      } else {
        y=x;
      }

      // get fontname
      if (flex_alloc((flex_ptr)&fontname,1+strlen(state->stickyfont))) {
        strcpy(fontname,state->stickyfont);
        if (xbase) *strchr(fontname,',')=0;
      }
        
    } else {
      // we have no size; obtain desktop default
      _swix(Wimp_ReadSysInfo,_IN(0)|_OUT(0),8,&fonth);
      if (fonth!=0) {
        int namelen;
        _swix(Font_ReadDefn,_INR(0,1)|_IN(3)|_OUT(2),fonth,0,0x4c4c5546,&namelen);
        if (namelen<strlen(state->stickyfont)) namelen=strlen(state->stickyfont);
        if (flex_alloc((flex_ptr)&fontname,1+namelen)) {
          _swix(Font_ReadDefn,_INR(0,1)|_OUTR(2,3),fonth,fontname,&x,&y);
          strcpy(fontname,state->stickyfont);
        }

      } else {
        // no font, so set 12 point.
        x=y=12*16;
      }
    }
    if (fontname) {
      textarea_set_font(0,state->obj,newpin.data.sticky.textarea,fontname,x,y);
      pinlist->info[i].data.sticky.font_x=x;
      pinlist->info[i].data.sticky.font_y=y;
      pinlist->info[i].data.sticky.font_name=indirected_add_fromflex((flex_ptr)&fontname,1+strlen(fontname));
      flex_free((flex_ptr)&fontname);
    } else {
      pinlist->info[i].data.sticky.font_name=indirected_add("Homerton.Medium",1+sizeof("Homerton.Medium"));
    }
  } else {
    // set font to desktop font if possible
    _swix(Wimp_ReadSysInfo,_IN(0)|_OUT(0),8,&fonth);
    if (fonth!=0) {
      char *fontname;
      int namelen,x,y;
      _swix(Font_ReadDefn,_INR(0,1)|_IN(3)|_OUT(2),fonth,0,0x4c4c5546,&namelen);
      pinlist->info[i].data.sticky.font_name=indirected_add(NULL,1+namelen);
      if (pinlist->info[i].data.sticky.font_name!=INDIRECT_FREEI) {
        fontname=indirected(pinlist->info[i].data.sticky.font_name,char *);
        _swix(Font_ReadDefn,_INR(0,1)|_OUTR(2,3),fonth,fontname,&x,&y);
        for (namelen=0;fontname[namelen]>31;namelen++);
        fontname[namelen]=0;
        textarea_set_font(0,state->obj,newpin.data.sticky.textarea,fontname,x,y);
        pinlist->info[i].data.sticky.font_x=x;
        pinlist->info[i].data.sticky.font_y=y;
      }
    } else {
      pinlist->info[i].data.sticky.font_name=INDIRECT_FREEI;
    }
  }
  // we need to find out the window handle so we can save details later (if it moves) and sort the title and resize icons
  // can't do this from TextArea so we run the window stack and look for a window which is missing the title icon
  // we can then tweak it
  
  // need to resize the window now to get the exact desired width and coords...

  WimpGetWindowInfoBlock info;
  info.window_handle=pinlist->info[i].data.sticky.textarea_handle;
  wimp_get_window_info(&info);
  info.window_data.extent.xmax=info.window_data.extent.xmin+w;
  wimp_set_extent(info.window_handle,&info.window_data.extent);

  info.window_data.visible_area.xmax=pinlist->info[i].icondata.bbox.xmax;
  
  wimp_open_window((WimpOpenWindowBlock *)&info);

  return i;
}

void sticky_help(WimpMessage *message,_backdrop_state *state)
{
  IGNORE(state);
  message->hdr.action_code=Wimp_MHelpReply;
  msgtrans_lookup_to("StkWin",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;
  wimp_send_message(17,message,message->hdr.sender,0,NULL);
}
/*
void sticky_move_to_bbox(_backdrop_state *state,int icon)
{
  WimpGetWindowStateBlock win;
  // move and adjust sticky size to fit a desired bbox
  WimpOpenWindowBlock open;
  WimpGetWindowOutlineBlock outline;
  BBox offsets;

  window_get_wimp_handle(0,state->obj,&win.window_handle);
  wimp_get_window_state(&win);
  
  open.window_handle=pinlist->info[icon].data.sticky.textarea_handle;
  wimp_get_window_state((WimpGetWindowStateBlock *)&open); // so we have the correct position in the stack

  open.visible_area=pinlist->info[icon].icondata.bbox;

  wimp_open_window(&open);
  
  // we may need to nudge it a bit to get the correct position/size
  // get outline of the new sticky
  outline.window_handle=pinlist->info[icon].data.sticky.textarea_handle;
  wimp_get_window_outline(&outline);
  
  // compare with the desired outline and reopen at the right size/position
  offsets.xmin=pinlist->info[icon].icondata.bbox.xmin-outline.outline.xmin;
  offsets.ymin=pinlist->info[icon].icondata.bbox.ymin-outline.outline.ymin;
  offsets.xmax=pinlist->info[icon].icondata.bbox.xmax-outline.outline.xmax;
  offsets.ymax=pinlist->info[icon].icondata.bbox.ymax-outline.outline.ymax;
  
  // need to resize the window now to get the exact desired width and coords...
  win.window_handle=pinlist->info[icon].data.sticky.textarea_handle;
  wimp_get_window_state(&win);
  win.visible_area.xmin+=offsets.xmin;
  win.visible_area.xmax+=offsets.xmax;
  win.visible_area.ymin+=offsets.ymin;
  win.visible_area.ymax+=offsets.ymax;
  
  wimp_open_window((WimpOpenWindowBlock *)&win);
}*/

static int sticky_close(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  FilterCloseEvent *ev=(FilterCloseEvent *)event;
  int length;
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(event_code);
  IGNORE(id);

  // find the sticky
  int p=sticky_find_from_handle(ev->window_handle);
  if (p==-1) return 0;

  // if sticky is empty, just remove it
  textarea_get_text(0,state->obj,pinlist->info[p].data.sticky.textarea,NULL,0,&length);

  if (length<2) {
    sticky_delete(state,p,1);
  } else {
    // remove this one if confirmed...
    confirm(msgtrans_lookup("StkDelMsg"),msgtrans_lookup("StkDelDel"),msgtrans_lookup("StkDelCan"),sticky_delete_postconfirm,ev->window_handle,state);
  }
  return 1;
}

static int sticky_menu_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 StkMenu_SaveAll:
      sticky_save(state);
      break;

    case StkMenu_Delete:
      {
        int i=sticky_find_from_component(id->ancestor_component);
        if (i!=-1) {
          confirm(msgtrans_lookup("StkDelMsg"),msgtrans_lookup("StkDelDel"),msgtrans_lookup("StkDelCan"),sticky_delete_postconfirm,pinlist->info[i].data.sticky.textarea_handle,state);
        }
      }
      break;
  }
  return 1;
}

static int sticky_toolclick(int event_code,WimpPollBlock *poll,IdBlock *id,void *handle)
{
  // start a window move drag
  WimpDragBox drag;
  WimpGetWindowStateBlock win;

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

  drag.wimp_window=sticky_title_parent;
  drag.drag_type=Wimp_DragBox_DragWindow;
  wimp_drag_box(&drag);

  return 1;
}

static void sticky_title_set_extents(int w,int h,int gadget_only)
{
  BBox extent;
  extent.xmin=0;
  extent.xmax=w;
  extent.ymin=-h;
  extent.ymax=0;

  if (!gadget_only) {
    window_set_extent(0,sticky_title_win,&extent);
  }

  gadget_move_gadget(0,sticky_title_win,StkTool_Title,&extent);
}

static int sticky_openwindow(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  FilterOpenEvent *e=(FilterOpenEvent *)event;
  WimpGetWindowOutlineBlock win;
  WimpGetWindowStateBlock toolstate;

  wimp_open_window(&e->open_window);

  // sort out extents for the gadget and tool window
  // find window outline
  win.window_handle=e->open_window.window_handle;
  wimp_get_window_outline(&win);
  sticky_title_set_extents(e->open_window.visible_area.xmax-e->open_window.visible_area.xmin-20,win.outline.ymax-e->open_window.visible_area.ymax,0);

  // reopen to new visible area size
  window_get_wimp_handle(0,sticky_title_win,&toolstate.window_handle);
  wimp_get_window_state(&toolstate);
  toolstate.visible_area.xmax=toolstate.visible_area.xmin+e->open_window.visible_area.xmax-e->open_window.visible_area.xmin-20;
  toolstate.xscroll=0;
  wimp_open_window((WimpOpenWindowBlock *)&toolstate);
  return 1;
}

void sticky_init(_backdrop_state *state)
{
  _kernel_oserror *e;

  if (!flex_alloc((flex_ptr)&sticky_filename,1+sizeof(STICKY_FILENAME))) {
    msgtrans_report_error("NoMem");
    exit(1);
  }

  strcpy(sticky_filename,STICKY_FILENAME);

  event_register_toolbox_handler(-1,TEvent_Sticky_CloseClick,sticky_close,state);

  e=toolbox_create_object(0,"StickyMenu",&menu_stickymenu);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    menu_stickymenu=-1;
    return;
  }
  
  e=toolbox_create_object(0,"stickytool",&sticky_title_win);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    sticky_title_win=-1;
    return;
  }

  event_register_toolbox_handler(menu_stickymenu,Menu_Selection,sticky_menu_choice,state);
  event_register_wimp_handler(sticky_title_win,Wimp_EMouseClick,sticky_toolclick,NULL);
  event_register_toolbox_handler(-1,TEvent_Sticky_Open,sticky_openwindow,NULL);
}

void sticky_menu_click(_backdrop_state *state, WimpPollBlock *poll)
{
  int icon;
  icon=sticky_find_from_handle(poll->mouse_click.window_handle);

  if (icon!=-1 && menu_stickymenu!=-1) {
    unsigned int fg,bg;
    int colour_block[2];
    ObjectId dbox;

    toolbox_show_object(1,menu_stickymenu,4,0,state->obj,pinlist->info[icon].data.sticky.textarea);

    textarea_get_colour(0,state->obj,pinlist->info[icon].data.sticky.textarea,&fg,&bg);

    menu_get_sub_menu_show(0,menu_stickymenu,StkMenu_FG,&dbox);
    colour_block[0]=fg;
    colour_block[1]=0;
    colourdbox_set_colour(0,dbox,&colour_block[0]);

    menu_get_sub_menu_show(0,menu_stickymenu,StkMenu_BG,&dbox);
    colour_block[0]=bg;
    colour_block[1]=0;
    colourdbox_set_colour(0,dbox,&colour_block[0]);

    menu_get_sub_menu_show(0,menu_stickymenu,StkMenu_Title,&dbox);
    writablefield_set_value(0,dbox,StkTitle_Title,pinlist->info[icon].data.sticky.title==INDIRECT_FREEI?"":indirected(pinlist->info[icon].data.sticky.title,char *));
  }
}

int sticky_update_colour(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  IGNORE(event_code);
  ColourDboxColourSelectedEvent *e=(ColourDboxColourSelectedEvent *)event;

  // iterate the list and update any selected stickies
  _backdrop_state *state=(_backdrop_state *)handle;
  int i;
  unsigned int fg,bg;

  i=sticky_find_from_component(id->ancestor_component);
  if (i!=-1) {
    // get current colours
    textarea_get_colour(0,state->obj,pinlist->info[i].data.sticky.textarea,&fg,&bg);
    // update colours
    if (id->parent_component==StkMenu_FG) {
      textarea_set_colour(0,state->obj,pinlist->info[i].data.sticky.textarea,e->colour_block[0],bg);
    } else {
      textarea_set_colour(0,state->obj,pinlist->info[i].data.sticky.textarea,fg,e->colour_block[0]);
    }

  }

  return 1;
}

static char *sticky_save_buffer=NULL;
static int sticky_save_length=0;

/* sticky data export */
int sticky_save_tobeshown(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(handle);

  _kernel_oserror *e;
  unsigned int sel_start,sel_end;


  if (sticky_save_buffer) {
    flex_free((flex_ptr)&sticky_save_buffer);
    sticky_save_buffer=NULL;
  }

  // work out if there's a selection available
  e=textarea_get_text(0,id->ancestor_id,id->ancestor_component,NULL,0,&sticky_save_length);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    return 1;
  }

  if (!flex_alloc((flex_ptr)&sticky_save_buffer,sticky_save_length)) {
    msgtrans_report_error("NoMem");
    return 1;
  }

  e=textarea_get_text(0,id->ancestor_id,id->ancestor_component,sticky_save_buffer,sticky_save_length,NULL);
  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
    flex_free((flex_ptr)&sticky_save_buffer);
    sticky_save_buffer=NULL;
    return 1;
  }
  
  e=textarea_get_selection_points(0,id->ancestor_id,id->ancestor_component,&sel_start,&sel_end);

  if (e || sel_start==sel_end) {
    saveas_selection_available(0,id->self_id,0);
  } else {
    saveas_selection_available(0,id->self_id,1);
  }

  sticky_save_length-=1;
  saveas_set_file_size(0,id->self_id,sticky_save_length); // lose trailing null terminator
  return 1;
}

int sticky_save_fill(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  SaveAsFillBufferEvent *e=(SaveAsFillBufferEvent *)event;
  IGNORE(event_code);
  IGNORE(handle);
  
  if (e->hdr.flags & 1) {
    // save selection only
    unsigned int sel_start,sel_end;

    textarea_get_selection_points(0,id->ancestor_id,id->ancestor_component,&sel_start,&sel_end);
    sel_start+=e->no_bytes;

    saveas_buffer_filled(0,id->self_id,sticky_save_buffer+sel_start,sel_end-sel_start);
  } else {
    saveas_buffer_filled(0,id->self_id,sticky_save_buffer+e->no_bytes,sticky_save_length-e->no_bytes);
  }
  return 1;
}

int sticky_save_to_file(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  SaveAsSaveToFileEvent *e=(SaveAsSaveToFileEvent *)event;
  IGNORE(event_code);
  IGNORE(handle);
  
  if (e->hdr.flags & 1) {
    // save selection only
    unsigned int sel_start,sel_end;
    textarea_get_selection_points(0,id->ancestor_id,id->ancestor_component,&sel_start,&sel_end);
    _swix(OS_File,_INR(0,5),10,e->filename,0xfff,0,sticky_save_buffer+sel_start,sticky_save_buffer+sel_end);
  } else {
    _swix(OS_File,_INR(0,5),10,e->filename,0xfff,0,sticky_save_buffer,sticky_save_buffer+sticky_save_length);
  }
  saveas_file_save_completed(1,id->self_id,e->filename);
  return 1;
}

int sticky_save_completed(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  IGNORE(event_code);
  IGNORE(event);
  IGNORE(id);
  IGNORE(handle);
  
  if (sticky_save_buffer) {
    flex_free((flex_ptr)&sticky_save_buffer);
    sticky_save_buffer=NULL;
    sticky_save_length=0;
  }
  return 1;
}

static void sticky_set_furniture(_backdrop_state *state,int handle,int index,int add)
{
  WimpGetWindowStateBlock info;
  WimpOpenWindowBlock *ns=(WimpOpenWindowBlock *)&info;
  WimpGetWindowInfoBlock fullinfo;
  WimpGetWindowOutlineBlock win;
  int parent;
  unsigned int flags;

  window_get_wimp_handle(0,state->obj,&parent);

  info.window_handle=handle;
  fullinfo.window_handle=handle;
  wimp_get_window_state2(&info,&parent,&flags);
  wimp_get_window_info(&fullinfo); // we need the first call so we can get the flags

  // check window extent in case it has been shrunk horizontally (happens with drags to select inside for some reason)
  if (fullinfo.window_data.extent.xmax-fullinfo.window_data.extent.xmin < GADGET_WIDTH) {
    // adjust the extent
    fullinfo.window_data.extent.xmax=fullinfo.window_data.extent.xmin+GADGET_WIDTH;
    wimp_set_extent(fullinfo.window_handle,&fullinfo.window_data.extent);
  }

  // ensure that we are within reasonable bounds
  if (info.visible_area.xmax<64) {
    int w=info.visible_area.xmax-info.visible_area.xmin;
    // shunt to the right
    info.visible_area.xmax=64;
    info.visible_area.xmin=64-w;
  }

  if (info.visible_area.xmin>state->window_width-96) {
    int w=info.visible_area.xmax-info.visible_area.xmin;
    // shunt to the left
    info.visible_area.xmin=state->window_width-96;
    info.visible_area.xmax=info.visible_area.xmin+w;
  }

  if (info.visible_area.ymax>state->window_height+iconbar_height-52) {
    int h=info.visible_area.ymax-info.visible_area.ymin;
    // shunt down
    info.visible_area.ymax=state->window_height+iconbar_height-52;
    info.visible_area.ymin=info.visible_area.ymax-h;
  }

  if (info.visible_area.ymax<iconbar_height+52) {
    int h=info.visible_area.ymax-info.visible_area.ymin;
    // shunt up
    info.visible_area.ymax=iconbar_height+52;
    info.visible_area.ymin=info.visible_area.ymax-h;
  }

  if (add) {
    info.flags|=(WimpWindow_VScroll | WimpWindow_HScroll | WimpWindow_CloseIcon|WimpWindow_BackIcon|WimpWindow_TitleIcon|WimpWindow_SizeIcon | WimpWindow_Moveable);
  } else {
    info.flags&=~(WimpWindow_VScroll | WimpWindow_HScroll | WimpWindow_CloseIcon|WimpWindow_BackIcon|WimpWindow_TitleIcon|WimpWindow_SizeIcon | WimpWindow_Moveable);
  }
  wimp_open_window2(ns,parent,flags|1);

  // update outline
  win.window_handle=pinlist->info[index].data.sticky.textarea_handle;
  wimp_get_window_outline(&win);
  pinlist->info[index].icondata.bbox=win.outline;

  if (add) {
    // add title if there is one
    if (sticky_title_win!=-1 && pinlist->info[index].data.sticky.title!=INDIRECT_FREEI) {
      sticky_title_parent=handle;
      WindowShowObjectBlock show;
      button_set_value(0,sticky_title_win,StkTool_Title,indirected(pinlist->info[index].data.sticky.title,char *));
      show.visible_area.xmin=win.outline.xmin+80;
      show.visible_area.xmax=win.outline.xmax;
      show.visible_area.ymin=ns->visible_area.ymax; // already set earlier
      show.visible_area.ymax=win.outline.ymax;
      sticky_title_set_extents(show.visible_area.xmax-show.visible_area.xmin,show.visible_area.ymax-show.visible_area.ymin,0);
      show.xscroll=0;
      show.yscroll=0;
      show.behind=-1;
      show.window_flags=(1<<23) | (1<<31) | (1<<4) | (1<<6);
      show.parent_window_handle=handle;
      show.alignment_flags=(1<<16) | (2<<18) | (2<<20) | (2<<22) | (1<<0);
      toolbox_show_object(Toolbox_ShowObject_AsSubWindow,sticky_title_win,Toolbox_ShowObject_FullSpec,&show,0,0);
    }
  } else {
    // hide title if it is attached to the current window
    window_get_wimp_handle(0,sticky_title_win,&info.window_handle);
    wimp_get_window_state2(&info,&parent,&flags);
    if (parent==handle) {
      toolbox_hide_object(0,sticky_title_win);
      sticky_title_parent=0;
    }
  }

}

void sticky_check_furniture(_backdrop_state *state)
{
  int i;

      for (i=0;i<pinlist->count;i++) {
        if (pin_is_sticky(&pinlist->info[i])) {
          if (pinlist->info[i].icondata.flags & WimpIcon_Selected) {
            pinlist->info[i].icondata.flags &=~ WimpIcon_Selected;
            sticky_set_furniture(state,pinlist->info[i].data.sticky.textarea_handle,i,0);
            sticky_furniture_count--;
          }
        }
      }
}

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

  window_get_wimp_handle(0,sticky_title_win,&tools);
  if (event->pointer_leaving_window.window_handle==tools) return 1;

  icon=sticky_find_from_handle(event->pointer_leaving_window.window_handle);

  switch (event_code) {
    case Wimp_EPointerEnteringWindow:
      // anyone have furniture?
      sticky_check_furniture(state);

      // if we are entering a sticky, we need to add some furniture
      if (icon!=-1) {
        pinlist->info[icon].icondata.flags |= WimpIcon_Selected;
        sticky_set_furniture(state,event->pointer_leaving_window.window_handle,icon,1);
        sticky_furniture_count++;
      }
    break;
  }

  return 1;
}

int sticky_font_shown(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  IGNORE(handle);
  IGNORE(event);
  IGNORE(event_code);

  int icon=sticky_find_from_component(id->ancestor_component);
  if (icon!=-1) {
    fontdbox_set_size(3,id->self_id,pinlist->info[icon].data.sticky.font_y/16,(100*pinlist->info[icon].data.sticky.font_x)/pinlist->info[icon].data.sticky.font_y);
    if (pinlist->info[icon].data.sticky.font_name!=INDIRECT_FREEI) {
      fontdbox_set_font(0,id->self_id,indirected(pinlist->info[icon].data.sticky.font_name,char *));
    }
  } else {
    // no longer a valid sticky
    toolbox_hide_object(0,id->self_id);
  }

  return 1;
}

int sticky_update_font(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  FontDboxApplyFontEvent *e=(FontDboxApplyFontEvent *)event;
  IGNORE(handle);
  IGNORE(event_code);

  int icon=sticky_find_from_component(id->ancestor_component);
  if (icon!=-1) {
    pinlist->info[icon].data.sticky.font_y=e->height*16;
    pinlist->info[icon].data.sticky.font_x=e->height*16*e->aspect/100;
    if (pinlist->info[icon].data.sticky.font_name==INDIRECT_FREEI) {
      pinlist->info[icon].data.sticky.font_name=indirected_add(e->font,1+strlen(e->font));
    } else {
      indirected_update(pinlist->info[icon].data.sticky.font_name,e->font,strlen(e->font)+1);
    }

    textarea_set_font(0,id->ancestor_id,id->ancestor_component,indirected(pinlist->info[icon].data.sticky.font_name,char *),pinlist->info[icon].data.sticky.font_x,pinlist->info[icon].data.sticky.font_y);

  }


  return 1;
}

static void sticky_insert_text(_backdrop_state *state,int icon,char *buffer)
{
  // delete any selection if present
  unsigned int start,end;
  textarea_get_selection_points(0,state->obj,pinlist->info[icon].data.sticky.textarea,&start,&end);
  if (start!=end) {
    textarea_replace_text(0,state->obj,pinlist->info[icon].data.sticky.textarea,start,end,"");
  }

  // and now insert the desired bit
  textarea_get_cursor_position(0,state->obj,pinlist->info[icon].data.sticky.textarea,&start);
  textarea_insert_text(0,state->obj,pinlist->info[icon].data.sticky.textarea,start,buffer);

  // and move cursor to the end of the insertion
  textarea_set_cursor_position(1,state->obj,pinlist->info[icon].data.sticky.textarea,start+strlen(buffer),NULL);
}

int sticky_dataload(WimpMessage *message,_backdrop_state *state)
{
  // insert this into a sticky
  _kernel_oserror *e=NULL;
  char *buffer;
  int length;
  int filetype;
  int icon;

  // find the sticky
  icon=sticky_find_from_handle(message->data.data_load.destination_window);
  if (icon==-1) return 1; // not found; abort

  if (akbd_pollsh()) {
    // just the filename
    sticky_insert_text(state,icon,message->data.data_load.leaf_name);
  } else {
    e=_swix(OS_File,_INR(0,1)|_OUT(4)|_OUT(6),23,message->data.data_load.leaf_name,&length,&filetype);
    if (e==NULL) {
      if (filetype!=0xfff) {
        msgtrans_report_error("EStkFFF");
        return 1;
      } else {
        if (!flex_alloc((flex_ptr)&buffer,length+1)) {
          msgtrans_report_error("NoMem");
          return 1;
        }
      }
      e=_swix(OS_File,_INR(0,3),255,message->data.data_load.leaf_name,buffer,0);
      buffer[length+1]=0; // ensure null termination
      if (e==NULL) {
        sticky_insert_text(state,icon,buffer);
      }
      flex_free((flex_ptr)&buffer);
    }
  }

  if (e) {
    wimp_report_error(e,0,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
  } else {
    // send Ack
    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);
  }

  return 1;
}

int sticky_title_action(int event_code,ToolboxEvent *event,IdBlock *id,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  int icon=sticky_find_from_component(id->ancestor_component);
  IGNORE(event_code);

  if (icon==-1) return 1;

  switch (id->self_component) {
    case StkTitle_OK:
      {
        int length;

        // get new title
        writablefield_get_value(0,id->self_id,StkTitle_Title,NULL,0,&length);
        if (length<2) {
          // remove existing title
          if (pinlist->info[icon].data.sticky.title!=INDIRECT_FREEI) {
            indirected_remove(pinlist->info[icon].data.sticky.title);
            pinlist->info[icon].data.sticky.title=INDIRECT_FREEI;
          }
        } else {
          if (pinlist->info[icon].data.sticky.title==INDIRECT_FREEI) {
            if ((pinlist->info[icon].data.sticky.title=indirected_add(NULL,length))==-1) return 1;
          } else {
            if (!indirected_update(pinlist->info[icon].data.sticky.title,NULL,length)) return 1;
          }
          writablefield_get_value(0,id->self_id,StkTitle_Title,indirected(pinlist->info[icon].data.sticky.title,char *),length,NULL);
        }
      }

    case StkTitle_Cancel:
      if ((event->hdr.flags & ActionButton_Selected_Adjust)==0) {
        toolbox_hide_object(0,menu_stickymenu);
      }
      break;
  }

  return 1;
}

