/* scan.c */

#include "stats.h"

#define  TIME_SLICE  10      /* default time slice in centiseconds */
#define  MAX_PATH_NAME  255  /* max number of chars in a path name */
#define  DIR_BUFF_SIZE  100  /* space for each OS_GBPB 10 call */

/* local data structures for directory scanning */

typedef struct {
    unsigned int load;
    unsigned int exec;
    unsigned int length;
    unsigned int attributes;
    int type;                   /* 1 => File, 2 => Dir, 3 => Image file */
    char name[1];
} DirEntryRec, *DirEntryPtr;

typedef struct _dirbuffrec {
    struct _dirbuffrec *prev;     /* pointer to parent directory buffer;
                                       NULL => this is the root */
    int index;                    /* for next call of OS_GBPB 10;
                                       -1 => all done */
    int count;                    /* number of entries remaining in buffer,
                                       including the current one */
    DirEntryPtr entry;            /* points to current entry in buffer;
                                       NULL => no more in buffer */
    unsigned int size;            /* space occupied by files inside this
                                       directory */
    char buff [DIR_BUFF_SIZE];    /* filled up by OS_GBPB */
} DirBuffRec, *DirBuffPtr;

static DirBuffPtr dirbuff = NULL; /* identifies current scan position */
static unsigned int currdirsize;
static char currdir [MAX_PATH_NAME + 1];

static Bool scan_in_progress = FALSE;
static Bool scan_paused = FALSE;
static Bool scan_complete = TRUE;
static Bool update_display = TRUE;
static int time_slice = TIME_SLICE;
static Bool hasoverflowed = FALSE;    /* set when more than 2^31 bytes */

#define  D_COUNTS   0
#define  D_BYTES    1

static int display_val = D_COUNTS;

#define  D_ABS      0
#define  D_PERCENT  1

static int display_format = D_PERCENT;

static ObjectId scan_dbox;
static int small_ymin;      /* extent when not full display */
static int full_ymin;       /* extent for full display */

static char *rootdir = NULL;
static int numranges;
static unsigned int *range = NULL;
static int *filecount = NULL;
static unsigned int *filesize = NULL;
static int *dircount = NULL;
static unsigned int *dirsize = NULL;


/* for save protocols */
static char textsavename[MAX_PATH_NAME+1] = "Stats";
static char csvsavename[MAX_PATH_NAME+1] = "StatsCSV";

#define  SAVE_TEXT     0
#define  SAVE_CSV      1

static int savetype;

#define  LINE_LENGTH  63      /* max. chars in each line of output */
#define  HDR_LINES     4      /* extra lines (for headers, trailers etc.) */

static char *savebuff;
static int savebuffsize;


/* forward reference */

static void scan_display_state (void);
static void scan_display_position (void);



/**************************  Miscellaneous veneers *************************/


static error * check_alloc (void **p, int n)
{
    *p = calloc (1, n);
    if (*p == NULL)
        return error_lookup ("NoMem");

    return NULL;
}


static void check_free (void **p)
{
    free (*p);
    *p = NULL;
}



/***************************  More toolbox veneers *************************/


/*
 * Enable NULL events
 */

static void enable_nulls (void)
{
    unsigned int mask;

    event_get_mask (&mask);
    event_set_mask (mask & 0xfffffffe);

    return;
}


/*
 * Disable NULL events
 */

static void disable_nulls (void)
{
    unsigned int mask;

    event_get_mask (&mask);
    event_set_mask (mask | 1);

    return;
}



/***************************  Application routines *************************/


/*
 * Converts strings of the form [&]<int>[k|K|m|M|g|G] into integers.
 */

static unsigned int myatoi (char *t)
{
    unsigned int n = 0;
    unsigned int base = 10;
    unsigned int x;

    if (*t == '&')
    {
        base = 16;
        t++;
    }

    while (TRUE)
    {
        x = 100;
        if (*t >= '0' && *t <= '9')
            x = *t - '0';
        if (base == 16)
        {
            if (*t >= 'a' && *t <= 'f')
                x = *t - 'a' + 10;
            else
            if (*t >= 'A' && *t <= 'F')
                x = *t - 'A' + 10;
        }

        if (x == 100)
            switch (*t)
            {
            case 'k': case 'K':    return n*1024;
            case 'm': case 'M':    return n*1048576;
            case 'g': case 'G':    return n*1024*1048576;
            default:               return n;
            }

        n = n*base + x;
        t++;
    }
}


