/*
    DAYS.C

Written by DavidW
Started on 4/12/91
*/

#include <p_std.h>          /* basic Psion header file */
#include <p_file.h>         /* defines E_FILE_ error values, etc */
#include <p_dbf.h>          /* defines DbfXXX functions */
#include <p_date.h>         /* defines date and time functions */
#include <p_config.h>       /* defines E_CONFIG struct */
#include <epoc.h>           /* general Epoc definitions */
#include <wlib.h>           /* defines al Window Server library functions */
#include <hwif.h>           /* defines all Hwif library functions */

GLREF_D UWORD DatLocked;            /* non-zero when application "busy" */
GLREF_D TEXT *DatUsedPathNamePtr;   /* full path name of current file */

LOCAL_D TEXT *cmds[]=
    {           /* the commands in the menu bar */
    "nNew",
    "oOpen",
    "mMerge",
    "aSave as",
    "kCompress",
    "xExit",
    "iInsert text",
    "cCopy text",
    "vEvaluate",
    "hShow symbols",
    "sSave",
    "rRevert",
    "eEnter anniversary",
    "dDelete anniversary",
    "uChange date",
    "fFind",
    "gFind next",
    "bFind back",
    "tFind date",
    "jJump to",
    "lList view",
    "wWander through",
    "pPrint",
    "yPrint setup",
    NULL
    };

#define FILE_ITEMS          6       /* how many items in each menu */
#define EDIT_ITEMS          6
#define CHANGE_ITEMS        3
#define SEARCH_ITEMS        5
#define SPECIAL_ITEMS       4

#define ITEMS_BEFORE_FILE       0
#define ITEMS_BEFORE_EDIT       FILE_ITEMS
#define ITEMS_BEFORE_CHANGE     (ITEMS_BEFORE_EDIT+EDIT_ITEMS)
#define ITEMS_BEFORE_SEARCH     (ITEMS_BEFORE_CHANGE+CHANGE_ITEMS)
#define ITEMS_BEFORE_SPECIAL    (ITEMS_BEFORE_SEARCH+SEARCH_ITEMS)

#define LIST_ITEM           ITEMS_BEFORE_SPECIAL
#define SYMBOLS_ITEM        ITEMS_BEFORE_EDIT+3

LOCAL_D H_MENU_DATA mdata[]=
    {                   /* the menus in the menu bar */
    "File",FILE_ITEMS,
    "Edit",EDIT_ITEMS,
    "Change",CHANGE_ITEMS,
    "Search",SEARCH_ITEMS,
    "Special",SPECIAL_ITEMS,
    NULL
    };

GLDEF_D TEXT ** _cmds=(&cmds[0]);           /* Hwif code expects this */
GLDEF_D H_MENU_DATA * _mdata=(&mdata[0]);   /* Hwif code expects this */

LOCAL_D UINT MainWid;       /* ID of main window */

LOCAL_D struct
    {                       /* format of data at start of Days DBF file */
    UWORD dataStart;      
    UWORD needVersion;    
    UWORD firHeader;
    UBYTE fir[3];
    } ModelHeader=
        {
        DbfHeaderLength,
        0x100f,                     /* DBF code version number required */
        (DbfRecordTypeFir<<12)|3,       /* 3 fields defined */
            {
            DbfFieldTypeWord,       /* word field ("DayInYear") */
            DbfFieldTypeString,     /* string field ("name") */
            DbfFieldTypeString      /* string field ("notes") */
            }
        };

LOCAL_D TEXT filename[P_FNAMESIZE];     /* for current filename */
LOCAL_D VOID *dH;                       /* handle for DbfXXX services */
LOCAL_D UBYTE dBuf[512];            /* buffer used by DbfXXX services */
LOCAL_D UWORD dOff;                 /* current offset into dBuf[] */

typedef struct
    {               /* structure of INDex ENTry */
    UWORD diy;          /* no of Day In Year */
    UWORD no;           /* no of record in file (secondary key) */
    } INDENT;

LOCAL_D INDENT index[500];      /* cope with up to 500 entries */
LOCAL_D WORD count;             /* how many entries */
LOCAL_D WORD where;             /* current entry */

LOCAL_D WORD top;       /* entry at top of screen (list view) */
LOCAL_D UWORD ViewList=FALSE;       /* TRUE if in list view */

LOCAL_D VOID *edit[3];              /* the three editors */
LOCAL_D UWORD show_statwin=TRUE;    /* whether Status Window is showing */
LOCAL_D UWORD ScreenWidth;          /* width of main display area */
LOCAL_D INT emph=0;             /* which editor is emphasised */

LOCAL_D UWORD lastbflags=W_BORD_CORNER_4;   /* state of border flags */

LOCAL_D UWORD ShowSymbols=FALSE;        /* whether editors display symbols */

LOCAL_D WORD currdiy;       /* Day In Year of current entry */
LOCAL_D WORD origdiy;       /* currdiy before operation started */
LOCAL_D WORD dummdiy;   /* DIY of dummy entry (placeholder for real one) */

LOCAL_D WORD DummyEntry=FALSE;      /* whether dummy entry in place */
LOCAL_D WORD lastchanged=FALSE;     /* last state of "changed" flag */

LOCAL_D UBYTE daysim[]={31,29,31,30,31,30,31,31,30,31,30,31};

LOCAL_D TEXT find[20]={0};      /* the current find string */
LOCAL_D UWORD findtype=1;       /* which type of find is being done */

LOCAL_D UWORD PrintNamesOnly=1;     /* whether only to print names */
LOCAL_D UWORD PrintRec;             /* which record is being printed */
LOCAL_D UWORD PrintState;       /* which part of the record is being printed */
LOCAL_D UBYTE *PrintBuf;            /* the text being printed */
LOCAL_D WORD PrintLen;              /* length of text being printed */
LOCAL_D WORD PrintIndent;           /* indent before text being printed */
LOCAL_D TEXT PrintDate[40];         /* text of date of entry being printed */

LOCAL_D UWORD tly[]={0,17,35};  /* top left y-positions of three editors */
LOCAL_D UWORD bry[]={15,32,80}; /* bottom right y-positions of editors */


LOCAL_C VOID DrawBorder(INT i)
    {               /* draw border round i-th editor and emphasise */
    P_RECT rect;            /* appropriate rectangle */

    rect.tl.x=0;
    rect.br.x=ScreenWidth;
    if (!i)
        rect.br.x-=54;
    else if (i==1)
        rect.br.x-=10;
    rect.tl.y=tly[i];
    rect.br.y=bry[i];
    gBorderRect(&rect,      /* should border be emphasised? */
            W_BORD_CORNER_4|W_BORD_SHADOW_S|(i==emph? W_BORD_SHADOW_ON: 0));
    hEBEmphasise(edit[i],i==emph);  /* emphasise or de-emphasise editor */
    }

LOCAL_C VOID DrawWhereAndCount(VOID)
    {               /* draw the text "n/m" */
    TEXT buf[10];
    P_RECT box;     /* where to draw the text */

    p_atos(&buf[0],"%d/%d",where+1,count);  /* construct text to display */
    box.tl.x=ScreenWidth-54;
    box.tl.y=2;
    box.br.x=ScreenWidth;
    box.br.y=12;
    gPrintBoxText(&box,8,G_TEXT_ALIGN_CENTRE,0,buf,p_slen(buf));
    }

LOCAL_C INT CheckIfChanged(VOID)
    {               /* return TRUE if current entry has been changed */
    if (ViewList)
        return(0);
    return(origdiy!=currdiy
                    || hEBSenseChanged(edit[1]) || hEBSenseChanged(edit[2]));
    }

