//Today - the revised version

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "kernel.h"
#include "swis.h"
#include "flex.h"

#include "sflib/debug.h"

//Include the toolbox stuff
#include "colourdbox.h"
#include "dcs.h"
#include "drawfile.h"
#include "event.h"
#include "fileinfo.h"
#include "fontmenu.h"
#include "iconbar.h"
#include "menu.h"
#include "printdbox.h"
#include "quit.h"
#include "saveas.h"
#include "scale.h"
#include "toolbox.h"
#include "wimp.h"
#include "wimplib.h"

//Structure to hold data for a record
typedef struct {
  //If year is unknown_year it is unknown
  //If month or day are 0 they match to all months/days
  //If month or day are <0 they are unknown
  //Code if 1-7 for days, 8 for continue and 0 otherwise
  int year,month,day,type,code,startline,endline,fromown,textoff;
} REC_DATA;

//Structure to hold information for a list of records
typedef struct {
  ObjectId window,toolbar;
  //listdates flag is nonzero if month and day are to be listed in window
  int records,maxrecords,toolbarheight,listdates;
  REC_DATA *recorddata;
  //This block stores all the text for the window
  char *text;
  int curoff,maxoff;
} WIN_DATA;

//Structure to hold text strings
typedef struct {
  char str[32];
} STRING;

#include "main.h"

#define WimpVersion    310
#define recordstep 50
#define recordlimit 500
#define mintext 512
#define textstep 512
#define unknown_year -1000000
#define maxwordsinstring 256
static  WimpPollBlock  poll_block;
static  IdBlock        id_block;
int *sprite_area,version;
char monthnames[13][4];
static int daysinmonth[13]={0,31,29,31,30,31,30,31,31,30,31,30,31};
static char monthfiles[13][4]={"own","jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"};
MessagesFD messages[2]; /* = {NULL, NULL}; */
//FILE *debug;
WIN_DATA scanwin,srchwin,addwin,editwin;

//Lists of all of the types used by the program, 0=general list
//1=list for first data set to be joined, 2=list for second data set
#define maxeventlen 27
STRING eventlists[3][maxeventlen];
int lenofeventlists[3],eventlistmaps[3][maxeventlen];

#define numofwordlists 4
STRING *wordlist[numofwordlists];
int wordlistlen[numofwordlists];
int *thesauruslist;
char wordlisttoken[numofwordlists][3]={{'r','e',0},
                                       {'i','g',0},
                                       {'v','b',0},
                                       {'t','h',0}};

ObjectId iconbar_id,joinwindow,typemenu;
ComponentId iconbar_component;

int yearwidth,yeartextwidth,monthwidth,daywidth,daytextwidth,typewidth;
int windowwidth,highlightstate=0;
int ignoretypes,ignoreyears;
char todaypaths[2][256];

int scanday,scanmonth,scandayofweek;

int srchmask,srchrange,srchcasesen,srchdone=0;
int srchyear1,srchmonth1,srchday1,srchyear2,srchmonth2,srchday2;
char srchstring[64];

WIN_DATA *sortwin[2];

int numofwords[2],stringwords[2][maxwordsinstring];
char *sortlisttext;

//Lookup table of chars so punctuation is ignored and accents are removed
const char lookup[256]={
            0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
            0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
           ' ', 1 , 1 , 1 , 1 , 1 ,'&' , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
           '0','1','2','3','4','5','6','7','8','9', 1 , 1 , 1 , 1 , 1 , 1 ,
            1 ,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
           'P','Q','R','S','T','U','V','W','X','Y','Z', 1 , 1 , 1 , 1 , 1 ,
            1 ,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
           'P','Q','R','S','T','U','V','W','X','Y','Z', 1 , 1 , 1 , 1 , 1 ,
            1 ,'W','W', 1 , 1 ,'Y','Y', 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
           'A','A','A','A','A','A', 1 ,'C','E','E','E','E','I','I','I','I',
           'D','N','O','O','O','O','O', 1 ,'0','U','U','U','U','Y','P','S',
           'A','A','A','A','A','A', 1 ,'C','E','E','E','E','I','I','I','I',
           'D','N','O','O','O','O','O', 1 ,'0','U','U','U','U','Y','P','Y'};

//Lookup table of chars so punctuation is ignored and accents are removed, returns 2 if letter is lowercase or a number
//used when looking for initials
const char lookup2[256]={
            0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
            0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ,
            1 , 1 , 1 , 1 , 1 , 1 , 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 ,'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
           'P','Q','R','S','T','U','V','W','X','Y','Z', 1 , 1 , 1 , 1 , 1 ,
            1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ,
            2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 1 , 1 , 1 , 1 , 1 ,
            1 ,'W', 2 , 1 , 1 ,'Y', 2 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
            1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 ,
           'A','A','A','A','A','A', 1 ,'C','E','E','E','E','I','I','I','I',
           'D','N','O','O','O','O','O', 1 , 2 ,'U','U','U','U','Y','P', 2 ,
            2 , 2 , 2 , 2 , 2 , 2 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 ,
            2 , 2 , 2 , 2 , 2 , 2 , 2 , 1 , 2 , 2 , 2 , 2 , 2 , 2 , 2 , 2 };

//Lookup table of chars so punctuation is ignored and accents are removed
const char lookup3[256]={
             0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15,
            16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
           ' ','!','\"','#','$','%','&','\'','(',')','*','+',',','-','.','/',
           '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?',
           '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
           'P','Q','R','S','T','U','V','W','X','Y','Z','[','\\',']','^','_',
           '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o',
           'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',127,
           '','','','','','','','','','','','','','','','',
           '','','','','','','','','','','','','','','','',
           '','','','','','','','','','','','','','','','',
           '','','','','','','','','','','','','','','','',
           'A','A','A','A','A','A','','C','E','E','E','E','I','I','I','I',
           'D','N','O','O','O','O','O','','0','U','U','U','U','Y','P','S',
           'a','a','a','a','a','a','','c','e','e','e','e','i','i','i','i',
           'd','n','o','o','o','o','o','','0','u','u','u','u','y','p','y'};

//Compares two strings
int caseinsencmp(char *str1,char *str2)
{
  int offset=0;
  do {
    if (str1[offset]<32) {
      if (str2[offset]<32) {
        return 1;
      }
      else
      {
        return 0;
      }
    }
    if (str2[offset]<32) {return 0;}
    if (toupper(str1[offset])!=toupper(str2[offset])) {return 0;}
    offset++;
  } while(1);

  return 1;
}

//Routine to move to the start of the next word in the string
int movetonextword(char *string,int offset)
{
  while (lookup[string[offset]]==1 || lookup[string[offset]]==32) {offset++;}
  return offset;
}

//Routine to move to the char after the end of the current word
int movetoendofword(char *string,int offset)
{
  while (lookup[string[offset]]>32) {offset++;}
  return offset;
}

//Routine to test words at start of strings are same, if verbcheck is non-zero
//and words do not match it checks to see if the endings are in the verb list
//The term chars are th upper limits on the characters that cause termination
//setting to 32 means the strings do not terminate on spaces
int comparewords(char *str1,char *str2,int verbcheck,int termchar1,int termchar2)
{
  int offset=0,c1,c2,alluppercase1=1,alluppercase2=1;
  do {
    //Check if word contains lower case letters
    if (lookup2[str1[offset]]==2) {alluppercase1=0;}
    if (lookup2[str2[offset]]==2) {alluppercase2=0;}

    c1=lookup[str1[offset]];
    c2=lookup[str2[offset++]];
    //Check to see if both words have finished (and are equal)
    if (c1<=termchar1 && c2<=termchar2) {return 0;}
  } while (c1==c2);

  //Check for terminators so that verb ending check matches terminators
  if (c1<=termchar1) {c1=0;}
  if (c2<=termchar2) {c2=0;}

  //Check if verb check is to be done
  //Do not do a verb check if at a space and not terminating on spaces
  //Do not do a verb check if all of word is uppercase
  if (offset>1 && verbcheck!=0) {
    //See if string1 is terminated by a verb
    if (c1>32 && alluppercase1==0 && findwordinlist(str1+offset-1,2,0,termchar1,31)>=0) {c1=0;}
    //See if string2 is terminated by a verb
    if (c2>32 && alluppercase2==0 && findwordinlist(str2+offset-1,2,0,termchar2,31)>=0) {c2=0;}
  }

  //Check if word1 is greater
  if (c1>c2) {return 2;}
  //Check if word2 is greater
  if (c2>c1) {return -2;}
  return 0;
}

//Routine to find a word in a list of words (return index or -1 if not present)
int findwordinlist(char *string,int num,int verbcheck,int termchar1,int termchar2)
{
  int checkpos,firstpos,lastpos;

  //Set limits of possible positions
  firstpos=0;
  lastpos=wordlistlen[num];
  //Keep checking as long as there is a possible range
  while (firstpos<lastpos) {
    //Do a binary chop so check the position in middle of range
    checkpos=(firstpos+lastpos)/2;
    //Compare required result to check position
    switch (comparewords(string,wordlist[num][checkpos].str,verbcheck,termchar1,termchar2)) {
      case -2:
        //String < Check position so any match must be before checkpos
        lastpos=checkpos;
        break;
      case  0:
        //String matches, but check following items in case there are several
        //that match (eg United States and United States Of America)
        while (checkpos+1<lastpos && comparewords(string,wordlist[num][checkpos+1].str,verbcheck,termchar1,termchar2)==0) {checkpos++;}
        return checkpos;
        break;
      case  2:
        //String > Check position so any match must be after checkpos
        firstpos=checkpos+1;
        break;
    }
  }
  return -1;
}

//Routine to test words at start of strings are same, if verbcheck is non-zero and words do not match it checks to see
//if the endings are in the verb list
int comparewordswiththesaurus(char *str1,int offset1,char *str2,int offset2)
{
  if (offset1<0 || offset2<0) {
    //One or both words are in thesaurus so check is easy
    //This sorts words into order of words in thesaurus from Z to A and then
    //words not in thesaurus from A to Z
    if (offset1>offset2) {return 2;}
    if (offset1<offset2) {return -2;}
    return 0;
  }

  //Neither word is in the thesaurus so do the full comparison of words
  return comparewords(str1+offset1,str2+offset2,1,32,32);
}

//Parse string by removing hard spaces, doubles/leading/trailing spaces
//Also fix quote marks
int parsestring(char *string)
{
  int offset1,offset2=0,offset3=0,nextchar,lastchar=32,counter=0,len,loop;
  char *offsettoreplace;
  for (offset1=0;string[offset1]>=32;offset1++) {
    nextchar=string[offset1];
    switch (nextchar) {
      case 32:
      case 160:
        //Only put spaces in if last was not a space
        if (lastchar!=32) {
          lastchar=string[offset2++]=32;
        }
        break;
      case '"':
      case '\'':
      case '`':
      case '':
      case '':
      case '':
      case '':
      case '':
        //Handle quotes
        if (lastchar==32) {
          nextchar='';
          counter++;
        }
        else
        {
          if (counter>0 && lookup[string[offset1+1]]<=32) {
            nextchar='';
            counter--;
          }
          else
          {
            nextchar='';
          }
        }
        lastchar=string[offset2++]=nextchar;
        offset3=offset2;
        break;
      default:
        //Copy all remaining characters as they are
        lastchar=string[offset2++]=nextchar;
        offset3=offset2;
        break;
    }
  }
  //Check for trailing '.'s
  for (;offset3>0 && string[offset3-1]=='.';offset3--) {}
  string[offset3++]=0;

  offset1=0;
  while (string[offset1]>=32) {
    offset1=movetonextword(string,offset1);
    if (string[offset1]>=32) {
      //Count the number of initials in the word
      counter=0;
      for (offset2=offset1;lookup[string[offset2]]>32 && string[offset2+1]=='.';offset2+=2) {counter++;}
      if (counter>1 && lookup[string[offset2]]<=32) {
        //Set of initials followed by punctuation, terminator or a space
        offset2=offset1+2;
        offset1++;
        for (loop=1;loop<counter;loop++) {
          string[offset1]=string[offset2];
          offset1++;
          offset2+=2;
        }
        memmove(string+offset1,string+offset2,strlen(string+offset2)+1);
      }
      else
      {
        offset1=movetoendofword(string,offset1);
      }
    }
  }

  //Now scan through the string for words that are to be replaced
  offset1=0;
  while (string[offset1]>=32) {
    //Move to the start of the next word, ignoring punctuation and spaces
    offset1=movetonextword(string,offset1);
    if (string[offset1]>=32) {
      //Have not reached the end of the string so check the word for a match
      loop=findwordinlist(string+offset1,0,0,32,31);
      if (loop!=-1) {
        //Have found a word in the string so replace it
        //Find start of block after word
        offsettoreplace=strchr(wordlist[0][loop].str,',')+1;
        offset2=offset1+(offsettoreplace-wordlist[0][loop].str-1);
        //Find length of block
        len=offset3-offset2;
        //Find new start of block
        offset3=offset1+strlen(offsettoreplace);
        //Move block after word to its new start
        memmove(string+offset3,string+offset2,len);
        //Find new end of the block
        offset3=offset3+len;
        //Now copy the new word over the string, setting case of first letter
        len=0;
        if (islower(offsettoreplace[0]) && isupper(string[offset1+len])) {
          string[offset1+len]=toupper(offsettoreplace[0]);
          len++;
        }
        for (;offsettoreplace[len]>=32;len++) {
          string[offset1+len]=offsettoreplace[len];
        }
        offset1+=len;
      }
      else
      {
        //Does not match so Move to the end of this word
        do {
          offset1++;
        } while (lookup[string[offset1]]>32);
      }
    }
  }
  return 1;
}

//Finds widths of strings for justification
int findfontwidths(void)
{
  char text[8];
  int loop,maxwidth,width,xeig,xpixels;
  if (version>=350) {
    sprintf(text,"9999");
    yeartextwidth=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,text,0);
    sprintf(text,"999BC");
    width=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,text,0);
    if (width>yeartextwidth) {yeartextwidth=width;}
    yearwidth=yeartextwidth+16;
    maxwidth=0;
    for (loop=0;loop<=12;loop++) {
      width=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,monthnames[loop],0);
      if (width>maxwidth) {maxwidth=width;}
    }
    monthwidth=maxwidth+16;
    sprintf(text,"99");
    daytextwidth=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,text,0);
    daywidth=daytextwidth+16;
    maxwidth=0;
    for (loop=0;loop<lenofeventlists[0];loop++) {
      width=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,eventlists[0][loop].str+2,0);
      if (width>maxwidth) {maxwidth=width;}
    }
    typewidth=maxwidth+16;
  }
  else
  {
    yeartextwidth=5*16;
    yearwidth=7*16;
    monthwidth=0;
    for (loop=0;loop<=12;loop++) {
      width=strlen(monthnames[loop])*16;
      if (width>monthwidth) {monthwidth=width;}
    }
    monthwidth+=16;
    daytextwidth=2*16;
    daywidth=4*16;
    typewidth=0;
    for (loop=0;loop<lenofeventlists[0];loop++) {
      width=strlen(eventlists[0][loop].str+2)*16;
      if (width>typewidth) {typewidth=width;}
    }
    typewidth+=32;
  }

  //Check maximum width allowed for window
  xpixels=_swi(OS_ReadModeVariable,_IN(0)|_IN(1)|_RETURN(2),-1,11)+1;
  xeig=_swi(OS_ReadModeVariable,_IN(0)|_IN(1)|_RETURN(2),-1,4);
  windowwidth=(xpixels<<xeig)-44;
  return 1;
}

//Reports an error and then returns a NULL so that routines calling report error terminate correctly
int reporterror(char *errortoken,char *str1)
{
  _kernel_oserror error;
  _swi(Hourglass_Smash,0);
  error.errnum=0;
  _swi(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4),&messages,errortoken,error.errmess,252,str1);
  _swi(Wimp_ReportError,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5),&error,2+4+(1<<8)+(2<<9),"Today",0,1,0);
  return NULL;
}

//This code is called by a Wimp_Quit message, it forces an immediate quit
int quit_message(WimpMessage *message,void *handle)
{
//  fclose(debug);
  _swi(MessageTrans_CloseFile,_IN(0),&messages);
  exit(0);
  return 1;
}

//This code is called when the iconbar is selected
int selectibar_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  toolbox_show_object(0,scanwin.window,0,0,iconbar_id,iconbar_component);
  gadget_set_focus(0,scanwin.toolbar,3);
  return 1;
}

//This code is called when the iconbar is adjusted
int adjustibar_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  toolbox_show_object(0,srchwin.window,0,0,iconbar_id,iconbar_component);
  gadget_set_focus(0,srchwin.toolbar,1);
  return 1;
}

//This code finds the number in a number range (returning unknown if blank)
int findnumber(ObjectId win,ComponentId ic,int unknown)
{
  int writeable,len,val;
  char numstring[20];
  numberrange_get_components(1,win,ic,&writeable,0,0,0);
  writablefield_get_value(0,win,writeable,numstring,20,&len);
  if (numstring[0]<32) {
    val=unknown;
  }
  else
  {
    numberrange_get_value(0,win,ic,&val);
  }
  return val;
}