/*
 * Check the parameters make sense, and then set up scan state ready to be
 *  resumed.
 *
 * Returns FALSE iff there is a problem.
 */

static Bool scan_init (char *root, char *ranges)
{
    int n = strlen (root);
    error *err = NULL;
    int i;

    if (*root == 0)
    {
        err = error_lookup ("EmptyRoot");
        goto fail;
    }

    /* allocate and initialise the root directory name */
    EG (fail, check_alloc ((void **)&rootdir, n + 1) );
    strcpy (rootdir, root);

    /* set initial scan state */
    EG (fail, check_alloc ((void **)&dirbuff, sizeof (DirBuffRec)) );
    dirbuff->prev = NULL;
    dirbuff->index = 0;        /* first read from start of directory */
    dirbuff->entry = NULL;     /* nothing in the buffer yet */
    strcpy (currdir, rootdir); /* this is the directory to scan */

    /* Determine how many ranges there are */
    {
        char *t = ranges;

        numranges = 1;      /* there's always one! */
        while (TRUE)
        {
            t = strchr (t, ',');
            numranges++;
            if (t == NULL)
                break;
            t++;
        }

        if (numranges > 50)
        {
            err = error_lookup ("TooManyRanges");
            goto fail;
        }
    }

    /* Allocate space for statistics collection */
    EG (fail, check_alloc ((void **)&range, numranges * sizeof (unsigned int)) );
    EG (fail, check_alloc ((void **)&filecount, numranges * sizeof (int)) );
    EG (fail, check_alloc ((void **)&filesize, numranges * sizeof (unsigned int)) );
    EG (fail, check_alloc ((void **)&dircount, numranges * sizeof (int)) );
    EG (fail, check_alloc ((void **)&dirsize, numranges * sizeof (unsigned int)) );

    /* initialise and check ascending order of range specifications */
    {
        char *t = ranges;
        char *s = t;
        int i = 0;

        while (TRUE)
        {
            t = strchr (t, ',');
            if (t != NULL)
                *t = 0;
            range[i++] = myatoi (s);
            if (t != NULL)
                *t = ',';

            if (i > 1 && range[i-1] <= range[i-2])
            {
                err = error_lookup ("OutOfOrder");
                goto fail;
            }
                
            if (t == NULL)
                break;
            t++;
            s = t;
        }

        /* add final range for all the rest */
        range[i] = 0xffffffff;
    }

    /* initialise statistics */
    currdirsize = 0;
    for (i = 0; i < numranges; i++)
    {
        filecount[i] = 0;
        dircount[i] = 0;
        filesize[i] = 0;
        dirsize[i] = 0;
    }

    /* allocate space for output buffer when saving */
    savebuffsize = (numranges + HDR_LINES) * (LINE_LENGTH + 1);
    EG (fail, check_alloc ((void **)&savebuff, savebuffsize) );
    

    return TRUE;

  fail:
    check_free ((void **)&rootdir);
    check_free ((void **)&range);
    check_free ((void **)&filecount);
    check_free ((void **)&filesize);
    check_free ((void **)&dircount);
    check_free ((void **)&dirsize);
    check_free ((void **)&dirbuff);
    check_free ((void **)&savebuff);

    if (err != NULL)
        error_box (err);

    return FALSE;
}


/*
 * Try to (re)fill the current directory buffer.
 *
 * On entry:
 *    dirbuff->index gives the value to pass to OS_GBPB 10 (may be -1)
 *
 * On successful exit:
 *    Result is TRUE
 *    dirbuff->index gives the next value to pass to OS_GBPB 10 (may be -1)
 *    dirbuff->count is the number of entries read into the buffer
 *    dirbuff->entry addresses the first entry in the buffer
 *
 * On unsuccessful exit:
 *    Result is FALSE
 */