LOCAL_C INT FindMonNo(INT *pdiy)
    {               /* find month number corresponding to day-in-year */
    INT monno;

    monno=0;
    while (*pdiy>=daysim[monno])
        (*pdiy)-=daysim[monno++];
    return(monno);
    }

LOCAL_C VOID DiyToBuf(INT diy,TEXT *pb)
    {               /* get full textual representation of day in year */
    E_CONFIG cfg;       /* current system defaults */
    TEXT mon[32];       /* name of month */
    TEXT suff[31][3];   /* numerical suffices */

    p_nmmon(&mon[0],FindMonNo(&diy));
    p_getsuffixes(&suff[0][0]);
    p_getctd(&cfg);
    if (cfg.dateType==E_DATE_EUROPE)
        p_atos(pb,"%u%s %s",diy+1,&suff[diy][0],&mon[0]);
    else
        p_atos(pb,"%s %u%s",&mon[0],diy+1,&suff[diy][0]);
    }

LOCAL_C VOID DiyToShortBuf(INT diy,TEXT *pb)
    {               /* get short textual representation of day in year */
    INT mon;
    E_CONFIG cfg;       /* to find date separator */
    INT first;          /* either day number or month number */
    INT second;         /* the other */

    mon=FindMonNo(&diy)+1;
    diy++;
    p_getctd(&cfg);
    if (cfg.dateType==E_DATE_EUROPE)
        {
        first=diy;
        second=mon;
        }
    else
        {
        first=mon;
        second=diy;
        }
    p_atos(pb,"%u%c%u",first,cfg.dateSeparator,second);
    }

LOCAL_C INT SetDateText(INT diy)
    {               /* set date text into the date "editor" */
    TEXT date[40];

    DiyToBuf(diy,&date[0]);
    return(hEBSetText(edit[0],&date[0],p_slen(&date[0])));
    }

LOCAL_C VOID ClearChangedFlags(VOID)
    {               /* acknowledge that changes have been absorbed */
    origdiy=currdiy;
    hEBClearChanged(edit[1]);
    hEBClearChanged(edit[2]);
    }

LOCAL_C VOID ZeroBufferStart(VOID)
    {               /* put dummy entry at start of dBuf[] */
    *(UWORD *)(&dBuf[2])=dummdiy;
    *(UWORD *)(&dBuf[4])=0;
    dOff=0;
    DbfTrash(dH);
    }

LOCAL_C VOID Check(INT val)
    {       /* terminate application on file error */
    TEXT buf[64];

    if (val<0 && val!=E_FILE_EOF)
        {
        uForceToFront();        /* come into foreground */
        DatLocked=TRUE;
        p_errs(&buf[0],val);    /* get error string */
        wsAlertW(WS_ALERT_CLIENT,&buf[0],"Terminating application",0);
        p_exit(0);              /* exit cleanly */
        }
    }

LOCAL_C VOID SeekTo(UINT current)
    {           /* read the specified record into dBuf[] */
    Check(DbfAbsRead(dH,current,&dOff));
    }

LOCAL_C INT LoadBuffer(INT j)
    {           /* read the specified record (maybe dummy) into dBuf[] */
    INT no;

    if (j>=count)
        return(-1);     /* no such record */
    no=index[j].no;
    if (DummyEntry && no==count-1)
        ZeroBufferStart();      /* record in process of being added */
    else
        SeekTo(no);             /* real record */
    return(0);
    }

LOCAL_C VOID LoadEditors(VOID)
    {               /* load editors in preparation to edit current entry */
    UBYTE *psrc;    /* points into dBuf[] */
    INT len;        /* length of text field */

    LoadBuffer(where);          /* read current record into dBuf[] */
    psrc=(&dBuf[dOff+2]);       /* position psrc to start of record */
    ClearChangedFlags();        /* no changes made yet */
    DrawWhereAndCount();        /* display new "n/m" text */
    origdiy=currdiy=(*(UWORD *)psrc);   /* read the Day In Year */
    if (SetDateText(currdiy))   /* set text of date "editor" */
        return;
    psrc+=2;                /* increment to first text field */
    len=(*psrc);            /* length of first text field */
    if (hEBSetText(edit[1],(TEXT *)psrc+1,len))
        return;
    psrc+=len+1;            /* increment to second text field */
    hEBSetText(edit[2],(TEXT *)psrc+1,*psrc);
    }

LOCAL_C INT AddMonthChoiceList(UWORD *pmonno)
    {           /* add choice list of all month names to current dialog */
    H_DI_CHOICE ch;
    TEXT mon[32];       /* for name of current month */
    INT i;

    if (uBeginDCL(&ch))
        return(-1);
    for (i=0; i<12; i++)
        {                               /* for each month */
        p_nmmon(&mon[0],i);             /* look up month name */
        if (uGrowDCL(&ch,&mon[0]))      /* try to add next entry */
            return(-1);
        }
    return(uAddDCL("Month",pmonno,&ch));
    }

LOCAL_C INT FindDiy(INT monno,INT dayinm)
    {                   /* find DayInYear for given day in given month */
    while (monno)
        dayinm+=daysim[--monno];
    return(dayinm);
    }

LOCAL_C VOID IndexDelete(VOID)
    {               /* delete an item from the index */
    UWORD deleted;      /* the record number of item deleted */
    UWORD i;
    INDENT *pindent;
    UWORD tomove;

    deleted=index[where].no;
    count--;            /* decrement total count number */
    tomove=count-where; /* how many index entries to move to close gap */
    p_bcpy(&index[where],&index[where+1],tomove*sizeof(INDENT));
    pindent=(&index[0]);    /* to scan through all index entries */
    i=count;
    while (i--)
        {
        if (pindent->no>deleted)
            (pindent->no)--;    /* since this record number now one less */
        pindent++;
        }
    }

LOCAL_C UWORD FindKey(VOID)
    {           /* read the DayInYear of the current entry */
    return(*(UWORD *)(&dBuf[dOff+2]));
    }

LOCAL_C INT compare(INDENT *p1,INDENT *p2)
    {           /* find which of two entries is first in order */
    UWORD diff;

    diff=p1->diy-p2->diy;
    return(diff? diff: p1->no-p2->no);
    }

LOCAL_C INT cmp(INT r,VOID *pindent)
    {           /* routine expected by p_bsrch */
    return(compare(pindent,&index[r]));
    }

LOCAL_C INT LocateByDate(INDENT *pent)
    {   /* locate index position matching (or nearest after) given entry */
    INT dir;    /* direction to nearest hit (as given by p_bsrch) */
    INT mid;    /* nearest hit (as given by p_bsrch) */

    if (!count)
        mid=0;
    else
        {
        dir=p_bsrch(count,cmp,&mid,pent);
        if (dir>0)
            mid++;
        }
    return(mid);
    }

LOCAL_C VOID IndexAppend()
    {               /* add index entry for new record at end of file */
    INT mid;
    UWORD tomove;
    INDENT new;

    new.no=count;       /* new record added at end of file */
    new.diy=currdiy;
    mid=LocateByDate(&new);     /* find where to insert index entry */
    tomove=count-mid;           /* how many entries to move down */
    count++;                    /* increment total count of records */
    p_bcpy(&index[mid+1],&index[mid],tomove*sizeof(INDENT));
    index[mid]=new;         /* write in the new entry */
    where=mid;              /* this entry is now current */
    }

LOCAL_C VOID IndexUpdate(VOID)
    {                   /* update index entry for record updated */
    IndexDelete();          /* delete the old entry... */
    IndexAppend();          /* ...and construct a new one */
    }