//This code is called when add event is selected in menu
int openadd_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int day,month,loop;
  unsigned int state;

  toolbox_get_object_state(0,addwin.window,&state);
  if ((state&1)==0) {
    //Add window is not currently open so set date to date of main window
    stringset_get_selected(1,scanwin.toolbar,1,&month);
    month++;
    day=findnumber(scanwin.toolbar,3,0);
    if (day==0) {day=1;}
    stringset_set_selected(0,addwin.window,1,monthnames[month]);
    numberrange_set_value(0,addwin.window,3,day);

    //Clear all text in window
    for (loop=34;loop<=38;loop++) {
      writablefield_set_value(0,addwin.window,loop,"");
    }
  }
  toolbox_show_object(0,addwin.window,0,0,iconbar_id,iconbar_component);
  return 1;
}

//This code is called when clear text is selected
int cleartext_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int loop;
  for (loop=34;loop<=38;loop++) {
    writablefield_set_value(0,addwin.window,loop,"");
  }
  gadget_set_focus(0,addwin.window,34);
  return 1;
}

//Make sure there is room for data
//Stops when it reaches the maximum, but if maximum is -ve it doesn't
//report an error message
int makeroomfordata(WIN_DATA *window,int maximum)
{
  if (window->records==abs(maximum)) {
    if (maximum>=0) {reporterror("e2",0);}
    return NULL;
  }
  if (window->records==window->maxrecords) {
    //Need more space for records so increase array
    if (window->recorddata==NULL) {
      if (flex_alloc((flex_ptr)&window->recorddata,(window->maxrecords+recordstep)*sizeof(REC_DATA))==0) {return reporterror("e1",0);}
    }
    else
    {
      if (flex_extend((flex_ptr)&window->recorddata,(window->maxrecords+recordstep)*sizeof(REC_DATA))==0) {return reporterror("e1",0);}
    }
    window->maxrecords+=recordstep;
  }
  if (window->maxoff-window->curoff<mintext) {
    //Need more space for text so increase array
    if (window->text==NULL) {
      if (flex_alloc((flex_ptr)&window->text,(window->maxoff+textstep)*sizeof(char))==0) {return reporterror("e1",0);}
    }
    else
    {
      if (flex_extend((flex_ptr)&window->text,(window->maxoff+textstep)*sizeof(char))==0) {return reporterror("e1",0);}
    }
    window->maxoff+=textstep;
  }
  return 1;
}

//Saves a record to the file
int saverecord(FILE *fileptr,REC_DATA *record,WIN_DATA *window)
{
  char header[11],*string;
  int length=0,offset=0,offset2,termchar,wordlen;
  //Build the header
  header[0]=eventlists[0][record->type].str[0];
  if (record->month<=0) {
    sprintf(header+1,"  ");
  }
  else
  {
    sprintf(header+1,"%02d",record->month);
  }
  if (record->day<=0) {
    sprintf(header+3,"  ");
  }
  else
  {
    sprintf(header+3,"%02d",record->day);
  }
  if (record->year==unknown_year) {
    sprintf(header+5,"    ");
  }
  else
  {
    sprintf(header+5,"%04d",record->year);
  }
  if (record->code>=1 && record->code<=7) {
    sprintf(header+9,"%d",record->code);
  }
  else
  {
    sprintf(header+9," ");
  }

  //Now save all of the rows to the file
  fprintf(fileptr,"%s",header);
  string=window->text+record->textoff;
  do {
    //Find length of the next word
    offset2=offset;
    for (;string[offset]>32;offset++) {}
    termchar=string[offset];
    wordlen=offset-offset2;
    if (length>0) {wordlen++;}
    if (length+wordlen<=70) {
      //Word will fit so save and move on
      if (length>0) {fputc(32,fileptr);}
      for (;offset2<offset;offset2++) {
        fputc(string[offset2],fileptr);
      }
      length+=wordlen;
      offset++;
    }
    else
    {
      //Need to break line
      if (length>0) {
        //Already have some text so terminate current and move on
        fputc(10,fileptr);
        header[9]='C';
        fprintf(fileptr,"%s",header);
        for (;offset2<offset;offset2++) {
          fputc(string[offset2],fileptr);
        }
        length=wordlen-1;
        offset++;
      }
      else
      {
        //Break word midway
        for (offset=offset2;offset<offset2+70;offset++) {
          fputc(string[offset],fileptr);
        }
        fputc(10,fileptr);
        header[9]='C';
        fprintf(fileptr,"%s",header);
        length=0;
        termchar=32;
      }
    }
  } while (termchar>0);
  fputc(10,fileptr);
  return 1;
}

//This code moves the file ptr to the start of the next line
int movetonextline(FILE *fileptr)
{
  int nextchar;
  //Keep looping until either end of file or end of blanks and comments
  do {
    //Check next char
    nextchar=fgetc(fileptr);
    if (nextchar==EOF) {return 0;}
    if (nextchar=='*') {
      //Have found a comment so move to the end
      do {
        nextchar=fgetc(fileptr);
        if (nextchar==EOF) {return 0;}
      } while (nextchar>=32);
    }
  } while (nextchar<=32 || nextchar==160);

  //Have now found a character that is not a comment line so put back
  ungetc(nextchar,fileptr);
  return 1;
}

//This code loads the header information for a record
int loadheader(FILE *fileptr,REC_DATA *record,int listnum)
{
  char header[10],typechar;
  int loop,mult=1;

  if (fread(header,sizeof(char),10,fileptr)==EOF) {return 0;}

  //Check type
  typechar=toupper(header[0]);
  for (loop=0;loop<lenofeventlists[listnum] && typechar!=eventlists[listnum][loop].str[0];loop++) {}
  if (loop<lenofeventlists[listnum]) {
    record->type=eventlistmaps[listnum][loop];
  }
  else
  {
    record->type=0;
  }

  //Check for hard spaces and errors in header
  for (loop=1;loop<=8;loop++) {
    if (header[loop]==160) {header[loop]=32;}
  }

  //Check month
  if (header[1]==' ' && header[2]==' ') {
    record->month=-1;
  }
  else
  {
    record->month=0;
    if (header[1]!=' ') {record->month+=10*(header[1]-'0');}
    if (header[2]!=' ') {record->month+=header[2]-'0';}
  }

  //Check day
  if (header[3]==' ' && header[4]==' ') {
    record->day=-1;
  }
  else
  {
    record->day=0;
    if (header[3]!=' ') {record->day+=10*(header[3]-'0');}
    if (header[4]!=' ') {record->day+=header[4]-'0';}
  }

  //Check year
  if (header[5]==' ' && header[6]==' ' && header[7]==' ' && header[8]==' ') {
    record->year=unknown_year;
  }
  else
  {
    record->year=0;
    if (header[5]=='-') {
      mult=-1;
    }
    else
    {
      if (header[5]!=' ') {record->year+=1000*(header[5]-'0');}
    }
    if (header[6]=='-') {
      mult=-1;
    }
    else
    {
      if (header[6]!=' ') {record->year+=100*(header[6]-'0');}
    }
    if (header[7]=='-') {
      mult=-1;
    }
    else
    {
      if (header[7]!=' ') {record->year+=10*(header[7]-'0');}
    }
    if (header[8]=='-') {
      mult=-1;
    }
    else
    {
      if (header[8]!=' ') {record->year+=(header[8]-'0');}
    }
    record->year*=mult;
  }

  //Check type
  if (header[9]>='1' && header[9]<='7') {
    record->code=header[9]-'0';
  }
  else
  {
    if (header[9]=='C' || header[9]=='c') {
      record->code=8;
    }
    else
    {
      record->code=0;
    }
  }

  return 1;
}

//This code adds a record to the list of records for a window
int addeventtolist(WIN_DATA *window)
{
  //Increment counters
  window->records++;
  //Increment offset to next string
  window->curoff+=strlen(window->text+window->curoff)+1;
  return 1;
}

//This code loads the next record from the file
//Returns -ve if no record and 0 if an error
int loadnextrecord(FILE *fileptr,WIN_DATA *window,int filenum,int maximum,int listnum,int tempfile)
{
  int nextchar,offset,continuation;
  REC_DATA *record,record2;

  if (makeroomfordata(window,maximum)==NULL) {return NULL;}

  //Move to start of line and leave if nothing left in file
  if (movetonextline(fileptr)==0) {return -1;}

  //Load and process the header
  record=&window->recorddata[window->records];
  if (loadheader(fileptr,record,listnum)==0) {return -1;}

  //Load the string
  record->textoff=offset=window->curoff;
  do {
    nextchar=fgetc(fileptr);
    if (nextchar!=EOF && nextchar>=32) {window->text[offset++]=nextchar;}
  } while (nextchar!=EOF && nextchar>=32);
  window->text[offset++]=0;

  //Check for any continuations
  do {
    continuation=0;
    //Move to start of line and leave if nothing left in file
    if (movetonextline(fileptr)!=0) {
      //Load and process the header
      if (loadheader(fileptr,&record2,listnum)!=0) {
        if (record2.code==8 && record2.type==record->type  && record2.year==record->year && record2.month==record->month && record2.day==record->day) {
          //Line is a contination of previous line so add text to previous
          continuation=1;
          offset--;
          window->text[offset++]=' ';
          do {
            nextchar=fgetc(fileptr);
            if (nextchar!=EOF && nextchar>=32) {window->text[offset++]=nextchar;}
          } while (nextchar!=EOF && nextchar>=32);
          window->text[offset++]=0;
        }
        else
        {
          //Not a continue so move to before the header
          fseek(fileptr,-10,SEEK_CUR);
        }
      }
    }
  } while (continuation==1);
  //Only parse the string if it was not loaded from a temporary file
  if (tempfile==0) {parsestring(window->text+record->textoff);}

  //Check that the events have years and reminders do not
  if (record->type==1 && record->year==unknown_year) {record->type=2;}
  if (record->type==2 && record->year!=unknown_year) {record->type=1;}

  //Set flag for own file
  record->fromown=(filenum==0);
  return 1;
}

//Loads all events from file filenum
//If tempfile is non-zero it loads from the tempfile
int loadallevents(WIN_DATA *joindata,char *path,int filenum,int tempfile,int listnum)
{
  char filename[256];
  FILE *fileptr;
  int recresult;

  if (tempfile!=0) {
    sprintf(filename,"%s.TodayData._%s",path,monthfiles[filenum]);
  }
  else
  {
    sprintf(filename,"%s.TodayData.%s",path,monthfiles[filenum]);
  }
  fileptr=fopen(filename,"rb");
  //If file could not be opened, treat as empty
  if (fileptr==NULL) {return 1;}

  //Now cycle through loading records
  do {
    recresult=loadnextrecord(fileptr,joindata,filenum,0x7fffffff,listnum,tempfile);
    if (recresult==0) {
      fclose(fileptr);
      return 0;
    }
    if (recresult>0) {addeventtolist(joindata);}
  } while (recresult>0);
  fclose(fileptr);
  return 1;
}

//Save all events to a file
int saveallevents(WIN_DATA *joindata,char *path,int filenum,int tempfile)
{
  char filename[256];
  int loop,loop2;
  FILE *fileptr;


  //Check if storing to tempfile, this is so that
  //when sorting data can be moved from one month file to another
  if (tempfile!=0) {
    sprintf(filename,"%s.TodayData._%s",path,monthfiles[filenum]);
  }
  else
  {
    sprintf(filename,"%s.TodayData.%s",path,monthfiles[filenum]);
  }
  fileptr=fopen(filename,"w");
  if (fileptr==NULL) {return reporterror("e3",filename);}
  loop2=0;
  loop=1;
  if (joindata->records>0 && joindata->recorddata[0].type==0) {loop=0;}
  for (;loop<lenofeventlists[0];loop++) {
    fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
    fprintf(fileptr,"*mmddyyyy %s\n",eventlists[0][loop].str+2);
    fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
    for(;loop2<joindata->records && joindata->recorddata[loop2].type<=loop;loop2++) {
      saverecord(fileptr,&joindata->recorddata[loop2],joindata);
    }
    fprintf(fileptr,"\n");
  }
  if (loop2<joindata->records) {
    fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
    fprintf(fileptr,"*mmddyyyy Unsorted\n");
    fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
    for(;loop2<joindata->records;loop2++) {
      saverecord(fileptr,&joindata->recorddata[loop2],joindata);
    }
  }
  fclose(fileptr);
  return 1;
}

//Routine to read the next upper case letter from the string
//If a terminator is reached 0 is returned
//If a lower case letter or a number is reached 1 is returned
//If a blank/punctuation is found before the letter blank is set
int readnextinitial(int num,REC_DATA *record,int *wordnum,int *offset,int *blank)
{
  int letter;
  *blank=0;

  //if next letter is a blank or last initial was start of a word, move to next word
  if ((*offset)<0 || lookup[sortwin[num]->text[record->textoff+stringwords[num][*wordnum]+(*offset)]]<=32) {
    *blank=1;

    (*wordnum)++;
    *offset=0;

    //If last word has been checked stop
    if (*wordnum>=numofwords[num]) {return 0;}

    //If next word has already been used stop
    if (stringwords[num][*wordnum]<0) {return 1;}
  }

  //Find the next letter, know that it must be alphanumeric but it could be lower case
  letter=lookup2[sortwin[num]->text[record->textoff+stringwords[num][*wordnum]+((*offset)++)]];

  //Check to see if the letter following the initial is lowercase
  if (lookup2[sortwin[num]->text[record->textoff+stringwords[num][*wordnum]+(*offset)]]==2) {(*offset)=-(*offset);}
  return letter;
}

//Routine to compare words into alphabetical order
int comparewords2(const void *e1,const void *e2)
{
  int r1,r2;
  r1=*(int *)e1;
  r2=*(int *)e2;
  return comparewordswiththesaurus(sortlisttext,r1,sortlisttext,r2);
}