static Bool refill_buffer (void)
{
    error *err = NULL;

    if (dirbuff->index == -1)
        return FALSE;

    err = swi (OS_GBPB, R0, 10,
                        R1, currdir,
                        R2, dirbuff->buff,
                        R3, 1000,
                        R4, dirbuff->index,
                        R5, DIR_BUFF_SIZE,
                        R6, 0,
                      OUT,
                        R3, &dirbuff->count,
                        R4, &dirbuff->index,
                      END);

    if (err)
    {
        error_box (err);
        return FALSE;
    }

    if (dirbuff->count == 0)
    {
        if (dirbuff->index != -1)
            error_box (error_lookup ("FunnyOSGBPB"));
        return FALSE;
    }
                          
    dirbuff->entry = (DirEntryPtr) dirbuff->buff;

    return TRUE;
}


/*
 * Replace the current directory buffer by its parent.
 * Also updates currdir.
 *
 * Returns TRUE unless the current directory buffer was the root on entry.
 */

static Bool unstack (void)
{
    DirBuffPtr prev = dirbuff->prev;

    check_free ((void **) &dirbuff);
    dirbuff = prev;

    /* remove leaf from currdir */
    {
        int n = strlen (currdir);

        while (n > 0 && currdir[n] != '.')
            n--;

        if (currdir[n] == '.')
            currdir[n] = 0;
    }

    return (dirbuff != NULL);
}


/*
 * Updates dirbuff->entry to address the next entry - if any - or NULL
 *  otherwise.
 */

static void next_entry (void)
{
    dirbuff->count--;
    if (dirbuff->count == 0)
        dirbuff->entry = NULL;
    else
    {
        char *p = (char *) dirbuff->entry;
        char *name = dirbuff->entry->name;

        p += offsetof (DirEntryRec, name);
        p += strlen (name) + 1;
        dirbuff->entry = (DirEntryPtr) ( ((unsigned int)p + 3) & ~3 );
    }

    return;
}


/*
 * Chain on a new directory buffer and make it current.
 * Also updates currdir.
 *
 * Returns TRUE unless some problem arises.
 */

static Bool stack (void)
{
    char *name = dirbuff->entry->name;
    DirBuffPtr new;
    error *err = NULL;

    /* extend currdir */
    {
        int len = strlen (currdir);

        if (len + strlen (name) > MAX_PATH_NAME)
        {
            error_box (error_lookup ("LongPath", currdir, name));
            return FALSE;
        }

        sprintf (currdir + len, ".%s", name);
    }

    /* allocate new directory buffer */
    err = check_alloc ((void **) &new, sizeof (DirBuffRec));
    if (err != NULL)
    {
        error_box (err);
        return FALSE;
    }

    /* link in and initialise directory buffer */
    new->prev = dirbuff;
    dirbuff = new;
    dirbuff->index = 0;
    dirbuff->entry = NULL;

    return TRUE;
}


/*
 * Continue scanning for a bit. Return FALSE if the scan is completed before
 *  the self-inflicted timeslice runs out.
 */

static Bool scan_continue (void)
{
    unsigned int timetostop;
    unsigned int timenow;
    int i;

    swi (OS_ReadMonotonicTime, OUT, R0, &timenow, END);
    timetostop = timenow + time_slice;

    while (TRUE)
    {
        /* is it time to refill the buffer of directory entries? */
        if (dirbuff->entry == NULL)
        {
            /* are there any more entries to be read for this directory? */
            if (!refill_buffer ())
            {
                /* update directory statistics */
                i = 0;
                while (range[i] < currdirsize)
                    i++;
                dircount[i]++;
                dirsize[i] += currdirsize;

                /* can we unstack, or have we reached the end? */
                if (unstack ())
                {
                    currdirsize = dirbuff->size;
                    next_entry ();
                }
                else
                    return FALSE;    /* all done */
            }
        }

        else
            switch (dirbuff->entry->type)
            {
            case 1:  case 3:          /* file, or image file */
                {
                    unsigned int size = dirbuff->entry->length;

                    /* update file statistics */
                    i = 0;
                    while (range[i] < size)
                        i++;
                    filecount[i]++;
                    filesize[i] += size;
                    currdirsize += size;

                    next_entry ();
                }
                break;

            case 2:                   /* directory */
                dirbuff->size = currdirsize;
                if (stack ())
                    currdirsize = 0;
                break;

            default:                  /* not expected */
                error_box (error_lookup ("BadObjectType",
                                         dirbuff->entry->type,
                                         currdir, dirbuff->entry->name) );
                break;
            }

        /* is it time to let someone else have a go? */
        swi (OS_ReadMonotonicTime, OUT, R0, &timenow, END);
        if (timenow >= timetostop)
            return TRUE;
    }

    return FALSE;
}