LOCAL_C INT Try(INT ret,TEXT *msg)
    {               /* in case of file error, give user chance to retry */
    TEXT buf[64];

    if (ret>=0)
        return(0);      /* no error */
    uForceToFront();    /* come to foreground */
    DatLocked=TRUE;     /* mark application as "busy" */
    p_errs(&buf[0],ret);
    if (wsAlertW(WS_ALERT_CLIENT,msg,&buf[0],"Continue","Exit",0))
        p_exit(0);      /* user is willing to exit application */
    DatLocked=FALSE;
    return(-1);
    }

LOCAL_C VOID SeekToCurrent(VOID)
    {           /* read the current record into dBuf[] */
    SeekTo(index[where].no);
    }

LOCAL_C INT DoUpdate(INT len)
    {           /* user has finished updating record */
    INT state;

    if (DummyEntry)
        {       /* was editing dummy record - so append real one */
        if (Try(DbfAppend(dH,len),"Failed to save changes"))
            return(-1);
        DummyEntry=FALSE;
        }
    else
        {
        SeekToCurrent();            /* find the record to update */
        state=DbfStateDisabled;     /* instant update */
        if (Try(DbfUpdate(&state,dH,len),"Failed to save changes"))
            return(-1);
        }
    IndexUpdate();          /* update the corresponding index entry */
    return(0);
    }

LOCAL_C VOID EditEmphasise(INT flag)
    {       /* pass emphasis or de-emphasis message to current editor */
    if (!ViewList)
        hEBEmphasise(edit[emph],flag);
    }       /* else no editors */

LOCAL_C INT RunDialog(VOID)
    {           /* layer round dialog code to handle cursor flashing */
    INT ret;

    EditEmphasise(FALSE);   /* remove any flashing cursor */
    ret=uRunDialog();       /* run the dialog */
    EditEmphasise(TRUE);    /* reinstate any flashing cursor */
    return(ret);
    }

LOCAL_C INT Confirm(TEXT *pb)
    {                       /* present a "No/Yes" dialog */
    if (uOpenDialog(pb))    /* title text supplied by caller */
        return(-1);
    if (uAddButtonList("No",-'n',"Yes",'y',NULL))
        return(-1);
    return(RunDialog()!='y');   /* return TRUE iff user answered Yes */
    }

LOCAL_C INT SizeRecord(VOID)
    {       /* return the length of first text field in current record */
    return(*(UWORD *)&dBuf[dOff+4]);
    }

LOCAL_C VOID AddDummyEntry()
    {       /* add index position for record in process of being defined */
    DummyEntry=TRUE;
    dummdiy=currdiy;
    IndexAppend();
    }

LOCAL_C INT CalculateBflags(VOID)
    {       /* find whether scroll arrows should be displayed on border */
    INT bflags;

    if (top)        /* check if more records off top of screen */
        bflags=W_BORD_CORNER_4|W_BORD_TOP_ON;
    else
        bflags=W_BORD_CORNER_4|W_BORD_TOP_OFF;
    if (top+8<count)    /* check if more records off bottom of screen */
        bflags|=W_BORD_BOT_ON;
    else
        bflags|=W_BORD_BOT_OFF;
    return(bflags);
    }

LOCAL_C VOID LoadYs(P_RECT *pbox,INT y)
    {           /* load top and bottom y-values for given screen line */
    pbox->tl.y=4+y*9;
    pbox->br.y=pbox->tl.y+9;
    }

LOCAL_C VOID LineToScreen(INT y,INT x1,INT x2,INT align,TEXT *pb,INT tlen)
    {               /* utility layer around gPrintBoxText */
    P_RECT box;

    box.tl.x=x1;
    box.br.x=x2;
    LoadYs(&box,y);
    gPrintBoxText(&box,8,align,0,pb,tlen);
    }

LOCAL_C VOID ClrLine(INT y)
    {               /* clear a line on the screen */
    P_RECT box;

    box.tl.x=4;
    box.br.x=ScreenWidth-4;
    LoadYs(&box,y);
    gClrRect(&box,G_TRMODE_CLR);
    }

LOCAL_C VOID SetBold(INT flag)
    {               /* prepare to print in bold (or in non-bold) */
    G_GC gc;

    gc.font=WS_FONT_BASE+flag;
    gSetGC(0,G_GC_MASK_FONT,&gc);
    }

LOCAL_C VOID GetCursorBox(INT i,P_RECT *pbox)
    {           /* get rectangle to draw cursor for specified entry */
    pbox->tl.x=4;
    pbox->br.x=10;
    LoadYs(pbox,i);
    }

LOCAL_C VOID DrawCursor(INT i,INT on)
    {           /* draw (or clear) cursor alongside given entry */
    P_RECT box;

    GetCursorBox(i,&box);
    gPrintBoxText(&box,8,G_TEXT_ALIGN_LEFT,0,"\034",on);
    }

LOCAL_C VOID DrawLine(INT i)
    {                   /* display a given entry on a line on screen */
    INT entno;
    UBYTE *psrc;        /* pointer into dBuf[] */
    TEXT date[6];       /* for short textual representation of date */

    entno=i+top;        /* offset by no of entry at screen top */
    if (LoadBuffer(entno))
        {               /* no such entry */
        ClrLine(i);
        return;
        }
    psrc=(&dBuf[dOff+2]);       /* point to start of record */
    DiyToShortBuf(*(UWORD *)psrc,&date[0]);     /* get short date text */
    DrawCursor(i,entno==where);         /* draw or clear cursor */
    SetBold(TRUE);                      /* draw date in bold */
    LineToScreen(i,14,49,G_TEXT_ALIGN_RIGHT,&date[0],p_slen(&date[0]));
    psrc+=2;                    /* position to first text field */
    SetBold(FALSE);             /* print remainder in non-bold */
    LineToScreen(i,54,ScreenWidth-4,G_TEXT_ALIGN_LEFT,(TEXT *)psrc+1,*psrc);
    }

LOCAL_C VOID ListDisplay(VOID)
    {               /* redraw all visible entries (in list view) */
    INT i;

    for (i=0; i<8; i++)
        DrawLine(i);
    }

LOCAL_C VOID WholeListDisplay(VOID)
    {               /* re-display whole screen (in list view) */
    if (top+8>count)
        top=count-8;        /* wasted spaced at end of display */
    if (top<0)
        top=0;
    if (where<top)
        top=where;          /* ensure current record on screen */
    else if (where>top+7)
        top=where-7;
    lastbflags=CalculateBflags();
    gBorder(lastbflags);        /* draw border */
    ListDisplay();              /* draw the entries themselves */
    }

LOCAL_C INT LookFor(UBYTE *pb)
    {           /* look for find text in given text field */
    return(p_bsubi((TEXT *)pb+1,*pb,&find[1],find[0]));
    }

LOCAL_C VOID RotateTo(INT newemph)
    {           /* change which editor is emphasised */
    INT oldemph;

    if (newemph!=emph)
        {
        oldemph=emph;
        emph=newemph;
        DrawBorder(oldemph);        /* clear former emphasis */
        DrawBorder(newemph);        /* apply new emphasis */
        }
    }

LOCAL_C INT SaveChanges(VOID)
    {               /* save changes made to current record */
    INT len1;           /* length of first text field */
    INT len2;           /* length of second text field */
    UBYTE *pb;          /* pointer into dBuf[] */
    TEXT *psrc;         /* pointer into editor's text */

    pb=(&dBuf[2]);          /* start at beginning of dBuf[] */
    *(UWORD *)pb=currdiy;   /* fill in word DIY field */
    pb+=2;                  /* position to first text field */
    psrc=hEBSenseText(edit[1]);     /* sense text in editor */
    len1=p_slen(psrc);
    *pb++=len1;                     /* record the length of the text */
    pb=p_bcpy(pb,psrc,len1);        /* record the text itself */
    psrc=hEBSenseText(edit[2]);     /* sense text in next editor */
    len2=p_slen(psrc);
    *pb++=len2;                     /* record the length of the text */
    p_bcpy(pb,psrc,len2);           /* record the text itself */
    dOff=0;
    if (DoUpdate(len1+len2+4))      /* give length of record being added */
        return(-1);
    ClearChangedFlags();    /* acknowledge editors are no longer "changed" */
    DrawWhereAndCount();    /* position in index may be different now */
    return(0);
    }