//Makes list of words. Sorts words into reverse alphabetic order of words in
//thesaurus and then alphabetic order of remaining words. List does not include
//initials or names that have matches between records.
//Returns non-zero if it has found matching names/initials
int makelistofwords(REC_DATA *record1,REC_DATA *record2)
{
  int offset1,offset2,letter1,letter2,blank1,blank2,loop,loop2,loop3,loop4,oldcount;
  int wordnum1,wordnum2,matchedinitialtoname,newoffset,newoffset2;
  int oldstart[2],oldend[2],extend,thesauruspos,result=0;
  char newname[128],ignorable[2][maxwordsinstring];
  REC_DATA *records[2];
  records[0]=record1;
  records[1]=record2;

  //Build lists of words
  for (loop=0;loop<2;loop++) {
    numofwords[loop]=0;
    for (offset1=0;numofwords[loop]<maxwordsinstring && sortwin[loop]->text[records[loop]->textoff+offset1]>=32;) {
      offset1=movetonextword(sortwin[loop]->text+records[loop]->textoff,offset1);
      if (lookup[sortwin[loop]->text[records[loop]->textoff+offset1]]>32) {
        stringwords[loop][numofwords[loop]]=offset1;

        //Check if word is ignorable
        if (findwordinlist(sortwin[loop]->text+records[loop]->textoff+offset1,1,0,32,31)>=0) {
          ignorable[loop][numofwords[loop]++]=1;
        }
        else
        {
          ignorable[loop][numofwords[loop]++]=0;
        }

        offset1=movetoendofword(sortwin[loop]->text+records[loop]->textoff,offset1);
      }
    }
  }

  //Now check for any initials
  for (loop=0;loop<numofwords[0];loop++) {
    if (stringwords[0][loop]>=0) {
      //Check if this word starts with an upper case letter
      if (lookup2[sortwin[0]->text[records[0]->textoff+stringwords[0][loop]]]>32) {
        for (loop2=0;loop2<numofwords[1];loop2++) {
          if (stringwords[1][loop2]>=0) {
            //See if the initials match initials in string2
            offset1=offset2=0;
            wordnum1=loop;
            wordnum2=loop2;
            matchedinitialtoname=0;
            do {
              letter1=readnextinitial(0,records[0],&wordnum1,&offset1,&blank1);
              letter2=readnextinitial(1,records[1],&wordnum2,&offset2,&blank2);
              if (blank1==1 && blank2==1 && matchedinitialtoname==0) {
                //Both sets of initials have reached a blank and have not matched an inital to a name
                //so they must match

                for (loop3=loop;loop3<wordnum1;loop3++) {stringwords[0][loop3]=-1;}
                for (loop3=loop2;loop3<wordnum2;loop3++) {stringwords[1][loop3]=-1;}

                //Stop looking for matches
                loop2=numofwords[1];

                //Set result so that calling routine knows match is found
                if (result==0) {
                  for (loop3=loop;loop3<wordnum1;loop3++) {
                    if (ignorable[0][loop3]==0) {
                      //Word would not be ignored normally so treat as common
                      result=1;
                      break;
                    }
                  }
                  for (loop3=loop2;loop3<wordnum2;loop3++) {
                    if (ignorable[1][loop3]==0) {
                      //Word would not be ignored normally so treat as common
                      result=1;
                      break;
                    }
                  }
                }
                break;
              }

              //See if either of the strings have terminated or letters do not match
              if (letter1<=2|| letter2<=2 || letter1!=letter2) {break;}

              //See if last letters were start of words
              if (offset1<0) {
                if (offset2<0) {
                  //Both letters were the start of words so compare the strings
                  if (comparewords(sortwin[0]->text+records[0]->textoff+stringwords[0][wordnum1]-offset1,sortwin[1]->text+records[1]->textoff+stringwords[1][wordnum2]-offset2,0,32,32)==0) {
                    //Build the new name to replace the old initials
                    if (matchedinitialtoname!=0) {
                      newoffset=0;
                      offset1=offset2=0;
                      wordnum1=loop;
                      wordnum2=loop2;
                      do {
                        letter1=readnextinitial(0,records[0],&wordnum1,&offset1,&blank1);
                        letter2=readnextinitial(1,records[1],&wordnum2,&offset2,&blank2);
                        if (offset1<0) {
                          if (offset2>=0) {
                            newoffset2=-offset1-1;
                            do {
                              newname[newoffset++]=sortwin[0]->text[records[0]->textoff+stringwords[0][wordnum1]+newoffset2++];
                            } while (lookup[sortwin[0]->text[records[0]->textoff+stringwords[0][wordnum1]+newoffset2]]>32);
                            newname[newoffset++]=32;
                          }
                        }
                        else
                        {
                          if (offset2<0) {
                            newoffset2=-offset2-1;
                            do {
                              newname[newoffset++]=sortwin[1]->text[records[1]->textoff+stringwords[1][wordnum2]+newoffset2++];
                            } while (lookup[sortwin[1]->text[records[1]->textoff+stringwords[1][wordnum2]+newoffset2]]>32);
                            newname[newoffset++]=32;
                          }
                          else
                          {
                            //Just copy the initial
                            newname[newoffset++]=letter1;
                            newname[newoffset++]=32;
                          }
                        }
                      } while (offset1>=0 || offset2>=0);
                      newname[newoffset]=0;
                      oldstart[0]=stringwords[0][loop];
                      oldend[0]=stringwords[0][wordnum1]-offset1-1;
                      oldstart[1]=stringwords[1][loop2];
                      oldend[1]=stringwords[1][wordnum2]-offset2-1;

                      //Now replace the initials in memory
                      for (loop3=0;loop3<2;loop3++) {
                        extend=newoffset-(oldend[loop3]-oldstart[loop3]);
                        if (extend!=0) {
                          //Need to change the size of the block
                          if (flex_midextend((flex_ptr)&sortwin[loop3]->text,records[loop3]->textoff+oldend[loop3],extend*sizeof(char))==1) {
                            //Extended the block so change all of the offsets
                            sortwin[loop3]->maxoff+=extend;
                            for (loop4=0;loop4<sortwin[loop3]->records;loop4++) {
                              if (sortwin[loop3]->recorddata[loop4].textoff>records[loop3]->textoff) {
                                sortwin[loop3]->recorddata[loop4].textoff+=extend;
                              }
                            }
                            for (loop4=0;loop4<numofwords[loop3];loop4++) {
                              if (stringwords[loop3][loop4]>oldstart[loop3]) {stringwords[loop3][loop4]+=extend;}
                            }
                          }
                          else
                          {
                            //Failed to extend block, so stop initials being overwritten
                            newoffset=0;
                          }
                        }

                        //Copy the string
                        for (newoffset2=0;newoffset2<newoffset;newoffset2++) {
                          sortwin[loop3]->text[records[loop3]->textoff+oldstart[loop3]+newoffset2]=newname[newoffset2];
                        }
                      }
                    }

                    //Set all used words so they are not looked at later
                    for (loop3=loop;loop3<=wordnum1;loop3++) {stringwords[0][loop3]=-1;}
                    for (loop3=loop2;loop3<=wordnum2;loop3++) {stringwords[1][loop3]=-1;}

                    //Stop looking for matches
                    loop2=numofwords[1];

                    //Set result so that calling routine knows match is found
                    if (result==0) {
                      for (loop3=loop;loop3<=wordnum1;loop3++) {
                        if (ignorable[0][loop3]==0) {
                          //Word would not be ignored normally so treat as common
                          result=1;
                          break;
                        }
                      }
                      for (loop3=loop2;loop3<=wordnum2;loop3++) {
                        if (ignorable[1][loop3]==0) {
                          //Word would not be ignored normally so treat as common
                          result=1;
                          break;
                        }
                      }
                    }
                  }

                  //Now stop at the match
                  break;
                }
                else
                {
                  //Matched an initial to a name
                  matchedinitialtoname=1;
                }
              }
              else
              {
                if (offset2<0) {
                  //Matched an initial to a name
                  matchedinitialtoname=1;
                }
              }
            } while (1);
          }
        }
      }
    }
  }

  //Now remove any of the words to be ignored (done now in case they were initials) and sort lists into order
  for (loop=0;loop<2;loop++) {
    oldcount=numofwords[loop];
    numofwords[loop]=0;
    for (loop2=0;loop2<oldcount;loop2++) {
      if (stringwords[loop][loop2]>=0) {
        //Word has not been removed by initial checking
        //Only keep word in the list if it is not to be ignored
        if (ignorable[loop][loop2]==0) {
          //Check if the word is in the thesaurus table
          thesauruspos=findwordinlist(sortwin[loop]->text+records[loop]->textoff+stringwords[loop][loop2],3,1,32,31);
          if (thesauruspos>=0) {
            //Word is in the thesaurus so find the store word in list that it
            //is equivalent to (instead of the offset)
            //Store as -(position+1) so that sorting puts all of the words
            //in the thesaurus at start of list
            stringwords[loop][numofwords[loop]]=-thesauruslist[thesauruspos]-1;
          }
          else
          {
            //Word is not in list so keep the pointer
            stringwords[loop][numofwords[loop]]=stringwords[loop][loop2];
          }
          numofwords[loop]++;
        }
      }
    }
    sortlisttext=sortwin[loop]->text+records[loop]->textoff;
    qsort((void *)stringwords[loop],(size_t)numofwords[loop],sizeof(int),comparewords2);
  }

  return result;
}

//Tests to see any words are common between string1 and string2
int checkforcommonword(REC_DATA *r1,REC_DATA *r2)
{
  int pos1=0,pos2=0,result;

  //Make list of all of the words in the two strings and stop if a common name
  //is found
  if (makelistofwords(r1,r2)!=0) {return 1;}

  //Find first word common to both lists
  for (;pos1<numofwords[0];pos1++) {
    //Find the first in second list>=word in first list
    result=2;
    while (pos2<numofwords[1] && result>0) {
      result=comparewordswiththesaurus(sortwin[0]->text+r1->textoff,stringwords[0][pos1],sortwin[1]->text+r2->textoff,stringwords[1][pos2]);
      if (result==0) {return 1;}
      if (result>=0) {pos2++;}
    }
  }
  return 0;
}

//Save all events to a file (but in possible duplicate states)
int savealleventsinduporder(WIN_DATA *joindata,char *path,int filenum,int tempfile,int *possibleduplicates)
{
  char filename[256];
  int loop,loop2,loop3,endpt,foundmore;
  FILE *fileptr;
  char *storedtable;

  //Allocate a table of currently stored events
  if (flex_alloc((flex_ptr)&storedtable,joindata->records*sizeof(char))==0) {return reporterror("e1",0);}
  for(loop=0;loop<joindata->records;loop++) {storedtable[loop]=0;}

  //Check if storing to tempfile, this is so that
  //when sorting data can be moved from one month file to another
  if (tempfile!=0) {
    sprintf(filename,"%s.TodayData._%s",path,monthfiles[filenum]);
  }
  else
  {
    sprintf(filename,"%s.TodayData.%s",path,monthfiles[filenum]);
  }
  fileptr=fopen(filename,"w");
  if (fileptr==NULL) {return reporterror("e3",filename);}

  //First save all events with unknown dates
  fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
  fprintf(fileptr,"*mmddyyyy Unknown Dates\n");
  fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
  for(loop=0;loop<joindata->records;loop++) {
    if (joindata->recorddata[loop].year==unknown_year || joindata->recorddata[loop].month<0 || joindata->recorddata[loop].day<0) {
      saverecord(fileptr,&joindata->recorddata[loop],joindata);
      storedtable[loop]=1;
    }
  }

  //Now save any records that may be duplicates
  fprintf(fileptr,"\n*-------- ------------------------------------------------------------\n");
  fprintf(fileptr,"*mmddyyyy Possible Duplicates\n");
  fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
  for(loop=0;loop<joindata->records;loop++) {
    if (storedtable[loop]==0) {
      //Find the end of the range of possible duplicates
      for (endpt=loop+1;endpt<joindata->records && joindata->recorddata[endpt].year==joindata->recorddata[loop].year && joindata->recorddata[endpt].month==joindata->recorddata[loop].month && joindata->recorddata[endpt].day==joindata->recorddata[loop].day;endpt++) {}
      //Now see if the record at loop has any duplicates
      for (loop2=loop+1;loop2<endpt;loop2++) {
        if (storedtable[loop2]==0) {
          if (checkforcommonword(&joindata->recorddata[loop],&joindata->recorddata[loop2])==1) {break;}
        }
      }
      if (loop2<endpt) {
        //Must have found a duplicate
        storedtable[loop]=storedtable[loop2]=2;
        //Now find any others that match these records
        do {
          foundmore=0;
          for (loop2=loop+1;loop2<endpt;loop2++) {
            if (storedtable[loop2]==0) {
              for (loop3=loop;loop3<endpt;loop3++) {
                if (storedtable[loop3]==2) {
                  if (checkforcommonword(&joindata->recorddata[loop2],&joindata->recorddata[loop3])==1) {
                    //The record matchs another record that has already matched
                    storedtable[loop2]=2;
                    foundmore=1;
                    break;
                  }
                }
              }
            }
          }
        } while (foundmore==1);

        //Now save the records
        saverecord(fileptr,&joindata->recorddata[loop],joindata);
        (*possibleduplicates)++;
        storedtable[loop]=1;
        for (loop2=loop+1;loop2<endpt;loop2++) {
          if (storedtable[loop2]==2) {
            saverecord(fileptr,&joindata->recorddata[loop2],joindata);
            storedtable[loop2]=1;
          }
        }

        //Now put a blank line afterwards to separate possible duplicates
        fprintf(fileptr,"\n");
      }
    }
  }

  //Now save any remaining records
  fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
  fprintf(fileptr,"*mmddyyyy Unlikely To Be Duplicates\n");
  fprintf(fileptr,"*-------- ------------------------------------------------------------\n");
  for(loop=0;loop<joindata->records;loop++) {
    if (storedtable[loop]==0) {
      saverecord(fileptr,&joindata->recorddata[loop],joindata);
    }
  }

  fclose(fileptr);

  flex_free((flex_ptr)&storedtable);
  return 1;
}

//Test to see if string 1 is inside string 2 or vice versa
int comparestrings(REC_DATA *r1,REC_DATA *r2)
{
  int found=0,pos1=0,pos2=0,result,offset,letter1,letter2;

  //Make list of all of the words in the two strings
  makelistofwords(r1,r2);

  //Count number of words in both strings
  for (;pos1<numofwords[0];pos1++) {
    //Find the first in second list>=word in first list
    result=2;
    while (pos2<numofwords[1] && result>0) {
      result=comparewordswiththesaurus(sortwin[0]->text+r1->textoff,stringwords[0][pos1],sortwin[1]->text+r2->textoff,stringwords[1][pos2]);
      if (result==0) {found++;}
      if (result>=0) {pos2++;}
    }
  }

  //Now do check to see which string is greater
  if (found==numofwords[0]) {
    //All words in 1 are also in 2
    if (found==numofwords[1]) {
      //All words in 2 are also in 1 so strings are equal
      //but would prefer to keep the string that is shorter (less punctuation)
      if (strlen(sortwin[0]->text+r1->textoff)<strlen(sortwin[1]->text+r2->textoff)) {return 1;}
      if (strlen(sortwin[0]->text+r1->textoff)>strlen(sortwin[1]->text+r2->textoff)) {return -1;}
      return 0;
    }
    else
    {
      //There are words in 2 which are not in 1, so 2 is slightly greater
      return -1;
    }
  }
  if (found==numofwords[1]) {
    //All words in 2 are also in 1, so 1 is slightly greater
    return 1;
  }

  //Each string contains words not in the other string so do alphabetic compare
  //this is so that the qsort routines do not get confused
  offset=0;
  do {
    letter1=lookup[sortwin[0]->text[r1->textoff+offset]];
    letter2=lookup[sortwin[1]->text[r2->textoff+offset]];
    if (letter1<letter2) {return -2;}
    if (letter1>letter2) {return 2;}
    offset++;
  } while (letter1>0);
  return 0;
}

//Routine to compare records for the join sort
int joincompare(const void *e1,const void *e2)
{
  REC_DATA *r1,*r2;
  r1=(REC_DATA *)e1;
  r2=(REC_DATA *)e2;

  if (ignoretypes==0) {
    if (r1->type>r2->type) {return 2;}
    if (r1->type<r2->type) {return -2;}
  }
  if (ignoreyears==0) {
    if (r1->year>r2->year) {return 2;}
    if (r1->year<r2->year) {return -2;}
  }
  if (r1->month>r2->month) {return 2;}
  if (r1->month<r2->month) {return -2;}
  if (r1->day>r2->day) {return 2;}
  if (r1->day<r2->day) {return -2;}
  if (r1->code>r2->code) {return 2;}
  if (r1->code<r2->code) {return -2;}
  return comparestrings(r1,r2);
}

//Sets the size of the window and justifies any text
//Only reopens if reopen is non-zero
int justifywindow(WIN_DATA *window,int reopen)
{
  int loop,offset,lines=0,block[25],textwidth,charwidth,spaceoffset,width;
  unsigned int state;
  char *string;
  BBox extent;

  textwidth=windowwidth-16-yearwidth-typewidth;
  if (window->listdates!=0) {textwidth-=daywidth+monthwidth;}
  charwidth=textwidth/16;
  //Fix 'just in case'
  if (charwidth<1) {charwidth=1;}

  //Justify each of the lines
  for (loop=0;loop<window->records;loop++) {
    //Remove line breaks
    string=window->text+window->recorddata[loop].textoff;
    for (offset=0;string[offset]!=0;offset++) {
      if (string[offset]==13) {string[offset]=32;}
    }

    window->recorddata[loop].startline=lines;
    lines++;
    if (version>=350) {
      //Keep breaking until end reached
      do {
        spaceoffset=0;
        offset=-1;
        //Move through words until find word too long
        do {
          //Move to end of word
          for (offset++;string[offset]>32;offset++) {}
          width=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,string,offset);
          if (width<=textwidth) {spaceoffset=offset;}
        } while (width<=textwidth && string[offset]>=32);

        //If no space, find first space
        if (spaceoffset==0) {
          for (spaceoffset=offset;string[spaceoffset]>32;spaceoffset++) {}
          offset=spaceoffset;
        }

        //If wrapped, break at string
        if (width>textwidth) {
          //Need to break line, break at last space
          string[spaceoffset]=13;
          string+=spaceoffset+1;
          offset=0;
          lines++;
        }
      } while (string[offset]!=0);
    }
    else
    {
      //Keep breaking until end reached
      do {
        spaceoffset=0;
        for (offset=0;string[offset]!=0 && offset<charwidth;offset++) {
          if (string[offset]==32) {spaceoffset=offset;}
        }

        //If no space, find first space
        if (spaceoffset==0) {
          for (spaceoffset=offset;string[spaceoffset]>32;spaceoffset++) {}
          offset=spaceoffset;
        }

        if (offset>=charwidth) {
          //Need to break line, break at last space
          string[spaceoffset]=13;
          string+=spaceoffset+1;
          offset=0;
          lines++;
        }
      } while (string[offset]!=0);
    }
    window->recorddata[loop].endline=lines-1;
  }

  //Set extent of window
  extent.xmin=extent.ymax=0;
  extent.xmax=windowwidth;
  extent.ymin=-window->toolbarheight;
  //window_set_extent(0,window->toolbar,&extent);
  if (lines>0) {extent.ymin-=8+40*lines;}
  window_set_extent(0,window->window,&extent);

  //Redraw the window if open
  if (reopen!=0) {
    toolbox_get_object_state(0,window->window,&state);
    if ((state&1)!=0) {
      window_get_wimp_handle(0,window->window,&block[0]);
      _swi(Wimp_GetWindowState,_IN(1),block);
      toolbox_show_object(0,window->window,1,&block[1],iconbar_id,iconbar_component);
      //Move minimum down to cope with case of zero entries left in window
      extent.ymin-=100;
      window_force_redraw(0,window->window,&extent);
    }
  }
  return 1;
}

//Tests if the record is on the correct date, returns 1 if it is
int testifcorrectdate(REC_DATA *record)
{
  if (record->month>0 && record->month!=scanmonth) {return 0;}
  if (record->day>0 && record->day!=scanday) {return 0;}
  if (record->code!=0 && record->code!=scandayofweek) {return 0;}
  return 1;
}