/*
 * Called to clear up after a scan has been interrupted prematurely by the
 *  user.
 */

static void scan_tidy (void)
{
    if (rootdir == NULL)
        error_box (error_lookup ("BadTidy"));

    check_free ((void **)&rootdir);
    check_free ((void **)&range);
    check_free ((void **)&filecount);
    check_free ((void **)&filesize);
    check_free ((void **)&dircount);
    check_free ((void **)&dirsize);
    check_free ((void **)&savebuff);

    while (dirbuff != NULL)
    {
        void *buff = (void *) dirbuff;

        dirbuff = dirbuff->prev;
        check_free (&buff);
    }

    return;
}



/*****************************  Scan dbox routine **************************/


/*
 * Convert unsigned integer to string.
 *
 */

static char * num (unsigned int n)
{
    static char s[20];

    sprintf(s, "%u", n);
    return s;
}


/*
 * Called to create the dbox in which the on-going state of the scan will be
 *  displayed.
 */

static void scan_create_dbox (void)
{
    EE ( toolbox_create_object (0, "Scan", &scan_dbox) );

    /* increase vertical size of window */
    {
        BBox extent;

        EE ( window_get_extent (0, scan_dbox, &extent) );
        extent.ymin -= (numranges - 1) * SCAN_SPACING;
        full_ymin = extent.ymin;
        EE ( window_set_extent (0, scan_dbox, &extent) );
    }

    /* move and add icons as necessary */
    {
        ObjectTemplateHeader *template;

        /* locate the dialogue box template */
        EE ( toolbox_template_lookup (0, "Scan", (void **) &template) );

        /* determine extent of dbox when not displaying full info */
        {
            Gadget *g;
            int size;

            EE ( window_extract_gadget_info
                    (0,
                     template,
                     ID_Scan_Current,
                     (void **) &g,
                     &size) );
            small_ymin = g->hdr.box.ymin - 12;
        }

        /* add a line of gadgets for each range */
        {
            Gadget *temp, *limit, *filecount, *dircount;
            int size;
            int i;

            /* extract gadget info from template to make gadget prototypes */
            EE ( window_extract_gadget_info
                    (0,
                     template,
                     ID_Scan_Limit,
                     (void **) &temp,
                     &size) );

            EE ( check_alloc ((void **) &limit, size) );
            memcpy (limit, temp, size);

            EE ( window_extract_gadget_info
                    (0,
                     template,
                     ID_Scan_FileCount,
                     (void **) &temp,
                     &size) );

            EE ( check_alloc ((void **) &filecount, size) );
            memcpy (filecount, temp, size);

            EE ( window_extract_gadget_info
                    (0,
                     template,
                     ID_Scan_DirCount,
                     (void **) &temp,
                     &size) );

            EE ( check_alloc ((void **) &dircount, size) );
            memcpy (dircount, temp, size);

            /* add new lines to scan_dbox */
            for (i = 1; i < numranges; i ++)
            {
                limit->hdr.box.ymin -= SCAN_SPACING;
                limit->hdr.box.ymax -= SCAN_SPACING;
                limit->hdr.component_id = ID_Scan_Limit + 3*i;
                EE ( window_add_gadget (0, scan_dbox, limit, NULL) );

                filecount->hdr.box.ymin -= SCAN_SPACING;
                filecount->hdr.box.ymax -= SCAN_SPACING;
                filecount->hdr.component_id = ID_Scan_FileCount + 3*i;
                EE ( window_add_gadget (0, scan_dbox, filecount, NULL) );

                dircount->hdr.box.ymin -= SCAN_SPACING;
                dircount->hdr.box.ymax -= SCAN_SPACING;
                dircount->hdr.component_id = ID_Scan_DirCount + 3*i;
                EE ( window_add_gadget (0, scan_dbox, dircount, NULL ) );
            }
        }
    }

    /* initialise the icons */
    {
        int i;
        char s[20];

        displayfield_set_value (0, scan_dbox, ID_Scan_Root, rootdir);
        for (i = 0; i < numranges; i++)
        {
            sprintf (s, "%u", range[i]);
            EE ( displayfield_set_value (0, scan_dbox,
                                         ID_Scan_Limit + 3*i, s) );
        }

        scan_display_position ();
        scan_display_state ();
    }

    /* display the dbox */
    toolbox_show_object (0, scan_dbox, 0, 0, 0, 0);

    /* note no overflow initiallly */
    hasoverflowed = FALSE;

    return;
}