LOCAL_C INT SaveIfChanged(VOID)
    {                       /* save changes made, if any */
    if (!CheckIfChanged())
        return(0);
    return(SaveChanges());
    }

LOCAL_C VOID CompressFile(VOID)
    {                       /* user has requested file be compressed */
    UINT state;

    if (!hIsDbfCompressible(dH))
        wInfoMsg("Cannot compress on Flash");
    else
        {
        state=DbfStateDisabled;
        Check(DbfCompress(&state,dH));
        wInfoMsg("File compressed");
        }
    }

LOCAL_C VOID ChangeName(TEXT *newname)
    {           /* name of file being edited has changed */
    p_scpy(&filename[0],newname);       /* copy to own buffer */
    hSetUpStatusNames(&filename[0]);    /* inform system of the change */
    wsUpdate(WS_UPDATE_NAME);           /* force status window update */
    }

LOCAL_C INT ordf(INT r1,INT r2,VOID *junk)
    {           /* ordering function required by p_qsort() */
    return(compare(&index[r1],&index[r2]));
    }

LOCAL_C VOID excf(INT r1,INT r2,VOID *junk)
    {           /* exchange function required by p_qsort() */
    p_bswap(&index[r1],&index[r2],sizeof(INDENT));
    }

LOCAL_C VOID BuildIndex(VOID)
    {           /* build index for current file */
    INDENT *pindent;
    UWORD no;

    no=0;
    count=DbfCount(dH);
    pindent=(&index[0]);        /* to pass through whole index */
    while (no<count)
        {
        SeekTo(no);             /* position to given record in file */
        pindent->diy=FindKey(); /* extract its DayInYear value */
        pindent->no=no++;       /* record secondary key */
        pindent++;
        }
    p_qsort(no,ordf,excf,NULL);     /* sort the index */
    }

LOCAL_C VOID DoScroll(INT dely)
    {           /* scroll part of the screen display vertically */
    P_POINT offset;     /* offset to scroll by */
    P_RECT src;         /* source region for the scroll */

    offset.x=0;
    offset.y=dely*9;    /* each line is 9 pixels high */
    src.tl.x=4;
    src.br.x=ScreenWidth-4;
    if (dely>0)
        {       /* scrolling down - so omit display bottom from source */
        src.tl.y=4;
        src.br.y=4+(8-dely)*9;
        }
    else
        {       /* scrolling up - so omit display top from source */
        src.tl.y=4-dely*9;
        src.br.y=4+8*9;
        }
    wScrollRect(MainWid,&src,&offset);
    }

LOCAL_C VOID MakeHighlight(INT newcent)
    {               /* move the cursor to the specified new value */
    INT toscroll;

    if (newcent==where)
        return;     /* cursor already there */
    DrawCursor(where-top,FALSE);        /* remove old cursor */
    toscroll=top-newcent;
    if (toscroll>0)
        {               /* new cursor position off top of screen */
        top-=toscroll;
        if (toscroll<8)
            DoScroll(toscroll);
        else
            toscroll=8;   /* toscroll now gives number of lines to redraw */
        while (toscroll)
            DrawLine(--toscroll);   /* draw lines not handled by scroll */
        }
    else
        {
        toscroll=-7-toscroll;
        if (toscroll>0)     /* new cursor position off bottom of screen */
            {
            top+=toscroll;      
            if (toscroll<8)
                DoScroll(-toscroll);
            else
                toscroll=8;
            while (toscroll)
                DrawLine(8-toscroll--);/* draw lines not handled by scroll */
            }
        }
    where=newcent;
    DrawCursor(where-top,TRUE);     /* draw cursor in new position */
    }

LOCAL_C VOID NewWhere(UWORD new)
    {               /* there is a new current record */
    if (ViewList)
        MakeHighlight(new);     /* scroll if need be */
    else
        {           /* editors view - so change all editor contents */
        where=new;
        LoadEditors();
        }
    }

LOCAL_C VOID WholeDisplay(VOID)
    {               /* the whole screen needs redrawn */
    if (ViewList)
        WholeListDisplay();
    else
        LoadEditors();
    }

LOCAL_C VOID DisplayStart(VOID)
    {               /* a new file needs loading and displaying */
    BuildIndex();
    if (!count)
        AddDummyEntry();
    where=0;
    WholeDisplay();
    }

LOCAL_C VOID ChangeOverTo(VOID *newdH,TEXT *newname)
    {               /* change the file being edited */
    DbfClose(dH);
    dH=newdH;       /* (new file is already successfully opened) */
    ChangeName(newname);
    DisplayStart();
    }

LOCAL_C INT doOpen(VOID **pdH,TEXT *pb,INT flags,DbfHeader *pHead)
    {           /* utility layer over DbfOpen */
    INT state;

    state=DbfStateDisabled;
    p_scpy((TEXT *)&pHead->fileType[0],"OPLDatabaseFile");
    return(uErrorValue(DbfOpen(&state,pdH,pb,flags,pHead,&dBuf[0],512,1)));
    }

LOCAL_C INT TryOpenDbf(TEXT *pb,VOID **pdH)
    {           /* save any current changes, and try to open named file */
    DbfHeader Header;

    if (SaveIfChanged())
        return(-1);
    return(doOpen(pdH,pb,P_FOPEN|P_FUPDATE|P_FRANDOM,&Header));
    }

LOCAL_C VOID SetupFilename(TEXT *pb)
    {           /* initialise text for editor from current filename */
    p_bcpy(pb+1,DatUsedPathNamePtr,*pb=p_slen(DatUsedPathNamePtr));
    }

LOCAL_C VOID OpenDbf(VOID)
    {           /* user wants to open another file */
    H_DI_FSEL open;
    TEXT openbuf[P_FNAMESIZE+2];
    VOID *newdH;    /* open new file before closing old */

    SetupFilename(open.fname=(&openbuf[0]));    /* seed filename */
    open.flags=H_FILE_PICK_SELECTOR|H_FILE_RESTRICT_LIST|H_FILE_SET_DEFEXT;
    FOREVER
        {       /* loop until success or user cancels */
        if (uOpenDialog("Open anniversary file"))
            break;
        if (uAddDialogItem(H_DIALOG_FSEL,"File:",&open))
            break;
        if (RunDialog()<=0)
            break; 
        openbuf[1+openbuf[0]]=0;        /* convert from lbc to zts */
        if (!TryOpenDbf(&openbuf[1],&newdH))
            {       /* new file opened successfully */
            ChangeOverTo(newdH,&openbuf[1]);
            return;
            }
        }
    }

LOCAL_C INT TryNewDbf(TEXT *pb,VOID **pdH)
    {       /* save any changes, and try to create new named file */
    DbfHeader Header;   /* values defined at top of this file */

    if (SaveIfChanged())
        return(-1);
    hEnsurePath(pb);        /* check the directory exists */
    Header.createVersion=DbfVersion();
    p_bcpy(&Header.dataStart,&ModelHeader,sizeof(ModelHeader));
    return(doOpen(pdH,pb,P_FREPLACE|P_FUPDATE|P_FRANDOM,&Header));
    }