//Reads the next non-punctuation character
int readnextchar(char *string,int *offset)
{
  int c;
  //Keep loading chars until it is not a punctuation character
  do {
    c=string[(*offset)++];
    //Use lookup table to convert to upper case and remove all punctuation
    c=lookup[c];
  } while (c==1);
  return c;
}

//Routine to compare records for the sort
int comparerecords(const void *e1,const void *e2)
{
  REC_DATA *r1,*r2;
  int offset,offset2,char1,char2;
  r1=(REC_DATA *)e1;
  r2=(REC_DATA *)e2;

  if (r1->year>r2->year) {return 1;}
  if (r1->year<r2->year) {return -1;}
  if (r1->month>r2->month) {return 1;}
  if (r1->month<r2->month) {return -1;}
  if (r1->day>r2->day) {return 1;}
  if (r1->day<r2->day) {return -1;}
  if (r1->type>r2->type) {return 1;}
  if (r1->type<r2->type) {return -1;}
  if (r1->code>r2->code) {return 1;}
  if (r1->code<r2->code) {return -1;}

  offset=r1->textoff;
  offset2=r2->textoff;
  for (;sortwin[0]->text[offset]>=32 || sortwin[1]->text[offset2]>=32;) {
    char1=readnextchar(sortwin[0]->text,&offset);
    char2=readnextchar(sortwin[1]->text,&offset2);
    if (char1<char2) {return -1;}
    if (char1>char2) {return 1;}
  }
  return 0;
}

//Routine to decode the symbol in the supplied string and check if it is valid
//Returns symbol in c
//  0-255  ASCII Character
//  1000   Alphabetic
//  1001   Digit
//  1002   Alphanumeric
//  1003   Any character
//  1004   Punctuation
//  2000   0 or more chars
//  2001   not char
//  2002   0 or more of char
//  2003   1 or more of char
//Returns -ve if the symbol is invalid
//If inset is non-zero checks are done to make sure symbol is allowed in a set
int decode_symbol(char *str,int *len,int inset)
{
  //Check for magic characters
  switch (str[0]) {
    case '\\':
      *len=2;
      switch (str[1]) {
        case '*':
          //Check for 0 or more characters
          if (inset==0) {return 2000;}
          break;
        case '.':
          //Check for any character
          return 1003;
          break;
        case 'a':
        case 'A':
          //Check for an alphanumeric character
          return 1002;
          break;
        case 'd':
        case 'D':
          //Check for a digit
          return 1001;
          break;
        case 'c':
        case 'C':
          //Check for an alphabetic character
          return 1000;
          break;
        case 'p':
        case 'P':
          //Check for a punctuation character
          return 1004;
          break;
        case '\\':
        case '[':
        case ']':
        case '-':
          //Check for a normal
          return str[1];
          break;
        case '~':
          //Check for not a character
          if (inset==0) {return 2001;}
          break;
        case '&':
          //Check for 0 or more of a character
          if (inset==0) {return 2002;}
          break;
        case '^':
          //Check for 1 or more of a character
          if (inset==0) {return 2003;}
          break;
      }
    case '[':
      //Invalid if in a set
      if (inset==0) {
        *len=1;
        return '[';
      }
    case ']':
    case '-':
      //Invalid characters for symbols
      return -1;
    default:
      //All other chars
      *len=1;
      return str[0];
  }
  return -1;
}

//Code to check if a character is in a character set (0 if it isn't, 1 if it is)
//Returns the length to the end of the set in len
int findcharinset(char c,char *set,int *len)
{
  int off,c2,c3,len2;
  if (set[0]=='[') {
    //Find the length of the set
    for((*len)=1;set[(*len)]!=']';(*len)++) {}
    (*len)++;
    //Start of a set, keep checking until the end
    for(off=1;set[off]!=']';) {
      c2=decode_symbol(set+off,&len2,1);
      off+=len2;
      switch (c2) {
        case 1003:
          //Check for a single character
          if (c>=32) {return 1;}
          break;
        case 1002:
          //Check for an alphanumeric character
          if (isalnum(c)!=0) {return 1;}
          break;
        case 1001:
          //Check for a digit
          if (isdigit(c)!=0) {return 1;}
          break;
        case 1000:
          //Check for an alphabetic character
          if (isalpha(c)!=0) {return 1;}
          break;
        case 1004:
          //Check for a punctuation character
          if (lookup[c]==1 || lookup[c]=='&') {return 1;}
          break;
        default:
          //Check if matches to a range
          if (set[off]=='-') {
            off++;
            c3=decode_symbol(set+off,&len2,1);
            off+=len2;
            if (c>=c2 && c<=c3) {return 1;}
          }
          else
          {
            if (c==c2) {return 1;}
          }
          break;
      }
    }
  }
  else
  {
    //Single character
    c2=decode_symbol(set,len,1);
    switch (c2) {
      case 1003:
        //Check for a single character
        if (c>=32) {return 1;}
        break;
      case 1002:
        //Check for an alphanumeric character
        if (isalnum(c)!=0) {return 1;}
        break;
      case 1001:
        //Check for a digit
        if (isdigit(c)!=0) {return 1;}
        break;
      case 1000:
        //Check for an alphabetic character
        if (isalpha(c)!=0) {return 1;}
        break;
      case 1004:
        //Check for a punctuation character
        if (lookup[c]<=1 || lookup[c]=='&') {return 1;}
        break;
      default:
        //Check if matches exactly
        if (c==c2) {return 1;}
        break;
    }
  }
  return 0;
}

//Code checks for match between strings
//Returns non-zero if a match
int wildcardmatch(char *str1,char *str2,int casesen)
{
  int off1,off2,off3,off4,char1,char2,failed,len;

  //Run through possible starts to string
  for (off1=0;;off1++) {
    //Run through second string
    off3=0;
    for (off2=0;;off2++) {
      char1=str1[off1+off2];
      //Remove accents from letters
      char1=lookup3[char1];
      if (casesen==0) {char1=toupper(char1);}
      char2=decode_symbol(str2+off3,&len,0);
      //if str2 has finished then must be a match
      if (char2<32) {return 1;}
      //if str1 has finished then cannot be a match
      if (char1<32 && char2!=2000) {return 0;}
      //Check for magic characters in char2
      failed=0;
      switch (char2) {
        case 2000:
          //Check for 0 or more characters
          return wildcardmatch(str1+off1+off2,str2+off3+2,casesen);
          break;
        case 2001:
          //Check for not a character
          off3+=2;
          failed=findcharinset(char1,str2+off3,&len);
          break;
        case 2002:
        case 2003:
          //Check for 0/1 or more of a character
          off3+=2;
          for (off4=off2;;off2++) {
            char1=str1[off1+off2];
            //Remove accents from letters
            char1=lookup3[char1];
            if (casesen==0) {char1=toupper(char1);}
            if (findcharinset(char1,str2+off3,&len)==0) {break;}
          }
          if (char2==2003 && off2==off4) {failed=1;}
          off2--;
          break;
        default:
          //Check for character in set
          failed=1-findcharinset(char1,str2+off3,&len);
      }
      if (failed!=0) {break;}
      //Move to after the character/set
      off3+=len;
    }
  }
  return 0;
}

//Checks if an event is in the correct range
//Returns non-zero if it is in range
int recinrange(REC_DATA *record,int year1,int month1,int day1,int year2,int month2,int day2)
{
  //If year is unknown count as a match
  if (record->year==unknown_year) {return 1;}
  //If year is outside range, set as not match
  if (record->year<year1 || record->year>year2) {return 0;}

  //Check if before start of range
  if (record->year==year1) {
    //If month unknown count as a match
    if (record->month<=0) {return 1;}
    //If month too soon, not a match
    if (record->month<month1) {return 0;}
    if (record->month==month1) {
      //If day unknown count as a match
      if (record->day<=0) {return 1;}
      //If day too soon, not a match
      if (record->day<day1) {return 0;}
    }
  }

  //Check if after end of range
  if (record->year==year2) {
    //If month unknown count as a match
    if (record->month<=0) {return 1;}
    //If month too late, not a match
    if (record->month>month2) {return 0;}
    if (record->month==month2) {
      //If day unknown count as a match
      if (record->day<=0) {return 1;}
      //If day too late, not a match
      if (record->day>day2) {return 0;}
    }
  }
  return 1;
}

//Check if an event matches the search
int searchmatch(WIN_DATA *win,REC_DATA *record)
{
  if ((srchmask&(1<<record->type))==0) {return 0;}
  if (srchrange!=0 && recinrange(record,srchyear1,srchmonth1,srchday1,srchyear2,srchmonth2,srchday2)==0) {return 0;}
  if (wildcardmatch(win->text+record->textoff,srchstring,srchcasesen)==0) {return 0;}
  return 1;
}

//Routine to perform editing on data files
//If bit 0 is set a delete is performed, if bit 1 an add is performed
int editevent(WIN_DATA *win,int flags)
{
  int curfile=-1,loop,offset,offset2,nextfile,state,onradio,month;
  int updatescan=0,updatesearch=0;
  char newtext[400];
  _swi(Hourglass_On,0);
  if ((flags&2)!=0) {
    //Build the new text string first to see if there is going to be a problem
    offset=0;
    for (loop=0x22;loop<=0x26;loop++) {
      writablefield_get_value(0,win->window,loop,newtext+offset,400,&state);
      offset+=state-1;
      newtext[offset++]=' ';
    }
    newtext[offset++]=0;
    parsestring(newtext);
    if (newtext[0]<32) {
      if (win->recorddata!=NULL) {flex_free((flex_ptr)&win->recorddata);}
      if (win->text!=NULL) {flex_free((flex_ptr)&win->text);}
      return reporterror("e5",0);
    }
  }

  if ((flags&1)!=0) {
    //Check to see if the event being deleted is in either of the windows
    ignoretypes=ignoreyears=0;
    sortwin[0]=win;
    sortwin[1]=&scanwin;
    for (loop=0;loop<sortwin[1]->records && joincompare(&sortwin[0]->recorddata[0],&sortwin[1]->recorddata[loop])!=0;loop++) {}
    if (loop<sortwin[1]->records) {
      //Found a match so delete it
      updatescan=1;
      for (loop++;loop<sortwin[1]->records;loop++) {
        sortwin[1]->recorddata[loop-1]=sortwin[1]->recorddata[loop];
      }
      sortwin[1]->records--;
    }
    sortwin[1]=&srchwin;
    for (loop=0;loop<sortwin[1]->records && joincompare(&sortwin[0]->recorddata[0],&sortwin[1]->recorddata[loop])!=0;loop++) {}
    if (loop<sortwin[1]->records) {
      //Found a match so delete it
      updatesearch=1;
      for (loop++;loop<sortwin[1]->records;loop++) {
        sortwin[1]->recorddata[loop-1]=sortwin[1]->recorddata[loop];
      }
      sortwin[1]->records--;
    }

    //Perform delete, load all of the data into the win
    if (win->recorddata[0].fromown==0) {
      //Save to month file
      curfile=win->recorddata[0].month;
    }
    else
    {
      //Save to own file
      curfile=0;
    }
    if (loadallevents(win,todaypaths[0],curfile,0,0)==0) {
      //Error so lose all data
      if (win->text!=NULL) {flex_free((flex_ptr)&win->text);}
      if (win->recorddata!=NULL) {flex_free((flex_ptr)&win->recorddata);}
      return 1;
    }

    //Now scan for exact matches to data and remove duplicate and initial
    sortwin[0]=sortwin[1]=win;
    for (loop=1;loop<win->records && joincompare(&win->recorddata[0],&win->recorddata[loop])!=0;loop++) {}
    offset=0;
    for (offset2=1;offset2<win->records;offset2++) {
      if (offset2!=loop) {
        win->recorddata[offset++]=win->recorddata[offset2];
      }
    }
    if (loop<win->records) {
      win->records-=2;
    }
    else
    {
      win->records--;
    }
  }

  if ((flags&2)!=0) {
    //Peform add
    radiobutton_get_state(0,win->window,0x1a,&state,&onradio);
    stringset_get_selected(1,win->window,1,&month);
    month++;
    if (state!=0) {
      //Save to month file
      nextfile=month;
    }
    else
    {
      //Save to own file
      nextfile=0;
    }
    if (curfile!=nextfile) {
      if (curfile!=-1) {saveallevents(win,todaypaths[0],curfile,0);}
      curfile=nextfile;
      win->records=win->maxrecords=0;
      win->curoff=win->maxoff=0;
      if (loadallevents(win,todaypaths[0],curfile,0,0)==0) {
        //Error so lose all data
        if (win->text!=NULL) {flex_free((flex_ptr)&win->text);}
        if (win->recorddata!=NULL) {flex_free((flex_ptr)&win->recorddata);}
        return 1;
      }
    }

    //Add the new event and sort into order
    if (makeroomfordata(win,recordlimit)==NULL) {
      if (win->recorddata!=NULL) {flex_free((flex_ptr)&win->recorddata);}
      if (win->text!=NULL) {flex_free((flex_ptr)&win->text);}
      return NULL;
    }

    //Read data for the event into memory block
    win->recorddata[win->records].year=findnumber(win->window,19,unknown_year);
    win->recorddata[win->records].month=month;
    win->recorddata[win->records].day=findnumber(win->window,3,0);
    stringset_get_selected(1,win->window,0x28,&win->recorddata[win->records].type);
    win->recorddata[win->records].type++;
    win->recorddata[win->records].fromown=(curfile==0);

    //Check that the events have years and reminders do not
    if (win->recorddata[win->records].type==1 && win->recorddata[win->records].year==unknown_year) {win->recorddata[win->records].type=2;}
    if (win->recorddata[win->records].type==2 && win->recorddata[win->records].year!=unknown_year) {win->recorddata[win->records].type=1;}

    stringset_get_selected(1,win->window,29,&win->recorddata[win->records].code);
    win->recorddata[win->records].textoff=win->curoff;
    sprintf(win->text+win->curoff,"%s",newtext);
    addeventtolist(win);

    //Check if the new event needs to be added to the scan window
    if (testifcorrectdate(&win->recorddata[win->records-1])!=0) {
      //Add to the window and sort the contents
      if (makeroomfordata(&scanwin,-recordlimit)!=NULL) {
        scanwin.recorddata[scanwin.records]=win->recorddata[win->records-1];
        scanwin.recorddata[scanwin.records].textoff=scanwin.curoff;
        sprintf(scanwin.text+scanwin.curoff,"%s",newtext);
        addeventtolist(&scanwin);
        updatescan=1;

        //Sort the events
        sortwin[0]=sortwin[1]=&scanwin;
      }
    }

    //Check if the new event needs to be added to the srch window
    if (srchdone!=0 && searchmatch(win,&win->recorddata[win->records-1])!=0) {
      //Add to the window and sort the contents
      if (makeroomfordata(&srchwin,-recordlimit)!=NULL) {
        srchwin.recorddata[srchwin.records]=win->recorddata[win->records-1];
        srchwin.recorddata[srchwin.records].textoff=srchwin.curoff;
        sprintf(srchwin.text+srchwin.curoff,"%s",newtext);
        addeventtolist(&srchwin);
        updatesearch=1;

        //Sort the events
        sortwin[0]=sortwin[1]=&srchwin;
        qsort((void *)srchwin.recorddata,(size_t)srchwin.records,sizeof(REC_DATA),comparerecords);
      }
    }

    //Sort all of the data into type and date order
    sortwin[0]=sortwin[1]=win;
    ignoretypes=ignoreyears=0;
    qsort((void *)win->recorddata,(size_t)win->records,sizeof(REC_DATA),joincompare);
  }

  //Save the final output file and clear memory
  if (curfile!=-1) {saveallevents(win,todaypaths[0],curfile,0);}
  if (win->text!=NULL) {flex_free((flex_ptr)&win->text);}
  if (win->recorddata!=NULL) {flex_free((flex_ptr)&win->recorddata);}

  //Update other windows if needed
  if (updatescan!=0) {
    sprintf(newtext,"%d",scanwin.records);
    displayfield_set_value(0,scanwin.toolbar,14,newtext);
    justifywindow(&scanwin,1);
  }
  if (updatesearch!=0) {
    sprintf(newtext,"%d",srchwin.records);
    displayfield_set_value(0,srchwin.toolbar,14,newtext);
    justifywindow(&srchwin,1);
  }
  _swi(Hourglass_Off,0);
  return 1;
}

//This code is called when add event is selected
int addnew_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int loop,block[10],code;
  ObjectId tool_win;
  ComponentId tool_icon;

  //Initialise add window and perform the add
  if (editevent(&addwin,2)==NULL) {return NULL;}

  //If the event was not on a special day, clear text from window
  stringset_get_selected(1,addwin.window,29,&code);
  if (code==0) {
    for (loop=34;loop<=38;loop++) {
      writablefield_set_value(0,addwin.window,loop,"");
    }
  }

  //Move to first icon of text if in text icons
  _swi(Wimp_GetCaretPosition,_IN(1),block);
  window_wimp_to_toolbox(0,block[0],block[1],&tool_win,&tool_icon);
  if (tool_win==addwin.window && tool_icon>=34 && tool_icon<=38) {
    gadget_set_focus(0,addwin.window,34);
  }
  return 1;
}