/*
 * Just displays position
 */

static void scan_display_position (void)
{
    displayfield_set_value (0, scan_dbox, ID_Scan_Current, currdir);
    return;
}


/*
 * Called to update the contents of the scan dbox.
 */

static void scan_display_state (void)
{
    int i;
    unsigned int totfiles, totdirs;
    unsigned int totfilesize, totdirsize;
    unsigned int maxfiles, maxdirs;
    unsigned int maxfilesize, maxdirsize;

    /* calculate maxima and totals */
    totfiles = 0;
    totdirs = 0;
    totfilesize = 0;
    totdirsize = 0;
    maxfiles = 0;
    maxdirs = 0;
    maxfilesize = 0;
    maxdirsize = 0;
    for (i = 0; i < numranges; i++)
    {
        if (filecount[i] > maxfiles)
            maxfiles = filecount[i];
        totfiles += filecount[i];
        if (dircount[i] > maxdirs)
            maxdirs = dircount[i];
        totdirs += dircount[i];
        if (filesize[i] > maxfilesize)
            maxfilesize = filesize[i];
        totfilesize += filesize[i];
        if (dirsize[i] > maxdirsize)
            maxdirsize = dirsize[i];
        totdirsize += dirsize[i];
    }

    if (totfilesize > 0x7fffffff && !hasoverflowed)
    {
        error_box (error_lookup ("Overflow"));
        hasoverflowed = TRUE;
    }

    /* display totals */
    displayfield_set_value (0, scan_dbox, ID_Scan_NumFiles, num(totfiles));
    displayfield_set_value (0, scan_dbox, ID_Scan_NumDirs, num(totdirs));
    displayfield_set_value (0, scan_dbox,
                            ID_Scan_SizeFiles, num(totfilesize));
    displayfield_set_value (0, scan_dbox, ID_Scan_SizeDirs, num(totdirsize));

    /* numberranges are signed, so must halve values if overflow */
    if (hasoverflowed && display_format != D_PERCENT)
    {
        maxfilesize >>= 1;
        totfilesize >>= 1;
        maxdirsize >>= 1;
        totdirsize >>= 1;
    }

    /* display latest analysis */
    for (i = 0; i < numranges; i++)
    {
        double percent;
        unsigned int val, max;

        switch (display_format)
        {
        case D_ABS:
            switch (display_val)
            {
            case D_COUNTS:
                val = filecount[i];
                max = maxfiles;
                break;
            case D_BYTES:
                val = hasoverflowed ? filesize[i] >> 1 : filesize[i];
                max = maxfilesize;
                break;
            }
            if (max == 0)
                max = 1;
            numberrange_set_bounds (14,    /* upper, step, precision */
                                    scan_dbox, ID_Scan_FileCount + 3*i,
                                    0, max, 1, 0);
            numberrange_set_value (0, scan_dbox,
                                   ID_Scan_FileCount + 3*i, val);

            switch (display_val)
            {
            case D_COUNTS:
                val = dircount[i];
                max = maxdirs;
                break;
            case D_BYTES:
                val = hasoverflowed ? dirsize[i] >> 1 : dirsize[i];
                max = maxdirsize;
                break;
            }
            if (max == 0)
                max = 1;
            numberrange_set_bounds (14,    /* upper, step, precision */
                                    scan_dbox, ID_Scan_DirCount + 3*i,
                                    0, max, 1, 0);
            numberrange_set_value (0, scan_dbox,
                                   ID_Scan_DirCount + 3*i, val);
            break;

        case D_PERCENT:
            percent = 0.0;
            switch (display_val)
            {
            case D_COUNTS:
                if (totfiles != 0)
                    percent = ((double) filecount[i] * 1000.0)/
                              (double) totfiles;
                    break;
            case D_BYTES:
                if (totfilesize != 0)
                    percent = ((double) filesize[i] * 1000.0)/
                              (double) totfilesize;
                    break;
            }
            numberrange_set_value (0, scan_dbox,
                                   ID_Scan_FileCount + 3*i,
                                   (int) (percent + 0.5));

            percent = 0.0;
            switch (display_val)
            {
            case D_COUNTS:
                if (totdirs != 0)
                    percent = ((double) dircount[i] * 1000.0)/
                              (double) totdirs;
                    break;
            case D_BYTES:
                if (totdirsize != 0)
                    percent = ((double) dirsize[i] * 1000.0)/
                              (double) totdirsize;
                    break;
            }
            numberrange_set_value (0, scan_dbox,
                                   ID_Scan_DirCount + 3*i,
                                   (int) (percent + 0.5));
            break;
        }
    }

    return;
}