LOCAL_C VOID NewDbf(VOID)
    {           /* user wants to create a new file */
    H_DI_FSEL new;
    TEXT newbuf[P_FNAMESIZE+2];
    VOID *newdH;        /* create new file before closing old */

    SetupFilename(new.fname=(&newbuf[0]));      /* seed filename */
    new.flags=H_FILE_NEW_EDITOR|H_FILE_SET_DEFEXT;
    FOREVER
        {       /* loop until success or user cancels */
        if (uOpenDialog("New anniversary file"))
            break;
        if (uAddDialogItem(H_DIALOG_FSEL,"File:",&new))
            break;
        if (RunDialog()<=0)
            break;
        newbuf[1+newbuf[0]]=0;          /* convert from lbc to zts */
        if (!TryNewDbf(&newbuf[1],&newdH))
            {       /* new file created successfully */
            ChangeOverTo(newdH,&newbuf[1]);
            return;
            }
        }
    }

LOCAL_C INT DoMerge(TEXT *pb,INT flags,INT dir)
    {               /* common code for Merge and SaveAs */
    UINT state;

    state=DbfStateDisabled;
    return(uErrorValue(DbfCopyFile(&state,dH,pb,flags,1,dir)));
    }

LOCAL_C VOID MergeDbf(VOID)
    {               /* user wants to merge two files */
    H_DI_FSEL merge;
    TEXT mergebuf[P_FNAMESIZE+2];

    if (SaveIfChanged())    /* save any changes first */
        return;
    SetupFilename(merge.fname=(&mergebuf[0]));  /* seed filename */
    merge.flags=H_FILE_PICK_SELECTOR|H_FILE_RESTRICT_LIST|H_FILE_SET_DEFEXT;
    FOREVER
        {
        if (uOpenDialog("Merge anniversary file"))
            break;
        if (uAddDialogItem(H_DIALOG_FSEL,"File:",&merge))
            break;
        if (RunDialog()<=0)
            break; 
        mergebuf[1+mergebuf[0]]=0;      /* convert lbc to zts */
        if (!DoMerge(&mergebuf[1],P_FOPEN,DbfCopyToHandle))
            {           /* merging handled by Dbf services */
            wInfoMsg("File merged");
            DisplayStart();
            return;
            }
        }
    }

LOCAL_C VOID DoSave(TEXT *pb,INT usenew)
    {               /* Save to new filename and optionally use new file */
    VOID *newdH;            /* use separate Dbf handle */
    INT i;
    INT len;

    if (TryNewDbf(pb,&newdH))   /* start by creating the new file */
        return;
    DbfTrash(dH);               /* NB dBuf[] shared between two handles */
    for (i=0; i<count; i++)
        {           /* for all records in existing file */
        LoadBuffer(i);      /* load the record in dBuf[] */
        len=DbfCopyDown(dH,dOff);   /* prepare to write to new file */
        Try(DbfAppend(newdH,len),"Failed to copy an entry");
        }
    DbfClose(newdH);        /* close new file */
    if (usenew)
        {               /* switch to using new file */
        DbfClose(dH);
        ChangeName(pb);
        while (TryOpenDbf(pb,&dH))
            ;
        DisplayStart();
        }
    wInfoMsg("Saved");
    }

LOCAL_C VOID SaveDbfAs(VOID)
    {               /* user wants to SaveAs */
    H_DI_FSEL save;
    TEXT savebuf[P_FNAMESIZE+2];
    UWORD usenew;

    if (uOpenDialog("Save as"))
        return;
    SetupFilename(save.fname=(&savebuf[0]));    /* seed filename */
    save.flags=H_FILE_NEW_EDITOR|H_FILE_SET_DEFEXT;
    if (uAddDialogItem(H_DIALOG_FSEL,"File:",&save))
        return;
    usenew=1;
    if (uAddChoiceList("Use new file",&usenew,"No","Yes",NULL))
        return;
    if (RunDialog()>0)
        {
        savebuf[1+savebuf[0]]=0;
        DoSave(&savebuf[1],usenew==2);
        }
    }

LOCAL_C VOID SetSymbolsState(VOID)
    {           /* Set the "show symbols" state of the editors */
    hEBShowSymbols(edit[2],ShowSymbols);
    }

LOCAL_C VOID ToggleShowSymbols(VOID)
    {           /* toggle the "show symbols" state of the editors */
    ShowSymbols^=TRUE;
    if (!ViewList)
        SetSymbolsState();
    cmds[SYMBOLS_ITEM]=(ShowSymbols? "hHide symbols": "hShow symbols");
    }

LOCAL_C VOID ExitApplication(VOID)
    {           /* user wants to exit */
    if (!SaveIfChanged())
        p_exit(0);
    }

LOCAL_C INT DateDialog(TEXT *title,INT *pdiy)
    {       /* present a date dialog with given title (common code) */
    UWORD monno;        /* month number in year */
    INT dayinm;         /* day number in month */
    LONG ldayinm;       /* dayinm as a LONG */
    H_DI_NUMBER num;

    dayinm=(*pdiy);
    monno=FindMonNo(&dayinm)+1;     /* crack the given DayInYear */
    ldayinm=dayinm+1;
    num.value=(&ldayinm);
    num.low=1;
    num.high=31;        /* this limit is generally correct */
    FOREVER
        {
        if (uOpenDialog(title))
            return(-1);
        if (AddMonthChoiceList(&monno))
            return(-1);
        if (uAddDialogItem(H_DIALOG_NUMBER,"Day in month",&num))
            return(-1);
        if (RunDialog()<=0)
            return(-1);
        dayinm=(INT)ldayinm-1;
        if (dayinm<daysim[monno-1])
            break;
        wInfoMsg("Illegal date");
        }
    *pdiy=FindDiy(monno-1,dayinm);  /* reconstitute DayInYear */
    return(0);
    }

LOCAL_C INT doEditDate(TEXT *title)
    {           /* let user edit date, then set corresponding text */
    INT diy;

    diy=currdiy;
    if (DateDialog(title,&diy))
        return(-1);
    if (!SetDateText(diy))      /* set text into date "editor" */
        currdiy=diy;
    return(0);
    }

LOCAL_C INT CreateEditors(VOID)
    {                   /* create all three editors */
    H_EDIT_BOX heb;

    heb.win=MainWid;        /* editors lodge on main window */
    heb.maxchars=40;
    heb.vulen=ScreenWidth-12-32-54;
    heb.pos.x=5+32;
    heb.pos.y=2;
    edit[0]=hEBOpen(0,&heb);        /* date "editor" */
    if (!edit[0])
        return(-1);
    hEBSetCWidth(edit[0],0);    /* hide any cursor display in date editor */
    heb.maxchars=21;
    heb.vulen=ScreenWidth-12-32-10;
    heb.pos.x=5+32;
    heb.pos.y=19;
    edit[1]=hEBOpen(0,&heb);        /* name editor */
    if (!edit[1])
        return(-1);
    heb.maxchars=255;
    heb.vulen=ScreenWidth-12;
    heb.vislines=4;
    heb.pos.x=5;
    heb.pos.y=36;
    edit[2]=hEBOpen(H_EDIT_BOX_VISLINES|        /* notes editor */
                        H_EDIT_BOX_LEFT_CURSOR|H_EDIT_BOX_CLIPBOARD,&heb);
    if (!edit[2])
        return(-1);
    if (ShowSymbols)
        SetSymbolsState();      /* adjust show symbols state */
    return(0);
    }

LOCAL_C VOID CloseEditors(VOID)
    {           /* close all three editors */
    INT i;

    for (i=0; i<3; i++)
        {
        if (edit[i])
            {
            hEBClose(edit[i]);
            edit[i]=NULL;
            }
        }
    }