//This code is called when delete is selected
int delete_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  _kernel_oserror error;

  //First check if delete is really wanted
  error.errnum=0;
  _swi(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3),&messages,"e7",error.errmess,252);
  if (_swi(Wimp_ReportError,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5)|_RETURN(1),&error,1+2+4+(1<<8)+(4<<9),"Today",0,1,0)==2) {return 1;}

  //Call routine to delete event in editwin and not add a new event
  editevent(&editwin,1);
  toolbox_hide_object(0,editwin.window);
  return 1;
}

//This code is called when edit is selected
int edit_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  //Call routine to delete event and add a new event
  editevent(&editwin,3);
  toolbox_hide_object(0,editwin.window);
  return 1;
}

//This code is called when an item in the type list is selected
int altertypes_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int tick;
  menu_get_tick(0,id_block->self_id,id_block->self_component,&tick);
  tick=1-(tick==1);
  menu_set_tick(0,id_block->self_id,id_block->self_component,tick);
  return 1;
}

//This code is called when quit is selected on the menu
int menuquit_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
//  fclose(debug);
  _swi(MessageTrans_CloseFile,_IN(0),&messages);
  exit(0);
  return 1;
}

//Fix toolbar to window
int fixtoolbar(WIN_DATA *win,char *toolname)
{
  ObjectTemplateHeader *objheader;
  WindowTemplate *wintemp;

  //Load toolbar data and copy to a block so it can be edited
  toolbox_template_lookup(0,toolname,(void *)&objheader);

  //Find height of toolbar and set width as huge
  wintemp=objheader->body;
  win->toolbarheight=wintemp->window.visible_area.ymax-wintemp->window.visible_area.ymin;
  wintemp->window.visible_area.xmax=wintemp->window.visible_area.xmin+100000;

  //Create toolbar window and set as a toolbar
  toolbox_create_object(1,objheader,&win->toolbar);
  window_set_tool_bars(2,win->window,0,win->toolbar,0,0);
  return 1;
}

//This code initialises a record window
int createrecwindow(char *winname,char *toolname,WIN_DATA *win)
{
  //Create main window
  toolbox_create_object(0,winname,&win->window);

  //Add the toolbar
  fixtoolbar(win,toolname);

  //Set as no record data present
  win->records=win->maxrecords=0;
  win->recorddata=NULL;
  win->text=NULL;
  win->curoff=win->maxoff=0;
  return 1;
}

//This code is called when the month is changed in the scan window
int stringchanged_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int month;
  stringset_get_selected(1,id_block->self_id,id_block->self_component,&month);
  numberrange_set_bounds(2,id_block->self_id,id_block->self_component+2,0,daysinmonth[month+1],0,0);
  return 1;
}

//This code sets the date in the window to today
int settocurrentdate(ObjectId window,ComponentId day,ComponentId month,ComponentId year)
{
  int UTC[2],ordinals[10];

  //Read time
  UTC[0]=3;
  _swi(OS_Word,_IN(0)|_IN(1),14,UTC);
  _swi(Territory_ConvertTimeToOrdinals,_IN(0)|_IN(1)|_IN(2),-1,UTC,ordinals);

  //Now setup the window
  stringset_set_selected(0,window,month,monthnames[ordinals[5]]);
  numberrange_set_value(0,window,day,ordinals[4]);
  if (year>0) {numberrange_set_value(0,window,year,ordinals[6]);}
  return 1;
}

//This code scans a file
int scanfile(int filenum)
{
  FILE *fileptr;
  char filename[256];
  int recresult;

  //Open the file
  sprintf(filename,"%s.TodayData.%s",todaypaths[0],monthfiles[filenum]);
  fileptr=fopen(filename,"rb");
  //If file could not be opened, treat as empty
  if (fileptr==NULL) {return 1;}

  //Now cycle through loading records
  do {
    recresult=loadnextrecord(fileptr,&scanwin,filenum,recordlimit,0,0);
    if (recresult==0) {
      fclose(fileptr);
      return 0;
    }
    if (recresult>0) {
      //Found a record so deal with it
      if (testifcorrectdate(&scanwin.recorddata[scanwin.records])!=0) {
        //Event is on the correct day to add to window list
        addeventtolist(&scanwin);
      }
    }
  } while (recresult>0);
  fclose(fileptr);
  return 1;
}

//This code is called when a file is dragged to !Today
int draggingjoinfile_message(WimpMessage *message,void *handle)
{
  ObjectId self_id;
  ComponentId self_component;
  char filename[256];
  int type;
  unsigned int flags;

  window_wimp_to_toolbox(0,message->data.data_load.destination_window,message->data.data_load.destination_icon,&self_id,&self_component);
  if (self_id==joinwindow) {
    //Look to see if dragged item contains a directory 'TodayData'
    sprintf(filename,"%s.TodayData",message->data.data_load.leaf_name);
    type=_swi(OS_File,_IN(0)|_IN(1)|_RETURN(0),17,filename);
    if (type==2 || type==3) {
      //Only copy path if it contains data
      displayfield_set_value(0,joinwindow,4,message->data.data_load.leaf_name);
      gadget_get_flags(0,joinwindow,5,&flags);
      flags&=~0x80000000;
      gadget_set_flags(0,joinwindow,5,flags);
    }
  }


  return 1;
}

//This code is called when font is changed
int fontchange_message(WimpMessage *message,void *handle)
{
  findfontwidths();
  justifywindow(&scanwin,1);
  justifywindow(&srchwin,1);
  return 1;
}

//This code is called when mode is changed
int modechange_message(WimpMessage *message,void *handle)
{
  findfontwidths();
  //fixtoolbar(&scanwin,"ScanTool");
  //fixtoolbar(&srchwin,"SrchTool");
  justifywindow(&scanwin,0);
  justifywindow(&srchwin,0);
  return 1;
}

//Find day of the week, long winded way but should work
int finddayofweek(int day,int month)
{
  int ordinals[10],UTC[2];
  //Finds time, converts to ordinals, alters month and day
  //Finds UTC time and converts back to ordinals to get day of week
  UTC[0]=3;
  _swi(OS_Word,_IN(0)|_IN(1),14,UTC);
  _swi(Territory_ConvertTimeToOrdinals,_IN(0)|_IN(1)|_IN(2),-1,UTC,ordinals);
  ordinals[4]=day;
  ordinals[5]=month;
  _swi(Territory_ConvertOrdinalsToTime,_IN(0)|_IN(1)|_IN(2),-1,UTC,ordinals);
  _swi(Territory_ConvertTimeToOrdinals,_IN(0)|_IN(1)|_IN(2),-1,UTC,ordinals);
  return ordinals[7];
}

//This code does a scan of the current date
int beginscan(void)
{
  char text[12];

  _swi(Hourglass_On,0);
  //Read date to scan
  stringset_get_selected(1,scanwin.toolbar,1,&scanmonth);
  scanmonth++;
  scanday=findnumber(scanwin.toolbar,3,0);
  if (scanday==0) {return reporterror("e6",0);}
  scandayofweek=finddayofweek(scanday,scanmonth);

  //Clear all of previous scan data
  if (scanwin.recorddata!=NULL) {flex_free((flex_ptr)&scanwin.recorddata);}
  if (scanwin.text!=NULL) {flex_free((flex_ptr)&scanwin.text);}
  scanwin.records=scanwin.maxrecords=0;
  scanwin.curoff=scanwin.maxoff=0;

  //Do the scan
  if (scanfile(scanmonth)==1) {
    //Only do second scan if no error in first
    scanfile(0);
  }

  //Tidy up the memory
  if (scanwin.recorddata!=NULL) {
    flex_extend((flex_ptr)&scanwin.recorddata,scanwin.records*sizeof(REC_DATA));
    scanwin.maxrecords=scanwin.records;
  }
  if (scanwin.text!=NULL) {
    flex_extend((flex_ptr)&scanwin.text,scanwin.curoff*sizeof(char));
    scanwin.maxoff=scanwin.curoff;
  }

  //Update records icon
  sprintf(text,"%d",scanwin.records);
  displayfield_set_value(0,scanwin.toolbar,14,text);

  //Sort the events
  sortwin[0]=sortwin[1]=&scanwin;
  qsort((void *)scanwin.recorddata,(size_t)scanwin.records,sizeof(REC_DATA),comparerecords);

  //Setup the window
  justifywindow(&scanwin,1);
  _swi(Hourglass_Off,0);
  return 1;
}

//This routine checks if the string pointed to is a valid set
//Returns the length or -ve if invalid
int check_valid_set(char *str)
{
  int len,len2,c;

  if (str[0]=='[') {
    //Start of a set
    for (len=1;str[len]!=']';) {
      c=decode_symbol(str+len,&len2,1);
      //If an invalid symbol or an early terminator abandon the symbol
      if (c<32) {return -1;}
      len+=len2;
      if (c<256 && str[len]=='-') {
        //Have found a range symbol
        len++;
        c=decode_symbol(str+len,&len2,1);
        //If an invalid symbol or an early terminator abandon the symbol
        if (c<32 || c>255) {return -1;}
        len+=len2;
      }
    }
    len++;
  }
  else
  {
    //Single magic symbol
    c=decode_symbol(str,&len,1);
    if (c<0) {return -1;}
  }
  return len;
}

//This code is called when search is selected
int search_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int loop,searchresult=1,recresult=1,state,len;
  int offset=0,offset2=0,offset3=0,nextchar,char2;
  char text[12],filename[256],newsrch[64];
  FILE *fileptr;

  _swi(Hourglass_On,0);

  //Read string to search and parse it
  writablefield_get_value(0,srchwin.toolbar,1,newsrch,64,&len);
  //Remove leading spaces
  for (;newsrch[offset]==32 || newsrch[offset]==160;offset++) {}
  //Copy all up to terminator removing hard spaces
  for (;newsrch[offset]>=32;offset++) {
    nextchar=newsrch[offset];
    if (nextchar==160) {nextchar=32;}
    newsrch[offset2++]=nextchar;
    if (nextchar!=32) {offset3=offset2;}
  }
  //Put terminator after last non-space
  newsrch[offset3++]=0;

  //Check that all magic characters are valid
  for (offset=0;offset<offset3;) {
    char2=decode_symbol(newsrch+offset,&len,0);
    //Check for magic characters in char2
    switch (char2) {
      case -1:
        //Have found an invalid magic character so abort
        reporterror("e9",newsrch+offset);
        return 1;
        break;
      case 2000:
      case 1000:
      case 1001:
      case 1002:
      case 1003:
      case 1004:
        //Valid sybols with no follow on sets so ok
        offset+=len;
      case 2001:
      case 2002:
      case 2003:
        //Valid symbol but need to check the following set
        offset+=len;
        len=check_valid_set(newsrch+offset);
        if (len<=0) {
          //Set is invalid so error
          reporterror("e10",newsrch+offset);
          return 1;
        }
        offset+=len;
        break;
      case '[':
        //Check for a valid set
        len=check_valid_set(newsrch+offset);
        if (len<=0) {
          //Set is invalid so error
          reporterror("e10",newsrch+offset);
          return 1;
        }
        offset+=len;
        break;
      default:
        //Just a character so move on
        offset+=len;
    }
  }

  //Clear all of previous search data
  if (srchwin.recorddata!=NULL) {flex_free((flex_ptr)&srchwin.recorddata);}
  if (srchwin.text!=NULL) {flex_free((flex_ptr)&srchwin.text);}
  srchwin.records=srchwin.maxrecords=0;
  srchwin.curoff=srchwin.maxoff=0;

  //Check for event mask
  popup_get_menu(0,srchwin.toolbar,0x16,&typemenu);
  srchmask=1;
  for (loop=1;loop<lenofeventlists[0];loop++) {
    menu_get_tick(0,typemenu,loop,&state);
    srchmask+=(state!=0)<<loop;
  }

  //Check the range for the search
  optionbutton_get_state(0,srchwin.toolbar,2,&srchcasesen);
  //Copy all up to terminator removing hard spaces
  for (offset=0;newsrch[offset]>=32;offset++) {
    nextchar=newsrch[offset];
    if (srchcasesen==0) {nextchar=toupper(nextchar);}
    srchstring[offset]=nextchar;
  }
  srchstring[offset++]=0;

  optionbutton_get_state(0,srchwin.toolbar,11,&srchrange);
  numberrange_get_value(0,srchwin.toolbar,19,&srchyear1);
  stringset_get_selected(1,srchwin.toolbar,15,&srchmonth1);
  numberrange_get_value(0,srchwin.toolbar,17,&srchday1);
  srchmonth1++;
  numberrange_get_value(0,srchwin.toolbar,20,&srchyear2);
  stringset_get_selected(1,srchwin.toolbar,16,&srchmonth2);
  numberrange_get_value(0,srchwin.toolbar,18,&srchday2);
  srchmonth2++;

  //Run through all files searching in them
  for (loop=0;loop<=12 && searchresult!=0;loop++) {
    sprintf(filename,"%s.TodayData.%s",todaypaths[0],monthfiles[loop]);
    fileptr=fopen(filename,"rb");
    //If file cannot be opened, treat it as empty
    if (fileptr==NULL) {
      recresult=0;
    }
    else
    {
      recresult=1;
    }
    while (searchresult!=0 && recresult>0) {
      recresult=loadnextrecord(fileptr,&srchwin,loop,recordlimit,0,0);
      if (recresult==0) {searchresult=0;}
      if (recresult>0) {
        //Check if event type is in search mask
        if (searchmatch(&srchwin,&srchwin.recorddata[srchwin.records])!=0) {
          addeventtolist(&srchwin);
        }
      }
    }
    fclose(fileptr);
    _swi(Hourglass_Percentage,_IN(0),((loop+1)*100)/13);
  }

  //Tidy up the memory
  if (srchwin.recorddata!=NULL) {
    flex_extend((flex_ptr)&srchwin.recorddata,srchwin.records*sizeof(REC_DATA));
    srchwin.maxrecords=srchwin.records;
  }
  if (srchwin.text!=NULL) {
    flex_extend((flex_ptr)&srchwin.text,srchwin.curoff*sizeof(char));
    srchwin.maxoff=srchwin.curoff;
  }

  //Update records icon
  sprintf(text,"%d",srchwin.records);
  displayfield_set_value(0,srchwin.toolbar,14,text);

  //Sort the events
  sortwin[0]=sortwin[1]=&srchwin;
  qsort((void *)srchwin.recorddata,(size_t)srchwin.records,sizeof(REC_DATA),comparerecords);

  //Setup the window
  justifywindow(&srchwin,1);
  _swi(Hourglass_Off,0);
  srchdone=1;
  return 1;
}

//This code is called when scan is selected
int scan_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  beginscan();
  return 1;
}

//This code is called when scan today is selected
int scantoday_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  settocurrentdate(scanwin.toolbar,3,1,0);
  beginscan();
  return 1;
}

//Draws a piece of text onto screen, using fonts if needed
//If width is nonzero it right justifies to that width
int placetext(char *text,int x,int y,int width)
{
  if (version>=350) {
    //Can use wimp_textop
    if (width!=0) {
      _swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5),0x80000002,text,-1,-1,x+width,y-20);
    }
    else
    {
      _swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5),2,text,-1,-1,x,y-20);
    }
  }
  else
  {
    if (width!=0) {
      _swi(OS_Plot,_IN(0)|_IN(1)|_IN(2),4,x+width-16*strlen(text),y);
    }
    else
    {
      _swi(OS_Plot,_IN(0)|_IN(1)|_IN(2),4,x,y);
    }
    printf("%s",text);
  }
  return 1;
}