/*
 * Called to destroy the scan dbox when it is no longer needed.
 */

static void scan_destroy_dbox (void)
{
    toolbox_delete_object (0, scan_dbox);

    return;
}


/*
 * Called to save the results of the scan in the save buffer.
 */

static int scan_save_results (void)
{
    char *s = savebuff;
    char sep = savetype == SAVE_TEXT ? ' ' : ',';
    int i;
    int size;

    /* construct header */
    if (savetype == SAVE_TEXT)
    {
        sprintf (s, "File and directory size analysis for %s\n\n", rootdir);
        s += strlen (s);
    }
    sprintf (s, "     lower%c     upper%cfilecount%c  filesize%c dircount%c   dirsize\n\n", sep, sep, sep, sep, sep);
    s += strlen (s);

    /* output body lines */
    for (i = 0; i < numranges; i++)
    {
        sprintf (s, "%10u%c%10u%c%9u%c%10u%c%9u%c%10u\n",
                 i == 0 ? 0 : range[i-1] + 1, sep, range[i], sep,
                 filecount[i], sep, filesize[i], sep,
                 dircount[i], sep, dirsize[i]);
        s += strlen (s);
    }

    size = s - savebuff;
    if (size > savebuffsize)
        error_exit (error_lookup ("SaveSpace"));

    return size;
}



/*************************  Routines to handle events **********************/


void scan_start (char *root, char *ranges)
{
    if (scan_in_progress)
        error_box (error_lookup ("Scanning"));
    else
    {
        swi (Hourglass_On, END);
        if (scan_init (root, ranges))
        {
            scan_create_dbox ();
            scan_display_state ();
            scan_in_progress = TRUE;
            scan_paused = FALSE;
            scan_complete = FALSE;
            enable_nulls ();
        }
        swi (Hourglass_Off, END);
    }

    return;
}


Bool scan_pauseresume
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    scan_paused = !scan_paused;

    if (scan_paused)
        disable_nulls ();
    else
        enable_nulls ();

    menu_set_tick (0, idblock->self_id, ID_Scanmenu_Pause, scan_paused);

    return TRUE;
}


Bool scan_cancel
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (idblock);
    IGNORE (handle);

    swi (Hourglass_On, END);
    scan_tidy ();
    scan_destroy_dbox ();
    if (!scan_paused)
        disable_nulls ();
    scan_in_progress = FALSE;
    swi (Hourglass_Off, END);

    return TRUE;
}


Bool scan_save
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (idblock);
    IGNORE (handle);

    swi (Hourglass_On, END);
    scan_save_results ();
    swi (Hourglass_Off, END);

    return TRUE;
}


Bool scan_null
(
    int code,
    WimpPollBlock *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (idblock);
    IGNORE (handle);

    swi (Hourglass_On, END);
    if (!scan_continue ())
    {
        disable_nulls ();
        scan_paused = TRUE;
        scan_complete = TRUE;
        scan_display_position ();
        scan_display_state ();
    }
    else
    {
        scan_display_position ();
        if (update_display)
            scan_display_state ();
    }
    swi (Hourglass_Off, END);

    return TRUE;
}