LOCAL_C VOID ClearWholeWindow(VOID)
    {               /* clear the entire screen */
    P_RECT whole;

    whole.tl.x=whole.tl.y=0;
    whole.br.x=240;
    whole.br.y=80;
    gClrRect(&whole,G_TRMODE_CLR);
    }

LOCAL_C VOID SetStyle(INT style)
    {               /* set the style of the current graphics context */
    G_GC gc;

    gc.style=style;
    gSetGC(0,G_GC_MASK_STYLE,&gc);
    }

LOCAL_C VOID DisplayPrompts(VOID)
    {               /* display the prompts of the editors */
    SetStyle(G_STY_ITALIC);         /* use italics */
    gPrintText(5,2+8,"Date:",5);
    gPrintText(5,19+8,"Name:",5);
    SetStyle(0);                    /* clear italics */
    }

LOCAL_C VOID DrawFramework(VOID)
    {               /* draw the "framework" of the current display */
    INT i;

    for (i=0; i<3; i++)
        DrawBorder(i);
    DisplayPrompts();
    DrawWhereAndCount();
    }

LOCAL_C INT ToggleListMode(VOID)
    {           /* change between list view and editors view */
    if (ViewList)
        {       /* change over to editors view */
        if (CreateEditors())
            {       /* failed to create all editors */
            CloseEditors();     /* clean up */
            return(-1);
            }
        ClearWholeWindow();
        DrawFramework();
        LoadEditors();
        }
    else
        {       /* change over to list view */
        if (SaveIfChanged())
            return(-1);
        CloseEditors();
        ClearWholeWindow();
        WholeListDisplay();
        }
    ViewList^=TRUE;
    cmds[LIST_ITEM]=(ViewList? "lNotes view": "lList view");
    return(0);
    }

LOCAL_C INT CheckNotListMode(VOID)
    {           /* ensure in editors mode */
    if (!ViewList)
        return(0);
    return(ToggleListMode());
    }

LOCAL_C VOID EnterAnniversary(VOID)
    {           /* user wants to enter an anniversary */
    if (CheckNotListMode())
        return;     /* failed to create editors */
    if (SaveIfChanged())
        return;     /* failed to save changes */
    if (doEditDate("Enter anniversary"))
        return;     /* user cancelled or no memory for dialog */
    AddDummyEntry();
    LoadEditors();
    RotateTo(1);    /* emphasise the name editor */
    }

LOCAL_C INT CheckEditing(VOID)
    {       /* some menu commands are only allowed in notes window */
    if (ViewList || emph!=2)
        {
        wInfoMsg("Only available in notes window");
        return(-1);
        }
    return(0);
    }

LOCAL_C INT ComplainIfChanged(VOID)
    {       /* complain if there have been no changes */
    if (!CheckIfChanged())
        {
        wInfoMsg("Not changed");
        return(0);
        }
    return(-1);
    }

LOCAL_C VOID SaveAnniversary(VOID)
    {       /* user wants to save any changes made */
    if (!ComplainIfChanged())
        return;             /* no changes made */
    if (!SaveChanges())
        wInfoMsg("Anniversary saved");
    }

LOCAL_C VOID UndoEditing(VOID)
    {       /* user wants to "revert to saved" for current record */
    if (!ComplainIfChanged())
        return;
    if (Confirm("Undo editing"))
        return;         /* user failed to confirm loss of changes */
    LoadEditors();
    ClearChangedFlags();
    }

LOCAL_C VOID ChangeDate(VOID)
    {                   /* user wants to change an anniversary date */
    if (CheckNotListMode())
        return;
    RotateTo(0);        /* emphasise date window */
    doEditDate("Change date of anniversary");
    }

LOCAL_C VOID WanderThrough(VOID)
    {   /* provide help for user to "wander through" anniversaries */
    if (SaveIfChanged())
        return;
    if (ViewList)
        wInfoMsg("Use \030 and \031 to view all anniversaries");
    else
        {
        RotateTo(0);    /* compiler will share code here */
        wInfoMsg("Use \032 and \033 to view all anniversaries");
        }
    }

LOCAL_C VOID PrintSetupDialog(VOID)
    {       /* run the print setup dialog suite */
    EditEmphasise(FALSE);       /* remove any existing flashing cursor */
    hPrintSetupDialog();
    EditEmphasise(TRUE);        /* reinstate any flashing cursor */
    }

LOCAL_C INT DoFind(INT try,INT dir,INT k)
    {       /* look for string in given direction, in next k records */
    UBYTE *pb;          /* for text of record */
    INT off;            /* offset into field where found */
    INT newemph;        /* which editor to emphasise */

    FOREVER
        {           /* for up to k records */
        if (!k--)
            return(-1);     /* not found */
        try+=dir;
        if (try>=count)
            try=0;          /* handle wrap round forwards */
        else if (try<0)
            try=count-1;    /* handle wrap round backwards */
        SeekTo(index[try].no);      /* load text of specified record */
        pb=(&dBuf[dOff+4]);         /* position to name field */
        if (findtype&1)
            {               /* if searching in name field */
            newemph=1;
            off=LookFor(pb);
            if (off>=0)
                break;      /* found */
            }
        pb+=(*pb)+1;        /* position to notes field */
        if (findtype&2)
            {               /* if searching in notes field */
            newemph=2;
            off=LookFor(pb);
            if (off>=0)
                break;      /* found */
            }
        }
    if (ViewList)
        {
        if (newemph==1)
            {               /* can position to name field in list view... */
            MakeHighlight(try);
            return(0);
            }
        if (ToggleListMode())   /* ...but not to notes field */
            return(0);
        }
    NewWhere(try);          /* load editors with relevant record */
    hEBSetSelect(edit[newemph],off,off+find[0]);    /* highlight match */
    RotateTo(newemph);      /* emphasise correct editor */
    return(0);
    }

LOCAL_C VOID FindAnniversary(VOID)
    {                   /* user wants to Find */
    H_DI_EDIT edfind;

    if (SaveIfChanged())
        return;         /* failed to save changes */
    edfind.str=(&find[0]);      /* seed with previous find string */
    edfind.len=19;
    FOREVER
        {
        if (uOpenDialog("Find text"))
            return;
        if (uAddDialogItem(H_DIALOG_EDIT,"Text",&edfind))
            return;
        if (uAddChoiceList("Search",&findtype,"Just names",
                                        "Just notes","Names and notes",NULL))
            return;
        if (RunDialog()<=0)
            return;
        if (!DoFind(count,+1,count))
            return;             /* match found */
        wInfoMsg("Not found");
        }
    }

LOCAL_C VOID FindAgain(INT dir)
    {               /* user wants to Find Again */
    if (SaveIfChanged())
        return;
    if (DoFind(where,dir,count-1))
        wInfoMsg("No more found");
    }

LOCAL_C VOID JumpTo(VOID)
    {               /* user wants to jump to record by index number */
    LONG curr;
    H_DI_NUMBER num;
    INT new;

    if (SaveIfChanged())
        return;             /* failed to save changes */
    if (uOpenDialog(NULL))          /* this dialog has no title */
        return;
    curr=where+1;           /* seed with current value */
    num.value=(&curr);
    num.low=1;
    num.high=count;
    if (uAddDialogItem(H_DIALOG_NUMBER,"Jump to record",&num))
        return;
    if (RunDialog()<=0)
        return;
    new=(UINT)(curr-1);
    NewWhere(new);
    }

LOCAL_C INT Today(VOID)
    {               /* return DayInYear for today */
    P_DAYSEC ds;
    P_DATE dt;
    ULONG now;
    
    now=p_date();
    p_sttods(&now,&ds);
    p_dstodt(&ds,&dt);
    return(FindDiy(dt.month,dt.day));
    }