//Redraws the list of events
int redrawlist(WIN_DATA *window)
{
  int block[25],xmin,ymax,redrawcode,loop,x,y,offset,termchar;
  int topcoord,botcoord,firstrow,lastrow,highlight,displayfield;
  int rfirst,rlast;
  char text[12],*string;

  //Now find the size of the window
  window_get_wimp_handle(0,window->window,&block[0]);

  redrawcode=_swi(Wimp_RedrawWindow,_IN(1)|_RETURN(0),block);
  xmin=block[1]-block[5];
  ymax=block[4]-block[6]-window->toolbarheight;

  while (redrawcode!=0)
  {
    //Find rows to redraw
    topcoord=ymax-8-block[10];
    botcoord=ymax-8-block[8];
    firstrow=topcoord/40;
    lastrow=botcoord/40;

    for (loop=0;loop<window->records;loop++) {
      //Check if record needs to be drawn
      rfirst=window->recorddata[loop].startline;
      rlast=window->recorddata[loop].endline;
      if ((rfirst>=firstrow && rfirst<=lastrow) || (rlast>=firstrow && rlast<=lastrow) || (rfirst<=firstrow && rlast>=lastrow)) {
        y=ymax-16-40*window->recorddata[loop].startline;
        x=xmin+8;
        displayfield=(loop==0);

        //Set text colour
        highlight=window->recorddata[loop].fromown&highlightstate;
        if (version>=350) {
          if (highlight!=0) {
            _swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2),0,0x0000ff00,0xdddddd00);
          }
          else
          {
            _swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2),0,0x00000000,0xdddddd00);
          }
        }
        else
        {
          if (highlight!=0) {
            _swi(Wimp_SetColour,_IN(0),11);
          }
          else
          {
            _swi(Wimp_SetColour,_IN(0),7);
          }
        }

        //Display year
        if (displayfield==1 || window->recorddata[loop].year!=window->recorddata[loop-1].year) {
          displayfield=1;
          if (window->recorddata[loop].year==unknown_year) {
            sprintf(text,"????");
          }
          else
          {
            if (window->recorddata[loop].year<0) {
              sprintf(text,"%dBC",-window->recorddata[loop].year);
            }
            else
            {
              sprintf(text,"%d",window->recorddata[loop].year);
            }
          }
          placetext(text,x,y,yeartextwidth);
        }
        x+=yearwidth;

        //Only display date in the search window
        if (window->listdates!=0) {
          //Display month
          if (displayfield==1 || window->recorddata[loop].month!=window->recorddata[loop-1].month) {
            displayfield=1;
            if (window->recorddata[loop].month<=0) {
              placetext(monthnames[0],x,y,0);
            }
            else
            {
              placetext(monthnames[window->recorddata[loop].month],x,y,0);
            }
          }
          x+=monthwidth;

          //Display day
          if (displayfield==1 || window->recorddata[loop].day!=window->recorddata[loop-1].day) {
            displayfield=1;
            if (window->recorddata[loop].day<=0) {
              sprintf(text,"??");
            }
            else
            {
              sprintf(text,"%d",window->recorddata[loop].day);
            }
            placetext(text,x,y,daytextwidth);
          }
          x+=daywidth;
        }

        //Display type
        placetext(eventlists[0][window->recorddata[loop].type].str+2,x,y,0);
        x+=typewidth;

        //Display event
        string=window->text+window->recorddata[loop].textoff;
        do {
          //Find the terminating char and replace by a zero
          for (offset=0;string[offset]>=32;offset++) {}
          termchar=string[offset];
          string[offset]=0;
          placetext(string,x,y,0);
          string[offset]=termchar;
          if (termchar==13) {
            //Need to do next line
            string+=offset+1;
            offset=0;
            y-=40;
          }
        } while (string[offset]!=0);
      }
    }

    redrawcode=_swi(Wimp_GetRectangle,_IN(1)|_RETURN(0),block);
  }
  return 1;
}

//Redraws the list of events in the windows
int redraw_event(int event_code, WimpPollBlock *event, IdBlock *id_block,void *handle)
{
  if (id_block->self_id==scanwin.window) {redrawlist(&scanwin);}
  if (id_block->self_id==srchwin.window) {redrawlist(&srchwin);}
  return 1;
}

//Alters state of highlight flag
int highlight_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  BBox extent;
  highlightstate^=1;
  menu_set_tick(0,id_block->self_id,id_block->self_component,highlightstate);

  //Redraw the windows
  window_get_extent(0,scanwin.window,&extent);
  window_force_redraw(0,scanwin.window,&extent);
  window_get_extent(0,srchwin.window,&extent);
  window_force_redraw(0,srchwin.window,&extent);
  return 1;
}

//Sets shaded items in save menu
int openingsave_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  menu_set_fade(0,id_block->self_id,0,scanwin.records==0);
  menu_set_fade(0,id_block->self_id,1,scanwin.records==0);
  menu_set_fade(0,id_block->self_id,2,srchwin.records==0);
  menu_set_fade(0,id_block->self_id,3,srchwin.records==0);
  return 1;
}

//Routine deletes a record after copying data to kept record
int deleterecord(int del,int keep)
{
  int loop;
  if (sortwin[0]->recorddata[keep].type==0 || (sortwin[0]->recorddata[keep].type<=2 && sortwin[0]->recorddata[del].type>2)) {
    //Type of kept record is unknown or it is an event/rmeinder
    //and other record is more specific so copy type of other record
    sortwin[0]->recorddata[keep].type=sortwin[0]->recorddata[del].type;
  }
  if (sortwin[0]->recorddata[keep].year==unknown_year) {
    //Copy year if kept record has unknown year
    sortwin[0]->recorddata[keep].year=sortwin[0]->recorddata[del].year;
    //Check that the events have years and reminders do not
    if (sortwin[0]->recorddata[keep].type==1 && sortwin[0]->recorddata[keep].year==unknown_year) {sortwin[0]->recorddata[keep].type=2;}
    if (sortwin[0]->recorddata[keep].type==2 && sortwin[0]->recorddata[keep].year!=unknown_year) {sortwin[0]->recorddata[keep].type=1;}
  }

//  memmove(&sortwin[0]->recorddata[del],&sortwin[0]->recorddata[del+1],(sortwin[0]->records-1-del)*sizeof(REC_DATA));

  for(loop=del;loop<sortwin[0]->records-1;loop++) {
    sortwin[0]->recorddata[loop]=sortwin[0]->recorddata[loop+1];
  }
  sortwin[0]->records--;
  return 1;
}

//Loads all of the event types from a messages file
int loadeventlist(MessagesFD *messages,int listnum)
{
  char string[8];
  _kernel_oserror *error=NULL;
  int num;

  if (messages==NULL) {
    //Messages file does not exist so set to standard today types
    sprintf(eventlists[listnum][0].str,"B,Birth");
    eventlistmaps[listnum][0]=0;
    sprintf(eventlists[listnum][1].str,"S,Event");
    eventlistmaps[listnum][1]=1;
    sprintf(eventlists[listnum][2].str,"R,Reminder");
    eventlistmaps[listnum][2]=2;
    sprintf(eventlists[listnum][3].str,"D,Death");
    eventlistmaps[listnum][3]=3;
    lenofeventlists[listnum]=4;
  }
  else
  {
    lenofeventlists[listnum]=0;
    for (num=0;error==NULL && lenofeventlists[listnum]<maxeventlen;num++) {
      sprintf(string,"ev%d",num);
      error=_swix(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3),messages,string,eventlists[listnum][lenofeventlists[listnum]].str,20);
      if (error==NULL) {
        eventlistmaps[listnum][lenofeventlists[listnum]]=lenofeventlists[listnum];
        lenofeventlists[listnum]++;
      }
    }
  }
  return 1;
}

//Setup menus with event types
int setuptypemenus(int oldnumoftypes)
{
  char *strings,*stringsptr;
  int menuitem[10],loop;
  ComponentId entry;
  //Setup the type menu in the add window and list for search window
  flex_alloc((flex_ptr)&strings,40*lenofeventlists[0]);
  stringsptr=strings;
  for (loop=1;loop<lenofeventlists[0];loop++) {
    if (loop>1) {
      stringsptr[0]=',';
      stringsptr++;
    }
    stringsptr+=sprintf(stringsptr,"%s",eventlists[0][loop].str+2);
  }
  stringset_set_available(0,addwin.window,0x28,strings);
  stringset_set_available(0,editwin.window,0x28,strings);
  flex_free((flex_ptr)&strings);

  menuitem[0]=1;
  menuitem[3]=20;
  menuitem[4]=0;
  menuitem[5]=0;
  menuitem[6]=0;
  menuitem[7]=13;
  menuitem[8]=0;
  menuitem[9]=0;

  for (loop=1;loop<lenofeventlists[0];loop++) {
    if (loop<oldnumoftypes) {
      menu_set_entry_text(0,typemenu,loop,eventlists[0][loop].str+2);
    }
    else
    {
      menuitem[1]=loop;
      menuitem[2]=(int)(eventlists[0][loop].str+2);
      menu_add_entry(0,typemenu,-2,(char *)menuitem,&entry);
    }
  }
  return 1;
}

//Saves the specified list to the file
int savelisttomessages(FILE *fileptr2,int messlinetype)
{
  int loop,loop2,count=1;
  switch (messlinetype) {
    case 0:
      //Write out the event type strings
      for (loop=0;loop<lenofeventlists[0];loop++) {
        fprintf(fileptr2,"ev%d:%s\n",loop,eventlists[0][loop].str);
      }
      break;
    case 1:
    case 2:
    case 3:
      //Write out the word lists
      for (loop=0;loop<wordlistlen[messlinetype-1];loop++) {
        fprintf(fileptr2,"%s%d:%s\n",wordlisttoken[messlinetype-1],loop+1,wordlist[messlinetype-1][loop].str);
      }
      break;
    case 4:
      for (loop=0;loop<wordlistlen[3];loop++) {
        if (thesauruslist[loop]==loop) {
          fprintf(fileptr2,"%s%d:%s",wordlisttoken[messlinetype-1],count++,wordlist[3][loop].str);
          for (loop2=loop+1;loop2<wordlistlen[3];loop2++) {
            if (thesauruslist[loop2]==loop) {
              fprintf(fileptr2,"_%s",wordlist[3][loop2].str);
            }
          }
          fprintf(fileptr2,"\n");
        }
      }
      break;
  }
  return 1;
}

//Joins and sorts data
int joindata_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int loadedevents=0,savedevents=0,possibleduplicates=0,messlinetype;
  int filenum,loop,loop2,loop3,len,result,listnum,listnum2,namesmatch;
  int oldnumoftypes,letter,state,selected,recresult,pathnum;
  int curtempfile,nexttempfile,savedlinetype[1+numofwordlists];
  char text1[12],text2[12],text3[12],text4[12];
  char filepath[256],filepath2[256],messline[256];
  _kernel_oserror error,*error2;
  WIN_DATA joindata;
  FILE *fileptr,*fileptr2,*tempfileptr;

  _swi(Hourglass_On,0);
  joindata.maxrecords=0;
  joindata.recorddata=NULL;
  joindata.text=NULL;
  joindata.maxoff=0;
  joindata.curoff=joindata.records=0;
  oldnumoftypes=lenofeventlists[0];

  //Set mapping buffer for first set of files to load
  loadeventlist(&messages[0],1);

  todaypaths[1][0]=0;
  if (event->hdr.event_code==9) {
    displayfield_get_value(0,joinwindow,4,todaypaths[1],256,&len);

    //Build the new event type list by loading the lists into two buffers
    sprintf(todaypaths[1]+len-1,".Messages");
    error2=_swix(MessageTrans_OpenFile,_IN(0)|_IN(1)|_IN(2),&messages[1],todaypaths[1],0);
    todaypaths[1][len-1]=0;
    if (error2==NULL) {
      loadeventlist(&messages[1],2);
      loadlists(2);
      _swi(MessageTrans_CloseFile,_IN(0),&messages[1]);
    }
    else
    {
      loadeventlist(NULL,2);
    }

    //Now combine the event lists into one
    lenofeventlists[0]=0;
    for (listnum=1;listnum<=2;listnum++) {
      for (loop=0;loop<lenofeventlists[listnum];loop++) {
        //Find position of the event in the combined list
        for (loop2=0;loop2<lenofeventlists[0] && caseinsencmp(eventlists[listnum][loop].str+2,eventlists[0][loop2].str+2)==0;loop2++) {}
        //Do not allow too many events, set to unknown all letters used
        if (loop2==lenofeventlists[0] && lenofeventlists[0]==maxeventlen) {
          loop2=0;
        }
        eventlistmaps[listnum][loop]=loop2;
        //Check to see if the name already exists and if not copy it across
        if (loop2==lenofeventlists[0]) {
          //Add the string to the list
          sprintf(eventlists[0][lenofeventlists[0]].str,"%s",eventlists[listnum][loop].str);
          eventlistmaps[0][lenofeventlists[0]]=lenofeventlists[0];

          //Now check to see if the event has the same letter as any other event
          //or if it has the same name but a different letter
          for (listnum2=1;listnum2<=2;listnum2++) {
            for (loop2=0;loop2<lenofeventlists[listnum2];loop2++) {
              namesmatch=caseinsencmp(eventlists[0][lenofeventlists[0]].str+2,eventlists[listnum2][loop2].str+2);
              if ((namesmatch==1 && eventlists[0][lenofeventlists[0]].str[0]!=eventlists[listnum2][loop2].str[0]) || (namesmatch==0 && eventlists[0][lenofeventlists[0]].str[0]==eventlists[listnum2][loop2].str[0])) {
                //Set type to unknown as there is a clash with another  type
                //(same name different letter or same letter different name)
                eventlists[0][lenofeventlists[0]].str[0]=32;
              }
            }
          }

          //Now increase the length of the combined list
          lenofeventlists[0]++;
        }
      }
    }

    //Now run through all of the undefined event types using first free letter
    //that is contained within the string
    for (loop=0;loop<lenofeventlists[0];loop++) {
      if (eventlists[0][loop].str[0]==32) {
        loop3=1;
        do {
          loop3++;
          letter=toupper(eventlists[0][loop].str[loop3]);
          if (letter>='A' && letter<='Z') {
            for (loop2=0;loop2<lenofeventlists[0] && eventlists[0][loop2].str[0]!=letter;loop2++) {}
          }
        } while ((letter>=32 && letter<'A') || letter>'Z' || loop2<lenofeventlists[0]);
        if (letter>='A' && letter<='Z') {eventlists[0][loop].str[0]=letter;}
      }
    }

    //Now run through all of the undefined event types using first free letter
    for (loop=0;loop<lenofeventlists[0];loop++) {
      if (eventlists[0][loop].str[0]==32) {
        letter='A'-1;
        do {
          letter=letter+1;
          for (loop2=0;loop2<lenofeventlists[0] && eventlists[0][loop2].str[0]!=letter;loop2++) {}
        } while (loop2<lenofeventlists[0]);
        eventlists[0][loop].str[0]=letter;
      }
    }
  }

  //Shuffle events from the files for todaypaths[0] and todaypaths[1]
  //into the correct temporary files
  //First create the empty temporary files
  for (filenum=0;filenum<=12;filenum++) {
    sprintf(filepath,"%s.TodayData._%s",todaypaths[0],monthfiles[filenum]);
    tempfileptr=fopen(filepath,"w");

    if (tempfileptr==NULL) {
      if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
      if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
      //Restore the event type list
      loadeventlist(&messages[0],0);
      loadlists(1);
      reporterror("e3",filepath);
      return 1;
    }

    fclose(tempfileptr);
  }
  //Now set the current temp file to the own file and open to apend
  //(this is because only 12 files can be open at once)
  curtempfile=0;
  sprintf(filepath,"%s.TodayData._%s",todaypaths[0],monthfiles[0]);
  tempfileptr=fopen(filepath,"a");
  if (tempfileptr==NULL) {
    if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
    if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
    //Restore the event type list
    loadeventlist(&messages[0],0);
    loadlists(1);
    reporterror("e3",filepath);
    return 1;
  }

  for (filenum=0;filenum<=12;filenum++) {
    for (pathnum=0;pathnum<2;pathnum++) {
      if (todaypaths[pathnum][0]>=32) {
        sprintf(filepath,"%s.TodayData.%s",todaypaths[pathnum],monthfiles[filenum]);
        fileptr=fopen(filepath,"rb");
        recresult=1;
        while (fileptr!=NULL && recresult>0) {
          recresult=loadnextrecord(fileptr,&joindata,filenum,0x7fffffff,pathnum+1,0);
          if (recresult==0) {
            fclose(fileptr);
            fclose(tempfileptr);
            if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
            if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
            //Restore the event type list
            loadeventlist(&messages[0],0);
            loadlists(1);
            return 1;
          }
          if (recresult>0) {
            //Want to save the file to the temp files
            if (filenum==0 || joindata.recorddata[0].month<1 || joindata.recorddata[0].month>12) {
              //Record is from the own file or is in a silly month, so save to same filenum as it was loaded from
              nexttempfile=filenum;
            }
            else
            {
              //Save the record to the correct temporary month file
              nexttempfile=joindata.recorddata[0].month;
            }
            //Check if the temp file is already open
            if (nexttempfile!=curtempfile) {
              fclose(tempfileptr);
              curtempfile=nexttempfile;
              sprintf(filepath,"%s.TodayData._%s",todaypaths[0],monthfiles[curtempfile]);
              tempfileptr=fopen(filepath,"a");
              if (tempfileptr==NULL) {
               fclose(fileptr);
                fclose(tempfileptr);
                if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
                if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
                //Restore the event type list
                loadeventlist(&messages[0],0);
                loadlists(1);
                reporterror("e3",filepath);
                return 1;
              }
            }
            //Now save the record to the temp file
            saverecord(tempfileptr,&joindata.recorddata[0],&joindata);
          }
        };
        fclose(fileptr);
      }
    }
    _swi(Hourglass_Percentage,_IN(0),((filenum+1)*50/13));
  }
  fclose(tempfileptr);

  //Now sort the temporary files and remove duplicates
  for (filenum=0;filenum<=12;filenum++) {
    joindata.curoff=joindata.records=0;
    if (loadallevents(&joindata,todaypaths[0],filenum,1,0)==0) {
      //Error so lose all data
      if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
      if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
      //Restore the event type list
      loadeventlist(&messages[0],0);
      loadlists(1);
      return 1;
    }

    //Sort all of the data into date order
    loadedevents+=joindata.records;
    sortwin[0]=sortwin[1]=&joindata;
    ignoretypes=1;
    ignoreyears=0;
    qsort((void *)joindata.recorddata,(size_t)joindata.records,sizeof(REC_DATA),joincompare);

    //Remove all duplicates
    for (loop=joindata.records-1;loop>0;loop--) {
      if (joindata.recorddata[loop].year==unknown_year) {
        //Record has an unknown year so check against all events
        loop2=joindata.records-1;
        ignoreyears=1;
      }
      else
      {
        //Record has a year so only check against same year events
        loop2=loop-1;
        ignoreyears=0;
      }

      //Check against each of the previous records that are on same date
      for (;loop2>=0 && (ignoreyears==1 || (joindata.recorddata[loop2].year==joindata.recorddata[loop].year && joindata.recorddata[loop2].month==joindata.recorddata[loop].month && joindata.recorddata[loop2].day==joindata.recorddata[loop].day));loop2--) {
        if (loop2!=loop) {
          //Compare records
          result=joincompare(&joindata.recorddata[loop2],&joindata.recorddata[loop]);
          switch (result) {
            case -2:
            case 2:
              //Records are not equal so move down list
              break;
            case -1:
              //Records are equal, but second is loop so delete loop2
              deleterecord(loop2,loop);
              //loop2 is below loop, so deleting loop2 will lower loop
              loop--;
              break;
            case 0:
            case 1:
              //Records are equal, so delete loop
              deleterecord(loop,loop2);
              //Deleting loop so do not compare against any other records
              loop2=-1;
              break;
          }
        }
      }
    }

    //Now save all of the data to the files, but check order to be used
    radiobutton_get_state(0,joinwindow,8,&state,&selected);
    if (state==1) {
      //Sort all of the data into type and date order
      ignoretypes=ignoreyears=0;
      qsort((void *)joindata.recorddata,(size_t)joindata.records,sizeof(REC_DATA),joincompare);
      //Save data
      if (saveallevents(&joindata,todaypaths[0],filenum,1)==NULL) {
        if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
        if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
        //Restore the event type list
        loadeventlist(&messages[0],0);
        loadlists(1);
        return NULL;
      }
    }
    else
    {
      //Sort data into possible duplicate state and save
      ignoretypes=1;
      ignoreyears=0;
      qsort((void *)joindata.recorddata,(size_t)joindata.records,sizeof(REC_DATA),joincompare);
      //Save data
      if (savealleventsinduporder(&joindata,todaypaths[0],filenum,1,&possibleduplicates)==NULL) {
        if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
        if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
        //Restore the event type list
        loadeventlist(&messages[0],0);
        loadlists(1);
        return NULL;
      }
    }
    savedevents+=joindata.records;
    _swi(Hourglass_Percentage,_IN(0),50+((filenum+1)*50/13));
  }

  if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
  if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}

  //Now that it has been successful, update the type lists
  setuptypemenus(oldnumoftypes);
  findfontwidths();
  justifywindow(&scanwin,1);
  justifywindow(&srchwin,1);

  //Close messages file before editing it
  _swi(MessageTrans_CloseFile,_IN(0),&messages);
  sprintf(filepath,"%s.Messages",todaypaths[0]);
  fileptr=fopen(filepath,"r");
  if (fileptr==NULL) {
    if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
    if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
    //Restore the event type list
    _swix(MessageTrans_OpenFile,_IN(0)|_IN(1)|_IN(2),&messages,filepath,0);
    loadeventlist(&messages[0],0);
    loadlists(1);
    return reporterror("e4",filepath);
  }
  sprintf(filepath2,"%s._Messages",todaypaths[0]);
  fileptr2=fopen(filepath2,"w");
  if (fileptr2==NULL) {
    if (joindata.text!=NULL) {flex_free((flex_ptr)&joindata.text);}
    if (joindata.recorddata!=NULL) {flex_free((flex_ptr)&joindata.recorddata);}
    //Restore the event type list
    fclose(fileptr);
    _swix(MessageTrans_OpenFile,_IN(0)|_IN(1)|_IN(2),&messages,filepath,0);
    loadeventlist(&messages[0],0);
    loadlists(1);
    return reporterror("e8",filepath2);
  }
  //Copy all of the data from the old file to the new file
  for (loop=0;loop<=numofwordlists;loop++) {savedlinetype[loop]=0;}
  while(feof(fileptr)==0) {
    len=0;
    do {
      letter=fgetc(fileptr);
      if (letter!=EOF && letter>=32) {messline[len++]=letter;}
    } while (letter!=EOF && letter>=32);
    messline[len]=0;

    //Find the type of the line
    if (feof(fileptr)==0 || len>0) {
      messlinetype=-1;
      if (isalpha(messline[0]) && isalpha(messline[1])) {
        for (loop=2;messline[loop]>=32 && messline[loop]!=':' && isdigit(messline[loop]);loop++) {}
        if (messline[loop]==':' || messline[loop]<32) {
          //Looks like the string was a list item, so find list number
          if (tolower(messline[0])=='e' && tolower(messline[1])=='v') {messlinetype=0;}
          for (loop=0;loop<numofwordlists;loop++) {
            if (tolower(messline[0])==wordlisttoken[loop][0] && tolower(messline[1])==wordlisttoken[loop][1]) {messlinetype=loop+1;}
          }
        }
      }
      if (messlinetype>=0) {
        //Need to save all of the words in this list here
        if (savedlinetype[messlinetype]==0) {
          savedlinetype[messlinetype]=1;
          savelisttomessages(fileptr2,messlinetype);
        }
      }
      else
      {
        fprintf(fileptr2,"%s\n",messline);
      }
    }
  }

  //Write out any remaining lists that were not saved in correct place
  for (loop=0;loop<=numofwordlists;loop++) {
    if (savedlinetype[loop]==0) {
      savelisttomessages(fileptr2,loop);
    }
  }

  fclose(fileptr);
  fclose(fileptr2);
  //Move all of the temporary saved files into permanent spaces
  _swix(OS_FSControl,_IN(0)|_IN(1)|_IN(3),27,filepath,1);
  _swix(OS_FSControl,_IN(0)|_IN(1)|_IN(2),25,filepath2,filepath);
  _swix(MessageTrans_OpenFile,_IN(0)|_IN(1)|_IN(2),&messages,filepath,0);

  //Now run through renaming the new month files
  for (filenum=0;filenum<=12;filenum++) {
    sprintf(filepath,"%s.TodayData.%s",todaypaths[0],monthfiles[filenum]);
    sprintf(filepath2,"%s.TodayData._%s",todaypaths[0],monthfiles[filenum]);
    _swix(OS_FSControl,_IN(0)|_IN(1)|_IN(3),27,filepath,1);
    _swix(OS_FSControl,_IN(0)|_IN(1)|_IN(2),25,filepath2,filepath);
  }

  //Report
  _swi(Hourglass_Off,0);
  error.errnum=0;
  sprintf(text1,"%d",loadedevents);
  sprintf(text2,"%d",loadedevents-savedevents);
  sprintf(text3,"%d",savedevents);
  radiobutton_get_state(0,joinwindow,8,&state,&selected);
  if (state==1) {
    _swi(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5)|_IN(6),&messages,"t5",error.errmess,252,text1,text2,text3);
  }
  else
  {
    sprintf(text4,"%d",possibleduplicates);
    _swi(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5)|_IN(6)|_IN(7),&messages,"t6",error.errmess,252,text1,text2,text3,text4);
  }
  _swi(Wimp_ReportError,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_IN(5),&error,1+(1<<8)+(1<<9),"Today",0,1,0);
  return 1;
}