Bool scan_toggle_display
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (idblock);
    IGNORE (handle);

    update_display = !update_display;
    menu_set_tick (0, idblock->self_id, ID_Scanmenu_Display, update_display);

    /* toggle size of scan_dbox as well */
    {
        BBox extent;

        EE ( window_get_extent (0, scan_dbox, &extent) );
        extent.ymin = (extent.ymin == small_ymin) ? full_ymin : small_ymin;
        EE ( window_set_extent (0, scan_dbox, &extent) );

        EE ( toolbox_show_object (0, scan_dbox, 0, 0, 0, 0) );
    }

    /* and update the display immediately if necessary */
    if (update_display)
        scan_display_state ();

    return TRUE;
}


Bool scan_init_menu
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    menu_set_tick (0, idblock->self_id, ID_Scanmenu_Display, update_display);
    menu_set_tick (0, idblock->self_id, ID_Scanmenu_Pause, scan_paused);
    menu_set_fade (0, idblock->self_id, ID_Scanmenu_Pause, scan_complete);
    menu_set_fade (0, idblock->self_id, ID_Scanmenu_Save, !scan_complete);

    return TRUE;
}


Bool scan_fill_in_timeslice
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    numberrange_set_value (0, idblock->self_id,
                           ID_Centisecs_Val, time_slice);

    return TRUE;
}


Bool scan_set_timeslice
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    numberrange_get_value (0, idblock->self_id,
                           ID_Centisecs_Val, &time_slice);

    return TRUE;
}


Bool scan_fill_in_display_options
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    radiobutton_set_state (0, idblock->self_id,
                           (display_val == D_COUNTS) ? ID_DispOpts_Counts :
                                                       ID_DispOpts_Bytes,
                           1);
    radiobutton_set_state (0, idblock->self_id,
                           (display_format == D_ABS) ? ID_DispOpts_Abs :
                                                       ID_DispOpts_Percent,
                           1);

    return TRUE;
}


Bool scan_set_display_options
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    int res;

    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    radiobutton_get_state (0, idblock->self_id, ID_DispOpts_Counts,
                           &res, NULL);
    display_val = (res == 0) ? D_BYTES : D_COUNTS;
    radiobutton_get_state (0, idblock->self_id, ID_DispOpts_Abs,
                           &res, NULL);
    display_format = (res == 0) ? D_PERCENT : D_ABS;

    /* reset numberrange parameters for PERCENT mode */
    if (display_format == D_PERCENT)
    {
        int i;

        for (i = 0; i < numranges; i++)
        {
            numberrange_set_bounds (14,    /* upper, step, precision */
                                    scan_dbox, ID_Scan_FileCount + 3*i,
                                    0, 1000, 1, 1);
            numberrange_set_bounds (14,    /* upper, step, precision */
                                    scan_dbox, ID_Scan_DirCount + 3*i,
                                    0, 1000, 1, 1);
        }
    }

    if (scan_paused)
        scan_display_state ();

    return TRUE;
}


Bool scan_note_text_save
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (idblock);
    IGNORE (handle);

    savetype = SAVE_TEXT;

    return TRUE;
}


Bool scan_note_CSV_save
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    IGNORE (code);
    IGNORE (event);
    IGNORE (idblock);
    IGNORE (handle);

    savetype = SAVE_CSV;

    return TRUE;
}


Bool scan_prepare_save
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ObjectId saveas = idblock->self_id;
    int size;

    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    saveas_set_file_name (0, saveas,
                      savetype == SAVE_TEXT ? textsavename : csvsavename);
    saveas_set_file_type (0, saveas,
                      savetype == SAVE_TEXT ? 0xfff : 0xdfe);

    size = scan_save_results ();
    saveas_set_data_address (0, saveas,
                             (void *) savebuff, size,
                             NULL, 0);

    return TRUE;
}


Bool scan_save_over
(
    int code,
    ToolboxEvent *event,
    IdBlock *idblock,
    void *handle
)
{
    ObjectId saveas = idblock->self_id;

    IGNORE (code);
    IGNORE (event);
    IGNORE (handle);

    saveas_get_file_name (0, saveas,
                          savetype == SAVE_TEXT ? textsavename : csvsavename,
                          MAX_PATH_NAME + 1,
                          NULL);

    return TRUE;
}