LOCAL_C VOID FindDate(VOID)
    {               /* user wants to find record on given date */
    INT diy;
    INT mid;
    INDENT ent;

    diy=Today();        /* seed with today */
    if (DateDialog("Find date",&diy))
        return;         /* user cancelled or out of memory */
    ent.diy=diy;
    ent.no=0;
    mid=LocateByDate(&ent);
    if (mid>=count)
        mid=count-1;
    NewWhere(mid);
    }

LOCAL_C INT PrintLine(H_PRINT *pr)
    {           /* call-back function to print line of data */
    INT ind;

    switch (PrintState++)
        {
    case 0:     /* reached only at very beginning: set up print indent */
        PrintIndent=hPrintSenseBufWidth(" ",1)*4;
        hPrintSetSI(PrintIndent);       /* indent for word-wrapped lines */
        PrintState++;
    case 1:     /* print date */
        LoadBuffer(PrintRec);           /* load record into dBuf[] */
        PrintBuf=(&dBuf[dOff+2]);       /* position to DayInYear field */
        DiyToBuf(*(UWORD *)PrintBuf,&PrintDate[0]);
        PrintBuf+=2;                    /* position to name text field */
        pr->buf=(&PrintDate[0]);        /* print text version of date */
        pr->blen=p_slen(&PrintDate[0]);
        pr->style|=H_PRINT_STY_BOLD;    /* print in bold */
        break;
    case 2:     /* print name */
        pr->blen=(*PrintBuf++);
        pr->buf=(TEXT *)PrintBuf;
        PrintBuf+=pr->blen;             /* position to notes text field */
        PrintLen=(*PrintBuf++);         /* how many characters in note */
        break;
    default:    /* print (next section of) notes */
        if (PrintNamesOnly==1)
            {       /* if NOT only printing names */
            pr->buf=(TEXT *)PrintBuf;
            pr->indent=PrintIndent;
            ind=p_bloc(PrintBuf,PrintLen,'\n');     /* search for '\n' */
            if (ind<0)
                {           /* all (remaining) text is one "paragraph" */
                pr->blen=PrintLen;      /* print it all */
                PrintLen=0;             /* none left */
                }
            else
                {
                pr->blen=ind;       /* print rest of "paragraph" */
                PrintLen-=ind+1;    /* still some of note left to print */
                PrintBuf+=ind+1;    /* position to next "paragraph" */
                }
            if (pr->blen || PrintLen)
                break;      /* not finished note yet */
            }
        pr->blen=0;         /* print blank line */
        PrintState=1;       /* ready for date of next record */
        return(++PrintRec!=count);  /* return FALSE if finished */
        }
    pr->flags|=H_PRINT_KEEP;        /* keep record together, if possible */
    return(TRUE);                   /* still more to print */
    }

LOCAL_C VOID PrintAnniversaries(VOID)
    {           /* user wants to Print */
    if (SaveIfChanged())
        return;     /* failed to save changes */
    if (uOpenDialog("Print"))
        return;
    if (uAddChoiceList("Names only",&PrintNamesOnly,"No","Yes",0))
        return;
    if (RunDialog()<=0)
        return;
    PrintRec=0;             /* initialise print state variables */
    PrintState=0;
    hPrint(PrintLine);      /* specify call-back function */
    }

LOCAL_C VOID DeleteAnniversary(VOID)
    {           /* user wants to delete anniversary */
    INT state;

    if (count==1 && !SizeRecord())
        {
        wInfoMsg("Nothing to delete");
        return;
        }
    if (Confirm("Delete this anniversary"))
        return;         /* user failed to confirm */
    SeekToCurrent();            /* load current entry into dBuf[] */
    state=DbfStateDisabled;
    Check(DbfEraseRead(&state,dH,&dOff));
    IndexDelete();              /* remove current entry from index too */
    if (!count)
        AddDummyEntry();        /* don't leave index completely empty */
    if (where==count)
        where=count-1;
    WholeDisplay();
    }

LOCAL_C VOID ManageCommand(INT index)
    {               /* switch on command index to appropriate function */
    switch (index)
        {
    case ITEMS_BEFORE_FILE:
        NewDbf();
        break;
    case ITEMS_BEFORE_FILE+1:
        OpenDbf();
        break;
    case ITEMS_BEFORE_FILE+2:
        MergeDbf();
        break;
    case ITEMS_BEFORE_FILE+3:
        SaveDbfAs();
        break;
    case ITEMS_BEFORE_FILE+4:
        CompressFile();
        break;
    case ITEMS_BEFORE_FILE+5:
        ExitApplication();
        break;
    case ITEMS_BEFORE_EDIT:
        if (!CheckEditing())
            {       /* insert text - handled by editor */
            if (hEBPaste(edit[2])>0)
                wInfoMsg("No text to insert");
            }
        break;
    case ITEMS_BEFORE_EDIT+1:
        if (!CheckEditing())
            {       /* copy text - handled by editor */
            if (hEBCopy(edit[2])>0)
                wInfoMsg("No text to copy");
            else
                wInfoMsg("Text copied");
            }
        break;
    case ITEMS_BEFORE_EDIT+2:
        if (!CheckEditing())
            hEBEvaluate(edit[2]);   /* evaluate - handled by editor */
        break;
    case ITEMS_BEFORE_EDIT+3:
        ToggleShowSymbols();
        break;
    case ITEMS_BEFORE_EDIT+4:
        SaveAnniversary();
        break;
    case ITEMS_BEFORE_EDIT+5:
        UndoEditing();
        break;
    case ITEMS_BEFORE_CHANGE:
        EnterAnniversary();
        break;
    case ITEMS_BEFORE_CHANGE+1:
        DeleteAnniversary();
        break;
    case ITEMS_BEFORE_CHANGE+2:
        ChangeDate();
        break;
    case ITEMS_BEFORE_SEARCH:
        FindAnniversary();
        break;
    case ITEMS_BEFORE_SEARCH+1:
        FindAgain(+1);
        break;
    case ITEMS_BEFORE_SEARCH+2:
        FindAgain(-1);          /* this is "Find Backwards" */
        break;
    case ITEMS_BEFORE_SEARCH+3:
        FindDate();
        break;
    case ITEMS_BEFORE_SEARCH+4:
        JumpTo();
        break;
    case ITEMS_BEFORE_SPECIAL:
        ToggleListMode();
        break;
    case ITEMS_BEFORE_SPECIAL+1:
        WanderThrough();
        break;
    case ITEMS_BEFORE_SPECIAL+2:
        PrintAnniversaries();
        break;
    case ITEMS_BEFORE_SPECIAL+3:
        PrintSetupDialog();
        }
    }

LOCAL_C INT ObeySystemCommand(INT cmd,TEXT *pb,VOID **pdH)
    {       /* respond to SwitchFiles command from System Screen */
    return((cmd=='O'? TryOpenDbf: TryNewDbf)(pb,pdH));
    }

LOCAL_C VOID ProcessSystemCommand(VOID)
    {       /* respond to message from System Screen */
    TEXT buf[P_FNAMESIZE+2];
    VOID *newdH;

    wGetCommand((UBYTE *)&buf[0]);
    if (buf[0]=='X')
        ExitApplication();
    if (!ObeySystemCommand(buf[0],&buf[1],&newdH))
        ChangeOverTo(newdH,&buf[1]);
    }

LOCAL_C VOID TryExecuteCommand(INT keycode)
    {      /* try to find command to execute matching accelerator passed */
    keycode=uLocateCommand(keycode);        /* look up accelerator table */
    if (keycode>=0)                 /* found - keycode converted to index */
        ManageCommand(keycode);
    }