//Called when opening saveas window
int showingsaveas_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int size;
  //Guess size of file
  if (id_block->parent_component<2) {
    size=100*scanwin.records;
  }
  else
  {
    size=100*srchwin.records;
  }
  saveas_set_file_size(0,id_block->self_id,size);
  return 1;
}

//Called when save done
int savefile_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  FILE *fileptr;
  WIN_DATA *window;
  int loop,loop2,offset,offset2,displayfield;
  int wordlen,length,maxmonth,maxevent,maxheader,maxtext,termchar;
  char string[32],token[4];

  //Open the output file
  fileptr=fopen(event->data.bytes,"w");
  if (fileptr==NULL) {
    saveas_file_save_completed(0,id_block->self_id,event->data.bytes);
    return reporterror("e3",event->data.bytes);
  }

  //Check window to save
  if (id_block->parent_component<2) {
    window=&scanwin;
  }
  else
  {
    window=&srchwin;
  }

  //Check type of save to do
  if ((id_block->parent_component&1)==0) {
    //Do a full text save
    //Find max lengths of strings
    maxmonth=0;
    for (loop=0;loop<=12;loop++) {
      if (strlen(monthnames[loop])>maxmonth) {maxmonth=strlen(monthnames[loop]);}
    }
    maxmonth++;
    maxevent=0;
    for (loop=0;loop<lenofeventlists[0];loop++) {
      if (strlen(eventlists[0][loop].str+2)>maxevent) {maxevent=strlen(eventlists[0][loop].str+2);}
    }
    maxevent+=2;
    if (window->listdates!=0) {
      maxheader=6+maxmonth+4+maxevent;
    }
    else
    {
      maxheader=6+maxevent;
    }
    maxtext=80-maxheader;

    for (loop=0;loop<window->records;loop++) {
      displayfield=(loop==0);
      if (displayfield==1 || window->recorddata[loop].year!=window->recorddata[loop-1].year) {
        displayfield=1;
        if (window->recorddata[loop].year==unknown_year) {
          fprintf(fileptr," ???? ");
        }
        else
        {
          if (window->recorddata[loop].year>=0) {
            fprintf(fileptr," %4d ",window->recorddata[loop].year);
          }
          else
          {
            fprintf(fileptr,"%3dBC ",-window->recorddata[loop].year);
          }
        }
      }
      else
      {
        fprintf(fileptr,"      ");
      }

      if (window->listdates!=0) {
        //Display month
        length=0;
        if (displayfield==1 || window->recorddata[loop].month!=window->recorddata[loop-1].month) {
          displayfield=1;
          if (window->recorddata[loop].month<=0) {
            length=fprintf(fileptr,"%s",monthnames[0]);
          }
          else
          {
            length=fprintf(fileptr,"%s",monthnames[window->recorddata[loop].month]);
          }
        }
        for (;length<maxmonth;length++) {fputc(32,fileptr);}

        //Display day
        if (displayfield==1 || window->recorddata[loop].day!=window->recorddata[loop-1].day) {
          displayfield=1;
          if (window->recorddata[loop].day<=0) {
            fprintf(fileptr,"??  ");
          }
          else
          {
            fprintf(fileptr,"%2d  ",window->recorddata[loop].day);
          }
        }
        else
        {
          fprintf(fileptr,"    ");
        }
      }

      //Display type
      length=fprintf(fileptr,"%s",eventlists[0][window->recorddata[loop].type].str+2);
      for (;length<maxevent;length++) {fputc(32,fileptr);}

      //Display the event
      length=0;
      offset=window->recorddata[loop].textoff;
      do {
        //Find length of the next word
        offset2=offset;
        for (;window->text[offset]>32;offset++) {}
        termchar=window->text[offset];
        wordlen=offset-offset2;
        if (length>0) {wordlen++;}
        if (length+wordlen<=maxtext) {
          //Word will fit so save and move on
          if (length>0) {fputc(32,fileptr);}
          for (;offset2<offset;offset2++) {
            fputc(window->text[offset2],fileptr);
          }
          length+=wordlen;
          offset++;
        }
        else
        {
          //Need to break line
          if (length>0) {
            //Already have some text so terminate current and move on
            fputc(10,fileptr);
            for (loop2=0;loop2<maxheader;loop2++) {fputc(32,fileptr);}
            for (;offset2<offset;offset2++) {
              fputc(window->text[offset2],fileptr);
            }
            length=wordlen-1;
            offset++;
          }
          else
          {
            //Break word midway
            for (offset=offset2;offset<offset2+maxtext;offset++) {
              fputc(window->text[offset],fileptr);
            }
            fputc(10,fileptr);
            for (loop2=0;loop2<maxheader;loop2++) {fputc(32,fileptr);}
            length=0;
            termchar=32;
          }
        }
      } while (termchar>0);

      fputc(10,fileptr);
    }
  }
  else
  {
    //Do a CSV save
    for (loop=0;loop<=4;loop++) {
      sprintf(token,"t%d",loop);
      _swi(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3),&messages,token,string,32);
      fprintf(fileptr,"\"%s\"",string);
      if (loop<4) {fputc(',',fileptr);}
    }
    fputc(10,fileptr);
    for (loop=0;loop<window->records;loop++) {
      if (window->recorddata[loop].year==unknown_year) {
        fprintf(fileptr,"\"????\",");
      }
      else
      {
        if (window->recorddata[loop].year>=0) {
          fprintf(fileptr,"\"%d\",",window->recorddata[loop].year);
        }
        else
        {
          fprintf(fileptr,"\"%dBC\",",-window->recorddata[loop].year);
        }
      }
      if (window->listdates!=0) {
        if (window->recorddata[loop].month<=0) {
          fprintf(fileptr,"\"%s\",",monthnames[0]);
        }
        else
        {
          fprintf(fileptr,"\"%s\",",monthnames[window->recorddata[loop].month]);
        }
        if (window->recorddata[loop].day<=0) {
          fprintf(fileptr,"\"??\",");
        }
        else
        {
          fprintf(fileptr,"\"%d\",",window->recorddata[loop].day);
        }
      }
      fprintf(fileptr,"\"%s\",\"",eventlists[0][window->recorddata[loop].type].str+2);
      offset=window->recorddata[loop].textoff;
      for (;window->text[offset]>0;offset++) {
        if (window->text[offset]!=13) {
          fputc(window->text[offset],fileptr);
        }
        else
        {
          fputc(32,fileptr);
        }
      }
      fprintf(fileptr,"\"\n");
    }
  }

  //Close the file
  fclose(fileptr);

  //Say save is complete
  saveas_file_save_completed(1,id_block->self_id,event->data.bytes);
  return 1;
}

//Finds id_block for icon bar icon
int createibar_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  if (strcmp(event->data.bytes,"Iconbar")==0) {
    //Creating iconbar
    iconbar_id=id_block->self_id;
    iconbar_component=id_block->self_component;
  }
  return 1;
}

//Handles button clicks in search window
int button_event(int event_code, WimpPollBlock *event, IdBlock *id_block,void *handle)
{
  int block[25],row,row2,loop,offset=0,offset2,offset3,match=0;
  int textoff,left,right,termchar,nextchar,writeable,month;
  REC_DATA *record=NULL;
  WIN_DATA *butwin;
  char *string,filename[256];
  ObjectId window;

  if (id_block->self_id==scanwin.toolbar || id_block->self_id==srchwin.toolbar) {
    //Could be a button to open a window
    switch (id_block->self_component) {
      case 0x20:
        toolbox_show_object(0,scanwin.window,0,0,iconbar_id,iconbar_component);
        gadget_set_focus(0,scanwin.toolbar,3);
        break;
      case 0x21:
        toolbox_show_object(0,srchwin.window,0,0,iconbar_id,iconbar_component);
        gadget_set_focus(0,srchwin.toolbar,1);
        break;
      case 0x22:
        openadd_event(event_code,NULL,id_block,handle);
        break;
      case 0x23:
        toolbox_create_object(0,"JoinWin",&window);
        toolbox_show_object(0,window,0,0,iconbar_id,iconbar_component);
        break;
      case 0x24:
        //Open an edit window for the own and month files
        sprintf(filename,"Filer_Run %s.TodayData.%s",todaypaths[0],monthfiles[0]);
        _swi(Wimp_StartTask,_IN(0),filename);
        stringset_get_selected(1,scanwin.toolbar,1,&month);
        month++;
        sprintf(filename,"Filer_Run %s.TodayData.%s",todaypaths[0],monthfiles[month]);
        _swi(Wimp_StartTask,_IN(0),filename);
    }
  }
  else
  {
    if ((event->mouse_click.buttons==1 || event->mouse_click.buttons==4) && (id_block->self_id==srchwin.window || id_block->self_id==scanwin.window) && event->mouse_click.icon_handle==-1) {
      //Find selected row
      if (id_block->self_id==srchwin.window) {
        butwin=&srchwin;
      }
      else
      {
        butwin=&scanwin;
      }
      block[0]=event->mouse_click.window_handle;
      _swi(Wimp_GetWindowState,_IN(1),block);
      row=(block[4]-block[6]-butwin->toolbarheight-8-event->mouse_click.mouse_y)/40;
      //Find selected event
      for (loop=0;record==NULL && loop<butwin->records;loop++) {
        if (row>=butwin->recorddata[loop].startline && row<=butwin->recorddata[loop].endline) {record=&butwin->recorddata[loop];}
      }

      if (record!=NULL) {
        if (event->mouse_click.buttons==1) {
          //Want to edit the event so open the edit window
          //First copy record data to the edit window so a record is kept
          editwin.records=editwin.maxrecords=0;
          editwin.curoff=editwin.maxoff=0;
          if (makeroomfordata(&editwin,0x7fffffff)==NULL) {
            if (editwin.recorddata!=NULL) {flex_free((flex_ptr)&editwin.recorddata);}
            if (editwin.text!=NULL) {flex_free((flex_ptr)&editwin.text);}
            return NULL;
          }
          editwin.recorddata[0]=*record;
          editwin.records++;
          editwin.recorddata[0].textoff=0;
          for (offset=0;butwin->text[record->textoff+offset]>0;) {
            nextchar=butwin->text[record->textoff+offset];
            if (nextchar==13) {nextchar=32;}
            editwin.text[offset++]=nextchar;
          }
          editwin.text[offset++]=0;
          editwin.curoff=offset;

          //Now set up the gadgets
          if (record->year==unknown_year) {
            numberrange_get_components(1,editwin.window,19,&writeable,0,0,0);
            writablefield_set_value(0,editwin.window,writeable,"");
          }
          else
          {
            numberrange_set_value(0,editwin.window,19,record->year);
          }
          if (record->month<=0) {
            stringset_set_selected(0,editwin.window,1,monthnames[1]);
          }
          else
          {
            stringset_set_selected(0,editwin.window,1,monthnames[record->month]);
          }
          if (record->day<=0) {
            numberrange_get_components(1,editwin.window,3,&writeable,0,0,0);
            writablefield_set_value(0,editwin.window,writeable,"");
          }
          else
          {
            numberrange_set_value(0,editwin.window,3,record->day);
          }
          stringset_set_selected(0,editwin.window,0x28,eventlists[0][record->type].str+2);
          stringset_set_selected(1,editwin.window,29,(char *)(record->code));
          radiobutton_set_state(0,editwin.window,0x1a,1-record->fromown);
          radiobutton_set_state(0,editwin.window,0x1b,record->fromown);

          offset=0;
          for (loop=0x22;loop<=0x26;loop++) {
            //Find end of the next row
            offset2=offset;
            offset3=offset2; /* Added by SF -- appears uninitialised? */
            for (;editwin.text[offset2]>=32 && (offset2-offset)<80;offset2++) {
              if (editwin.text[offset2]==32) {offset3=offset2;}
            }
            if (editwin.text[offset2]<=32) {offset3=offset2;}
            termchar=editwin.text[offset3];
            editwin.text[offset3]=0;
            writablefield_set_value(0,editwin.window,loop,editwin.text+offset);
            editwin.text[offset3]=termchar;
            if (termchar>=32) {
              offset=offset3+1;
            }
            else
            {
              offset=offset3;
            }
          }

          toolbox_show_object(0,editwin.window,0,0,iconbar_id,iconbar_component);
        }
        else
        {
          if (id_block->self_id==srchwin.window) {
            //Scan date of record (if month/day defined)
            if (record!=NULL && record->month>0 && record->day>0) {
              stringset_set_selected(0,scanwin.toolbar,1,monthnames[record->month]);
              numberrange_set_value(0,scanwin.toolbar,3,record->day);
              beginscan();
              toolbox_show_object(0,scanwin.window,0,0,iconbar_id,iconbar_component);
            }
          }
          else
          {
            //Search for the word that is selected
            textoff=(event->mouse_click.mouse_x-(block[1]-block[5]+8+yearwidth+typewidth));
            string=scanwin.text+record->textoff;
            for (row2=record->startline;row2<row;row2++) {
              for (;string[offset]>=32;offset++) {}
              offset++;
            }

            offset3=offset;
            do {
              //Move to first terminator of letter
              for (offset2=offset3;string[offset2]>=32 && lookup[string[offset2]]<=32;offset2++) {}
              if (string[offset2]>=32) {
                //Found start of a word so now find the end of it
                for (offset3=offset2+1;lookup[string[offset3]]>32;offset3++) {}

                //Find the left and right edges of the word
                if (version>=350) {
                  if (offset2>offset) {
                    left=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,string+offset,offset2-offset);
                  }
                  else
                  {
                    left=0;
                  }
                  right=_swi(Wimp_TextOp,_IN(0)|_IN(1)|_IN(2)|_RETURN(0),1,string+offset,offset3-offset);
                }
                else
                {
                  left=(offset2-offset)*16;
                  right=(offset3-offset)*16;
                }

                if (textoff>=left && textoff<=right) {match=1;}
              }
            } while (match==0 && string[offset2]>=32);

            //If word is matched, open the search window
            if (match==1) {
              termchar=string[offset3];
              string[offset3]=0;
              writablefield_set_value(0,srchwin.toolbar,1,string+offset2);
              string[offset3]=termchar;
              search_event(0,0,id_block,handle);
              toolbox_show_object(0,srchwin.window,0,0,iconbar_id,iconbar_component);
              gadget_set_focus(0,srchwin.toolbar,1);
            }
          }
        }
      }
    }
  }
  return 1;
}

//Create record windows and initialise them
int setupwindows(void)
{
  int state;
  char opttext[40],path[256];
  FILE *optfile;
  ObjectId mainmenu;

  createrecwindow("ScanWin","ScanTool",&scanwin);
  scanwin.listdates=0;
  createrecwindow("SrchWin","SrchTool",&srchwin);
  srchwin.listdates=1;
  toolbox_create_object(0,"JoinWin",&joinwindow);
  toolbox_create_object(0,"AddWin",&addwin.window);
  toolbox_create_object(0,"EditWin",&editwin.window);
  addwin.recorddata=NULL;
  addwin.text=NULL;
  editwin.recorddata=NULL;
  editwin.text=NULL;

  toolbox_create_object(0,"TypeMenu",&typemenu);
  setuptypemenus(0);
  stringset_set_selected(1,addwin.window,0x28,0);
  menu_remove_entry(0,typemenu,102);

  settocurrentdate(scanwin.toolbar,3,1,0);
  settocurrentdate(addwin.window,3,1,19);
  beginscan();
  justifywindow(&srchwin,0);

  //Load options
  toolbox_create_object(0,"MainMenu",&mainmenu);
  sprintf(path,"%s.Options",todaypaths[0]);
  optfile=fopen(path,"r");
  while(feof(optfile)==0) {
    if (fscanf(optfile,"%s",opttext)!=EOF) {
      if (caseinsencmp(opttext,"HIGHLIGHT")==1) {
        fscanf(optfile,"%d",&highlightstate);
        menu_set_tick(0,mainmenu,4,highlightstate);
      }
      if (caseinsencmp(opttext,"ADDTOMONTH")==1) {
        fscanf(optfile,"%d",&state);
        radiobutton_set_state(0,addwin.window,0x1a,state);
      }
    }
  }
  fclose(optfile);
  return 1;
}

//Handles selection of all in type menu
int selectall_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int loop;
  for (loop=1;loop<lenofeventlists[0];loop++) {
    menu_set_tick(0,id_block->self_id,loop,1);
  }
  return 1;
}

//Handles selection of none in type menu
int selectnone_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int loop;
  for (loop=1;loop<lenofeventlists[0];loop++) {
    menu_set_tick(0,id_block->self_id,loop,0);
  }
  return 1;
}

//Saves the options to a file
int saveoptions_event(int event_code, ToolboxEvent *event, IdBlock *id_block,void *handle)
{
  int state,on;
  char path[256];
  FILE *optfile;
  sprintf(path,"%s.Options",todaypaths[0]);
  optfile=fopen(path,"w");
  fprintf(optfile,"Highlight %d\n",highlightstate);
  radiobutton_get_state(0,addwin.window,0x1a,&state,&on);
  fprintf(optfile,"AddToMonth %d\n",state);
  fclose(optfile);
  return 1;
}

//Routine to compare words for word lists
int comparewordlist(const void *e1,const void *e2)
{
  char *word1,*word2;
  word1=((STRING *)e1)->str;
  word2=((STRING *)e2)->str;
  return comparewords(word1,word2,1,31,31);
}

//Routine to load a list of words from the messages file
int loadlist(int num,int numoffiles)
{
  int loop,loop2,offset,messagenum;
  char string[8],string2[64];
  _kernel_oserror *error;
  flex_alloc((flex_ptr)&wordlist[num],sizeof(STRING));
  wordlistlen[num]=0;
  for (loop2=0;loop2<numoffiles;loop2++) {
    messagenum=1;
    do {
      sprintf(string,"%s%d",wordlisttoken[num],messagenum++);
      error=_swix(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3),&messages[loop2],string,string2,64);
      if (error==NULL) {
        //Keep loading strings until no more '_'s are found
        offset=0;
        do {
          flex_extend((flex_ptr)&wordlist[num],(wordlistlen[num]+1)*sizeof(STRING));
          for (loop=0;string2[offset+loop]!='_' && string2[offset+loop]>=32;loop++) {
            wordlist[num][wordlistlen[num]].str[loop]=string2[offset+loop];
          }
          wordlist[num][wordlistlen[num]].str[loop]=0;
          wordlistlen[num]++;
          offset+=loop+1;
        } while (string2[offset-1]=='_');
      }
    } while (error==NULL);
  }

  //Sort the words into order
  qsort((void *)wordlist[num],(size_t)wordlistlen[num],sizeof(STRING),comparewordlist);

  //Remove any duplicate words (keeping shortest)
  for (loop=wordlistlen[num]-1;loop>0;loop--) {
    if (comparewordlist(&wordlist[num][loop],&wordlist[num][loop-1])==0) {
      //Words are equal so find shortest and delete it
      if (strlen(wordlist[num][loop].str)>strlen(wordlist[num][loop-1].str)) {
        loop2=loop+1;
      }
      else
      {
        loop2=loop;
      }
      for (;loop2<wordlistlen[num];loop2++) {
        wordlist[num][loop2-1]=wordlist[num][loop2];
      }
      wordlistlen[num]--;
    }
  }
  return 1;
}

//Loads lists from message files, numoffiles is the number of files to look in
int loadlists(int numoffiles)
{
  int loop,loop2,offset,messagenum,equalpos,nextpos;
  char string[8],string2[64];
  _kernel_oserror *error;

  //Load all of the words
  for (loop=0;loop<numofwordlists;loop++) {loadlist(loop,numoffiles);}

  //Now create the table saying which words are equivalent
  flex_alloc((flex_ptr)&thesauruslist,wordlistlen[3]*sizeof(int));
  for (loop=0;loop<wordlistlen[3];loop++) {thesauruslist[loop]=-1;}

  for (loop2=0;loop2<numoffiles;loop2++) {
    messagenum=1;
    do {
      equalpos=wordlistlen[3];
      sprintf(string,"th%d",messagenum++);
      error=_swix(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3),&messages[loop2],string,string2,64);
      if (error==NULL) {
        offset=0;
        do {
          //Find positions of word in list
          nextpos=findwordinlist(string2+offset,3,1,31,31);

          //Check if the word is already set equal to something
          if (thesauruslist[nextpos]!=-1) {
            if (thesauruslist[nextpos]<equalpos) {
              //Need to reset equalpos to this new value
              for (loop=equalpos;loop<wordlistlen[3];loop++) {
                if (thesauruslist[loop]==equalpos) {thesauruslist[loop]=thesauruslist[nextpos];}
              }
              equalpos=thesauruslist[nextpos];
            }
            else
            {
              //Need to reset items to equalpos
              for (loop=thesauruslist[nextpos]+1;loop<wordlistlen[3];loop++) {
                if (thesauruslist[loop]==thesauruslist[nextpos]) {thesauruslist[loop]=equalpos;}
              }
              thesauruslist[nextpos]=equalpos;
            }
          }
          else
          {
            //If the equal word is unset or this word is lower than equal word
            //set the equalpos to this word
            if (nextpos<equalpos) {
              //New word is lower in the list so need to set all words that
              //are equal to equalpos equal to nextpos
              for (loop=equalpos;loop<wordlistlen[3];loop++) {
                if (thesauruslist[loop]==equalpos) {thesauruslist[loop]=nextpos;}
              }
              equalpos=nextpos;
            }

            //Set the word that this word is equal to (lowest in list)
            thesauruslist[nextpos]=equalpos;
          }

          //Move to the next word
          for (;string2[offset]!='_' && string2[offset]>=32;offset++) {}
          offset++;
        } while (string2[offset-1]=='_');
      }
    } while (error==NULL);
  }
  return 1;
}

int main(int argc,char *argv[])
{
    //Setup the list of wimp messages to receive
    static int wimp_messages[]={
      Wimp_MDataSave,
      Wimp_MDataOpen,
      Wimp_MDataLoad,
      Wimp_MDataSaveAck,
      Wimp_MFontChanged,
      Wimp_MModeChange,
      Wimp_MPreQuit,
      Wimp_MQuit};

    //Setup the list of toolbox events to receive
    static int toolbox_events[]={
      1,
      2,
      3,
      4,
      5,
      6,
      7,
      8,
      9,
      10,
      11,
      12,
      13,
      14,
      15,
      16,
      17,
      18,
      19,
      StringSet_ValueChanged,
      SaveAs_AboutToBeShown,
      SaveAs_SaveToFile,
      Toolbox_ObjectAutoCreated,
      0};

    int  event_code,loop,len;
//    int UTC[2];
//    char debug_filename[100];
    char string[8];

//    UTC[0]=3;
//    _swi(OS_Word,_IN(0)|_IN(1),14,UTC);
//    _swi(Territory_ConvertDateAndTime,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4),-1,UTC,debug_filename,100,"RAM::RamDisc0.$.%24%MI%SE%CS");
//    debug=fopen(debug_filename,"w");
//    if (debug==NULL) {
//      printf("Could not open debug file");
//      exit(0);
//    }


    //Copy data path into block so it cannot be corrupted by other copies
    len=_swi(OS_ReadVarVal,_IN(0)|_IN(1)|_IN(2)|_IN(3)|_IN(4)|_RETURN(2),"Today$Dir",todaypaths[0],256,0,0);

    //initialise the flex library.
    flex_init("Today",0, 0);

    //register ourselves with the Toolbox.
    toolbox_initialise (0, WimpVersion, wimp_messages, toolbox_events, "<Today$Dir>",&messages[0], &id_block, &version, 0, (void **)&sprite_area);

    //initialise the event library.
    event_initialise (&id_block);

    event_set_mask (1+256);

    //Register all of the events
    event_register_wimp_handler(-1,1,redraw_event,0);
    event_register_wimp_handler(-1,6,button_event,0);

    event_register_toolbox_handler(-1,1,selectibar_event,0);
    event_register_toolbox_handler(-1,2,adjustibar_event,0);
    event_register_toolbox_handler(-1,3,menuquit_event,0);
    event_register_toolbox_handler(-1,4,scan_event,0);
    event_register_toolbox_handler(-1,5,scantoday_event,0);
    event_register_toolbox_handler(-1,6,search_event,0);
    event_register_toolbox_handler(-1,7,highlight_event,0);
    event_register_toolbox_handler(-1,8,openingsave_event,0);
    event_register_toolbox_handler(-1,9,joindata_event,0);
    event_register_toolbox_handler(-1,10,openadd_event,0);
    event_register_toolbox_handler(-1,11,cleartext_event,0);
    event_register_toolbox_handler(-1,12,addnew_event,0);
    event_register_toolbox_handler(-1,13,altertypes_event,0);
    event_register_toolbox_handler(-1,14,joindata_event,0);
    event_register_toolbox_handler(-1,15,selectall_event,0);
    event_register_toolbox_handler(-1,16,selectnone_event,0);
    event_register_toolbox_handler(-1,17,saveoptions_event,0);
    event_register_toolbox_handler(-1,18,delete_event,0);
    event_register_toolbox_handler(-1,19,edit_event,0);
    event_register_toolbox_handler(-1,SaveAs_AboutToBeShown,showingsaveas_event,0);
    event_register_toolbox_handler(-1,SaveAs_SaveToFile,savefile_event,0);
    event_register_toolbox_handler(-1,StringSet_ValueChanged,stringchanged_event,0);
    event_register_toolbox_handler(-1,Toolbox_ObjectAutoCreated,createibar_event,0);
    event_register_message_handler(Wimp_MDataLoad,draggingjoinfile_message,0);
    event_register_message_handler(Wimp_MFontChanged,fontchange_message,0);
    event_register_message_handler(Wimp_MModeChange,modechange_message,0);
    event_register_message_handler(Wimp_MQuit,quit_message,0);

    //Load all of the month names from messages file
    for (loop=0;loop<=12;loop++) {
      sprintf(string,"m%d",loop);
      _swi(MessageTrans_Lookup,_IN(0)|_IN(1)|_IN(2)|_IN(3),&messages,string,monthnames[loop],4);
    }

    //Load all of the event names from messages file
    loadeventlist(&messages[0],0);

    loadlists(1);

    //Set size of screen data
    findfontwidths();

    setupwindows();

    //poll loop
    while (TRUE)
    {
        event_poll (&event_code, &poll_block, 0);
    }
}