LOCAL_C VOID RotateFocus(INT shifted)
    {               /* respond to TAB key */
    INT newemph;

    if (ViewList)
        ToggleListMode();
    else
        {
        newemph=emph;
        if (shifted)
            {           /* go backwards for SHIFT TAB */
            if (!newemph--)
                newemph=2;
            }
        else if (++newemph==3)
            newemph=0;
        RotateTo(newemph);
        }
    }

LOCAL_C VOID MoveTo(INT newwhere)
    {   /* move to given record (works in either list or editors view */
    if (SaveIfChanged())
        return;
    if (newwhere<0)
        newwhere=count-1;
    else if (newwhere>=count)
        newwhere=0;
    NewWhere(newwhere);
    }

LOCAL_C INT ResizeWidth(UWORD width)
    {           /* try to change the width of the main display region */
    INT ret;
    W_WINDATA wd;

    wd.extent.tl.x=wd.extent.tl.y=0;
    wd.extent.width=width;
    wd.extent.height=80;
    wSetWindow(MainWid,W_WIN_EXTENT,&wd);
    ret=uErrorValue(wCheckPoint());     /* check whether successful */
    if (!ret)
        ScreenWidth=width;
    return(ret);
    }

LOCAL_C VOID ToggleStatusWindow(VOID)
    {       /* attempt to toggle the visibility of the status window */
    if (show_statwin)
        {       /* status window is currently showing */
        if (ResizeWidth(240))
            return;
        wsDisable();        /* remove status window */
        }
    else
        {       /* status window not currently showing */
        if (ResizeWidth(189))
            return;
        wsEnable();         /* display status window */
        }
    show_statwin^=TRUE;
    ClearWholeWindow();
    if (ViewList)
        {
        gBorder(lastbflags);
        ListDisplay();
        }
    else
        {
        lastchanged=FALSE;
        DrawFramework();
        hEBChangeWidth(edit[0],ScreenWidth-12-32-54);
        hEBChangeWidth(edit[1],ScreenWidth-12-32-10);
        hEBChangeWidth(edit[2],ScreenWidth-12);   /* change editor widths */
        }
    }

LOCAL_C VOID UpdateDateDisplays(VOID)
    {       /* system time format defaults may have changed */
    if (ViewList)
        ListDisplay();
    else
        SetDateText(currdiy);
    }

LOCAL_C VOID KeyToEditors(WMSG_KEY *pkey)
    {           /* pass key to emphasised editor */
    if (!emph)
        {           /* emphasis on date "editor" */
        switch (pkey->keycode)
            {
        case W_KEY_LEFT:
            MoveTo(pkey->modifiers&W_PSION_MODIFIER? 0: where-1);
            break;
        case W_KEY_RIGHT:
            MoveTo(pkey->modifiers&W_PSION_MODIFIER? count-1: where+1);
            break;
        case W_KEY_RETURN:
            ChangeDate();
            }
        return;
        }
    if (pkey->keycode==W_KEY_RETURN && emph!=2)
        return;
    hEBHandleKey(edit[emph],pkey->keycode,pkey->modifiers);
    }

LOCAL_C VOID ScrollPageBy(INT delta)
    {                   /* implementation of PgUp / PgDn */
    if (delta<(-7))
        delta=(-7);
    else if (delta>7)
        delta=7;
    top+=delta;
    where+=delta;
    ListDisplay();
    }

LOCAL_C VOID KeyToList(WMSG_KEY *pkey)
    {           /* key handling routine for list view */
    switch (pkey->keycode)
        {
    case W_KEY_UP:
        if (where)
            MakeHighlight(where-1);
        break;
    case W_KEY_DOWN:
        if (where<count-1)
            MakeHighlight(where+1);
        break;
    case W_KEY_PAGE_UP:
        if (pkey->modifiers&W_CTRL_MODIFIER)
            MakeHighlight(0);
        else if (!top)
            MakeHighlight(0);
        else
            ScrollPageBy(-top);
        break;
    case W_KEY_PAGE_DOWN:
        if (pkey->modifiers&W_CTRL_MODIFIER)
            MakeHighlight(count-1);
        else if (top+8>=count)
            MakeHighlight(count-1);
        else
            ScrollPageBy(count-top-8);
        break;
        }
    }

LOCAL_C VOID CheckEmbellishments(VOID)
    {               /* called regularly (once every event) */
    INT bflags;
    INT changed;
    P_RECT box;     /* rectangle to draw "changed" asterisk in */

    if (ViewList)
        {       /* see if scroll arrows in border need changing */
        bflags=CalculateBflags();
        if (bflags!=lastbflags)
            gBorder(lastbflags=bflags);
        }
    else
        {   /* see if "changed" asterisk after name editor needs changing */
        changed=CheckIfChanged();
        if (changed==lastchanged)
            return;
        lastchanged=changed;
        box.tl.x=ScreenWidth-10;
        box.tl.y=19;
        box.br.x=ScreenWidth;
        box.br.y=29;
        gPrintBoxText(&box,8,G_TEXT_ALIGN_CENTRE,0,"*",changed);
        }
    }

LOCAL_C VOID MainLoop(VOID)
    {                   /* central event processing loop */
    INT ret;
    WMSG_KEY key;

    FOREVER
        {
        CheckEmbellishments();      /* check display up-to-date */
        uGetKey(&key);
        if (key.keycode&W_EVENT_KEY)
            {
            switch (key.keycode)
                {
            case CONS_EVENT_FOREGROUND:
                UpdateDateDisplays();
                break;
            case CONS_EVENT_COMMAND:
                ProcessSystemCommand();
                }
            }
        else if (key.keycode&W_SPECIAL_KEY)     /* Psion key pressed */
            TryExecuteCommand(key.keycode&(~W_SPECIAL_KEY));
        else
            {
            switch (key.keycode)
                {
            case W_KEY_MENU:
                if (key.modifiers&W_CTRL_MODIFIER)
                    ToggleStatusWindow();
                else
                    {           /* display menu bar */
                    ret=uPresentMenus();
                    if (ret>0)
                        TryExecuteCommand(ret);
                    }
                break;
            case W_KEY_MODE:        /* if assigned app button pressed */
                ToggleListMode();
                break;
            case W_KEY_TAB:
                RotateFocus(key.modifiers&W_SHIFT_MODIFIER);
                break;
            default:
                (ViewList? KeyToList: KeyToEditors)(&key);
                }
            }
        }
    }

LOCAL_C VOID SmallScreen(VOID)
    {           /* attempt to make room for status window */
    if (ResizeWidth(189))
        p_exit(0);
    }

LOCAL_C VOID CreateGC(VOID)
    {     /* attempt to create default graphics context on main window */
    INT hgc;        /* graphics context handle (if successful) */

    hgc=gCreateGC0(MainWid);
    if (hgc<0)
        p_exit(hgc);
    }

LOCAL_C VOID SpecificInit(VOID)
    {                   /* application initialisation specific to Days */
    INT command;

    uEscape(FALSE);     /* disable Psion-Esc */
    MainWid=uFindMainWid();     /* find id of main display window */
    CreateGC();                 /* create default graphics context */
    SmallScreen();              /* make room for status window */
    wsEnable();                 /* display status window */
    CreateEditors();            /* start in editor mode */
    command=hCrackCommandLine();        /* analyse command line */
    if (ObeySystemCommand(command,DatUsedPathNamePtr,&dH))
        p_exit(0);
    DrawFramework();
    DisplayStart();
    }

GLDEF_C VOID main(VOID)
    {                   /* the main() for all Hwif programs */
    uCommonInit();      /* initialisation common to all Hwif programs */
    SpecificInit();     /* initialisation specific to Days */
    MainLoop();         /* the central event processing loop */
    }
