/*-*-C-*-
 * Generic object processing for Misc CSE
 */

#include "resed.h"
#include "main.h"

#include "swicall.h"
#include "wimp.h"
#include "resformat.h"
#include "newmsgs.h"

#include "dbox.h"
#include "interactor.h"
#include "menu.h"
#include "registry.h"

#include "format.h"
#include "relocate.h"
#include "objedit.h"

#include "colours.h"
#include "objdefs.h"
#include "object.h"
#include "gui.h"
#include "icondefs.h"
#include "protocol.h"



/* For the popup menu to select a licence type for ProgInfo objects */

#define  LICENCE_MENU_SIZE  6     /* number of menu entries */
static MenuPtr licencemenu = NULL;


/* For the popup menu to select a position for Iconbar objects */

#define  POSITION_MENU_SIZE  8
static MenuPtr positionmenu = NULL;


/* For the popup menu to select a filetype for FileInfo and Saveas objects */

static MenuPtr filetypemenu = NULL;
static int numfiletypes;                /* number of fixed entries in menu */
static WindowPtr filetypewin = NULL;

typedef struct
{
    WindowPtr dbox;
    int icon;
} FiletypeClosRec, *FiletypeClosPtr;

static FiletypeClosRec filetypeclosure;


/* For the popup menu to select initial fonts for FontMenu & FontDbox objects */

static MenuPtr fontmenu = NULL;
static char *buff1 = NULL;
static char *buff2 = NULL;
static int buff1size = -1;
static int buff2size = -1;

typedef struct
{
    WindowPtr dbox;
    int icon;
    Bool systemfont;
} FontClosRec, *FontClosPtr;

static FontClosRec fontclosure;


/* For the ColourPicker to select initial colour for ColourDbox objects */

static ColourPickerBlockRec cpblock;
static int cphandle;
static int cpwinhandle;

typedef struct
{
    WindowPtr dbox;
    int icon;
} ColourPickerClosRec, *ColourPickerClosPtr;

static ColourPickerClosRec colourpickerclosure;



/*
 * Returns the address of the definition of objects of class 'class'.
 * If no such definition exists, a NULL pointer is returned.
 */

ObjectDefPtr object_find_def (int class)
{
    ObjectDefPtr def = objectdefs;

    while (def->class != -1)
    {
        if (def->class == class)
            return def;
        def++;
    }

    return NULL;
}


/*
 * Creates an object definition record for an unknown object.
 *
 * Returns address of record, or zero if there was insufficient memory.
 */

ObjectDefPtr object_create_def
(
    int class,
    int size,
    RelocationTablePtr reloctab
)
{
    int numrelocs = (reloctab == NULL) ? 0 : reloctab->numrelocations;
    ObjectDefPtr def = malloc (sizeof (ObjectDefRec));
    RelocationPtr reloc = (reloctab == NULL) ? 0 : reloctab->relocations;
    RefDefPtr refs;
    int i;

    if (def == NULL)
        return NULL;

    /* copy prototype unknown object definition */
    *def = unkobjectdef;

    /* allocate RefDefRec array, allowing for terminating entry */
    refs = (RefDefPtr) calloc (numrelocs + 1, sizeof (RefDefRec));
    if (refs == NULL)
        return NULL;

    /* initialise fields of body definition */
    def->class = class;
    def->body.size = size;
    def->body.refs = refs;

    /* fill in the refs array */
    for (i = 0; i < numrelocs; i++)
    {
        refs->offset = reloc[i].wordtorelocate;
        refs->emptyisnull = FALSE;

        switch (reloc[i].directive)
        {
        case RELOCATE_STRINGREFERENCE:
            refs->type = REF_STR;
            break;

        case RELOCATE_MSGREFERENCE:
            refs->type = REF_MSG;
            break;

        case RELOCATE_SPRITEAREAREFERENCE:
            refs->type = REF_SPRITE;
            break;

        case RELOCATE_OBJECTOFFSET:
            refs->type = REF_OFFSET;
            break;

        default:
            error_box (error_lookup ("BadUnkObjReloc", reloc[i].directive));
            return NULL;
        }

        refs++;
    }

    /* and terminate it */
    refs->type = REF_END;

    return def;
}


/*
 * Decide where to position a pop-up menu
 */

static void where_to_popup (
    PointPtr pos,     /* where to position the menu (output argument) */
    WindowPtr dbox,   /* handle for window containing pop-up menu icon */
    int i             /* handle for pop-up imenu icon */
)
{
    IconStateRec state;

    state.windowhandle = dbox->handle;
    state.iconhandle = i;

    swi (Wimp_GetIconState, R1, &state, END);

    pos->x = state.icon.bbox.maxx;
    pos->y = state.icon.bbox.maxy;
    wimp_convert_point (WorkToScreen, dbox, pos, pos);
    pos->x += 64;    /* because menu_post will subtract 64 */

    return;
}


/*
 * Creates an object record in object->body from the relocated object
 *  template 'template'.
 *
 * Returns the object's address, or NULL if no memory available.
 *
 * Copies are made of any strings.
 */

ObjectPtr object_load (ObjectPtr object, ObjectTemplatePtr template)
{
    ObjectDefPtr def = object->def;
    char *body = (char *) &object->body;

    /* straight copy of all fields */
    memcpy (body, (char *) template, def->body.size);

    /* take copies of any strings */
    {
        RefDefPtr refs = def->body.refs;

        while (refs->type != REF_END)
        {
            if (refs->type == REF_STR || refs->type == REF_MSG)
            {
                char **strloc = (char **) (body + refs->offset);

                if (refs->emptyisnull && **strloc == 0)
                    *strloc = NULL;
                else
                {
                    if (clonestring (strloc) != NULL)
                        return NULL;
                }
            }

            refs++;
        }
    }

    /* replace any length fields which are exactly the right size by -1 */
    {
        FieldDefPtr field = def->fields;

        while (field->type != FLD_END)
        {
            switch (field->type)
            {
            case FLD_MAND_ASS_STR:
                {
                    MandAssStrFldPtr f = &field->def->mandassstr;

                    gui_load_len_field (*((char **)(body + f->valoffset)),
                                            (int *)(body + f->lenoffset));
                }
                break;

            case FLD_OPT_ASS_STR:
                {
                    OptAssStrFldPtr f = &field->def->optassstr;

                    gui_load_len_field (*((char **)(body + f->valoffset)),
                                            (int *)(body + f->lenoffset));
                }
                break;

            case FLD_TITLE:
                {
                    TitleFldPtr f = &field->def->title;

                    gui_load_len_field (*((char **)(body + f->valoffset)),
                                            (int *)(body + f->lenoffset));
                }
                break;
            }

            field++;
        }
    }

    return object;
}


/*
 * The object record is re-assembled into the supplied template. Entries are
 * added to the strings, messages and relocation tables as necessary, and the
 * fields inside 'tip' are updated to reflect the new states of these tables.
 *
 * The address of the byte following the last one used in template is
 *  returned as result.
 */

char * object_save (
    TemplateInfoPtr tip,
    ObjectPtr object,
    ObjectTemplatePtr template
)
{
    ObjectDefPtr def = object->def;
    char *body = (char *) &object->body;
    char *templbody = (char *) template;

    /* straight copy of all fields */
    memcpy (templbody, body, def->body.size);

    /* relocate all relocatable fields in the body */
    {
        RefDefPtr ref = def->body.refs;

        while (ref->type != REF_END)
        {
            int reloctype = 
                (ref->type == REF_STR)    ? RELOCATE_STRINGREFERENCE      :
                (ref->type == REF_MSG)    ? RELOCATE_MSGREFERENCE         :
                (ref->type == REF_SPRITE) ? RELOCATE_SPRITEAREAREFERENCE  :
                                            RELOCATE_OBJECTOFFSET;

            relocate_make_ref (
                tip,
                reloctype,
                (int *) (templbody + ref->offset),
                *( (char **) (body + ref->offset) ));

            ref++;
        }
    }

    /* replace any "-1" length fields by the correct explicit value
       in the template */
    {
        FieldDefPtr field = def->fields;

        while (field->type != FLD_END)
        {
            switch (field->type)
            {
            case FLD_MAND_ASS_STR:
                {
                    MandAssStrFldPtr f = &field->def->mandassstr;

                    gui_save_len_field (*((char **)(body + f->valoffset)),
                                        (int *)(templbody + f->lenoffset));
                }
                break;

            case FLD_OPT_ASS_STR:
                {
                    OptAssStrFldPtr f = &field->def->optassstr;

                    gui_save_len_field (*((char **)(body + f->valoffset)),
                                        (int *)(templbody + f->lenoffset));
                }
                break;

            case FLD_TITLE:
                {
                    TitleFldPtr f = &field->def->title;

                    gui_save_len_field (*((char **)(body + f->valoffset)),
                                        (int *)(templbody + f->lenoffset));
                }
                break;
            }

            field++;
        }
    }

    return templbody + def->body.size;
}


/*
 * Sets the supplied arguments according to the space required to store
 * the object record 'object' as a template.
 *
 * Called from objedit_object_size(..).
 */

void object_size (
    ObjectPtr object,
    int *bodysize,
    int *stringsize,
    int *msgsize,
    int *numrelocs
)
{
    ObjectDefPtr def = object->def;
    RefDefPtr ref = def->body.refs;
    char *body = (char *) &object->body;

    *bodysize = def->body.size;
    *stringsize = 0;
    *msgsize = 0;
    *numrelocs = 0;

    while (ref->type != REF_END)
    {
        (*numrelocs)++;

        if (ref->type == REF_STR || ref->type == REF_MSG)
        {
            int size = 0;
            char *s = *( (char **)(body + ref->offset) );

            if (s != NULL)
                size = strlen (s) + 1;

            if (ref->type == REF_STR)
                *stringsize += size;
            else
                *msgsize += size;
        }

        ref++;
    }

    return;
}


/*
 * Any space malloc'd for fields within the object record is free'd.
 * Any dialogue box opened for the object is free'd.
 * And, finally, the object record itself is free'd.
 *
 * Called from objedit_close_object(..).
 */

void object_free (ObjectPtr object)

{
    /* deregister, delete and free any dbox associated with the object */
    if (object->dbox)
        object_close_dbox (object);

    /* free any strings in the object body */
    {
        char *body = (char *) &object->body;
        RefDefPtr refs = object->def->body.refs;

        while (refs->type != REF_END)
        {
            if (refs->type == REF_STR || refs->type == REF_MSG)
                free ( *( (char **) (body + refs->offset) ) );
            refs++;
        }
    }

    /* free the object definition if it's an unknown object */
    if (object->def->templatename == unkobjectdef.templatename)
    {
        free (object->def->body.refs);
        free (object->def);
    }

    /* and finally free the entire object record itself */
    free (object);

    return;
}


/*
 * Initialises the icons in the properties dbox for the given object.
 *
 * Called from:
 *   object_open_dbox(..) - after the properties dbox has been created and
 *    opened.
 *   object_dbox_mouse_click(..) - after the user has clicked ADJ on the
 *    Cancel or OK action buttons.
 *   object_dbox_key_pressed(..) - after the user has pressed SHIFT-RETURN.
 */

error * object_init_dbox (ObjectPtr object)
{
    char *body = (char *) &object->body;
    WindowPtr dbox = object->dbox;
    ObjectDefPtr def = object->def;
    FieldDefPtr field = def->fields;

    /* write object's name (and class if unknown) into the dbox' title bar */
    {
        char buf[256];
        char *title = dbox_gettitle (def->proto);

        if (object->def->templatename == unkobjectdef.templatename)
            sprintf (buf, title, object->def->class, object->name);
        else
            sprintf (buf, title, object->name);
        ER ( dbox_settitle (dbox, buf, TRUE) );
    }

    while (field->type != FLD_END)
    {
        switch (field->type)
        {
        case FLD_INTEGER:
            {
                IntegerFldPtr f = &field->def->integer;

                (f->displayashex ? dbox_sethex : dbox_setint)
                     ( dbox, f->valicon, *((int *)(body + f->offset)) );
            }
            break;

        case FLD_MAND_ASS_STR:
            {
                MandAssStrFldPtr f = &field->def->mandassstr;

                gui_put_len_str (dbox,
                                 f->valicon, f->lenicon,
                                 *((char **)(body + f->valoffset)),
                                   *((int *)(body + f->lenoffset)) );
            }
            break;

        case FLD_OPT_ASS_STR:
            {
                OptAssStrFldPtr f = &field->def->optassstr;

                gui_put_len_opt_str (dbox,
                                     f->opticon, f->valicon, f->lenicon,
                                     *((char **)(body + f->valoffset)),
                                       *((int *)(body + f->lenoffset)) );
            }
            break;

        case FLD_TITLE:
            {
                TitleFldPtr f = &field->def->title;

                gui_put_len_opt_str (dbox,
                                     f->othericon, f->valicon, f->lenicon,
                                     *((char **)(body + f->valoffset)),
                                       *((int *)(body + f->lenoffset)) );
                {
                    Bool dflt = !dbox_getbutton (dbox, f->othericon);

                    dbox_setbutton (dbox, f->dflticon, dflt);
                    dbox_shade (dbox, f->lenicon, dflt);
                    dbox_shade (dbox, f->upicon, dflt);
                    dbox_shade (dbox, f->downicon, dflt);
                }                              
            }
            break;

        case FLD_MAND_CONST_STR:
            {
                MandConstStrFldPtr f = &field->def->mandconststr;

                gui_put_str (dbox,
                             f->icon,
                             *((char **)(body + f->offset)) );
            }
            break;

        case FLD_OPT_CONST_STR:
            {
                OptConstStrFldPtr f = &field->def->optconststr;

                gui_put_opt_str (dbox,
                                 f->opticon, f->valicon,
                                 *((char **)(body + f->offset)) );
            }
            break;

        case FLD_FLAG:
            {
                FlagFldPtr f = &field->def->flag;
                Bool flagmeanson = f->flagmeanson;
                unsigned int flag = *((unsigned *)body) & f->mask;

                dbox_setbutton (dbox, f->opticon,
                                (flag != 0) ? flagmeanson : !flagmeanson);
            }
            break;

        case FLD_OPT_EVENT:
            {
                OptEventFldPtr f = &field->def->optevent;

                gui_put_opt_event ( dbox,
                                    f->dflticon, f->othericon, f->noneicon,
                                    f->valicon,
                                    *((int *)(body + f->offset)),
                                    (*((unsigned *)body) & f->mask) == 0 );
            }
            break;

        case FLD_FILETYPE:
            {
                FiletypeFldPtr f = &field->def->filetype;

                gui_put_filetype ( dbox,
                                   f->valicon,
                                   *((int *)(body + f->offset)) );
            }
            break;

        default:
            return error_lookup ("UnkFldType", field->type);
        }

        field++;
    }

    /* any special processing required? */
    if (def->specialinit != NULL)
        ER ( (def->specialinit) (object) );

    return NULL;
}


/*
 * Result is TRUE iff 'old' is identical to 'new'; both are object templates.
 * 
 * All space occupied by 'old' is released.
 */

static Bool compare_bodies (
    ObjectDefPtr def,         /* defines the format of the templates */
    char *old,
    char *new
)
{
    Bool ok = TRUE;

    /* compare and free all string fields */
    {
        RefDefPtr refs = def->body.refs;

        while (refs->type != REF_END)
        {
            if (refs->type == REF_STR || refs->type == REF_MSG)
            {
                char **oldstr = (char **) (old + refs->offset);
                char **newstr = (char **) (new + refs->offset);

                if (!equalstrings (*oldstr, *newstr))
                    ok = FALSE;
                free (*oldstr);
                *oldstr = *newstr;  /* makes comparison below safe */
            }

            refs++;
        }
    }

    /* compare the rest */
    if (memcmp ((char *) old, (char *) new, def->body.size) != 0)
        ok = FALSE;

    /* and free it */
    free (old);

    return ok;
}


/*
 * Create a new object template record that is a copy of the given one;
 *  return NULL if insufficient memory is available.
 */

static char * copy_body (
    ObjectDefPtr def,           /* defines the format of the template */
    char *object                /* the template itself */
)
{
    char *new;

    /* Allocate a new object template structure */
    new = (char *) malloc (def->body.size);
    if (new == NULL)
        return NULL;

    /* straight copy of all fields */
    memcpy (new, object, def->body.size);

    /* take copies of any strings */
    {
        RefDefPtr refs = def->body.refs;

        while (refs->type != REF_END)
        {
            if (refs->type == REF_STR || refs->type == REF_MSG)
            {
                if (clonestring ((char **) (new + refs->offset)) != NULL)
                    return NULL;
            }
            refs++;
        }
    }

    return new;
}


/*
 * Updates the given object according to the values in the icons in its
 *  properties dbox.
 *
 * 'altered' is set TRUE iff any of the object's properties have changed.
 *
 * Called from:
 *   object_dbox_mouse_click(..) - after the user has clicked on the OK
 *    action button.
 *   object_dbox_key_pressed(..) - after the user has pressed SHIFT-RETURN.
 */

static error * object_apply_dbox (ObjectPtr object, Bool *altered)
{
    char *body = (char *) &object->body;
    WindowPtr dbox = object->dbox;
    ObjectDefPtr def = object->def;
    FieldDefPtr field = def->fields;
    char *copy;

    /* first take a copy: we compare later to see if there is any change */
    copy = copy_body (def, body);
    if (copy == NULL)
        return error_lookup ("NoMem");


    /* any special processing required? */
    if (def->specialapply != NULL)
    {
        error *err = (def->specialapply) (object);

        if (err != NULL)
        {
            compare_bodies (def, copy, body);  /* just to free the copy */
            return err;
        }
    }

    while (field->type != FLD_END)
    {
        switch (field->type)
        {
        case FLD_INTEGER:
            {
                IntegerFldPtr f = &field->def->integer;

                *((int *)(body + f->offset)) = 
                    dbox_getint (dbox, f->valicon);
            }
            break;

        case FLD_MAND_ASS_STR:
            {
                MandAssStrFldPtr f = &field->def->mandassstr;

                gui_get_len_str (dbox,
                                 f->valicon, f->lenicon,
                                 (char **)(body + f->valoffset),
                                   (int *)(body + f->lenoffset) );
            }
            break;

        case FLD_OPT_ASS_STR:
            {
                OptAssStrFldPtr f = &field->def->optassstr;

                gui_get_len_opt_str (dbox,
                                     f->opticon, f->valicon, f->lenicon,
                                     (char **)(body + f->valoffset),
                                       (int *)(body + f->lenoffset) );
            }
            break;

        case FLD_TITLE:
            {
                TitleFldPtr f = &field->def->title;

                gui_get_len_opt_str (dbox,
                                     f->othericon, f->valicon, f->lenicon,
                                     (char **)(body + f->valoffset),
                                       (int *)(body + f->lenoffset) );
            }
            break;

        case FLD_MAND_CONST_STR:
            {
                MandConstStrFldPtr f = &field->def->mandconststr;

                gui_get_str (dbox,
                             f->icon,
                             (char **)(body + f->offset) );
            }
            break;

        case FLD_OPT_CONST_STR:
            {
                OptConstStrFldPtr f = &field->def->optconststr;

                gui_get_opt_str (dbox,
                                 f->opticon, f->valicon,
                                 (char **)(body + f->offset) );
            }
            break;

        case FLD_FLAG:
            {
                FlagFldPtr f = &field->def->flag;
                Bool flagmeanson = f->flagmeanson;
                Bool flag = dbox_getbutton (dbox, f->opticon);

                if (flagmeanson && flag || !flagmeanson && !flag)
                    *((unsigned *)body) |= f->mask;
                else
                    *((unsigned *)body) &= ~f->mask;
            }
            break;

        case FLD_OPT_EVENT:
            {
                OptEventFldPtr f = &field->def->optevent;

                gui_get_opt_event ( dbox,
                                    f->dflticon, f->noneicon,
                                    f->valicon,
                                    (int *)(body + f->offset),
                                    (unsigned *)body, f->mask );
            }
            break;

        case FLD_FILETYPE:
            {
                FiletypeFldPtr f = &field->def->filetype;

                gui_get_filetype ( dbox,
                                   f->valicon,
                                   (int *)(body + f->offset));
            }
            break;

        default:
            compare_bodies (def, copy, body);  /* just to free the copy */
            return error_lookup ("UnkFldType", field->type);
        }

        field++;
    }


    /* now see if any changes have actually been recorded */
    *altered = !compare_bodies (def, copy, body);

    return NULL;
}


/*
 * Create and open the object's dialogue box.
 *
 * If the corresponding dialogue box is already open, it is simply raised
 *  to the top of the window stack.
 *
 * Otherwise, a new dialogue box is created and opened.
 *
 * Any new dbox will be displayed at the same position as the most recent
 *  position of any other window created from the same template; this is
 *  achieved by copying co-ordinates to the template whenever an existing
 *  window is reopened - see object_reopen_dbox(..).
 *
 * Called from:
 *   objedit_load(..) - after a new object has been received from the shell.
 *   received_resed_object_load(..) in c.protocol - after the user has
 *   double-clicked on an icon in a document window in the shell that
 *   represents an object that is already known to us.
 *
 * The parameter 'show' determines whether the dbox is actually opened
 *  on the screen. This is only set FALSE when called from objedit_load(..)
 *  after force-loading an object which we do not already know about. Such
 *  an object will be grabbed back and deleted forthwith, so an irritating
 *  screen flicker is avoided by not opening the window.
 */

error * object_open_dbox (ObjectPtr object, Bool show)
{
    if (object->dbox == NULL)
    {
        ER ( wimp_copy_template (object->def->proto, &object->dbox,
                                 object->def->protosize) );
        ER ( swi (Wimp_CreateWindow,  R1, &object->dbox->visarea,
                        OUT,  R0, &object->dbox->handle,  END) );
        ER ( registry_register_window (object->dbox->handle,
                                       ObjectDbox, (void *) object) );

        /* create cleared bodycopy record for unknown object */
        if (object->def->templatename == unkobjectdef.templatename)
        {
            int size = object->def->body.size;
            char *bodycopy = NULL;

            if (size > 0)
            {
                bodycopy = calloc (size, 1);

                if (bodycopy == NULL)
                    return error_lookup ("NoMem");
            }

            object->bodycopy = bodycopy;
        }

        if (!show)
            return NULL;

        ER ( swi (Wimp_OpenWindow,  R1, object->dbox,  END) );
        ER ( object_init_dbox (object) );
    }
    else
    {
        object->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, object->dbox,  END) );
    }   

    return dbox_set_caret_to (object->dbox,
                              object->def->icons.firstwritable);
}


/*
 * Close and delete the object's properties dbox.
 *
 * Called from:
 *   close_window_request(..) in c.main - when the user clicks on the dbox's
 *    close icon [not at present].
 *   object_free(..) - which is called when all trace of the object is to be
 *    removed.
 */

error * object_close_dbox (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;

    /* de-register the window */
    ER ( registry_deregister_window (dbox->handle) );

    /* hide and delete the window */

    /*
     * This service call is fielded by BorderUtils 0.05 which fixes
     *  a bug associated with the deletion of windows which have pressed-in
     *  slabbed icons in wimps before 3.17
     */

    ER ( swi (OS_ServiceCall, R0, dbox->handle,
                              R1, 0x44ec5,
                              R2, taskhandle, END) );

    ER ( swi (Wimp_DeleteWindow,  R1, dbox,  END) );

    /* free the memory allocated to the window */
    free ((char *) dbox);

    /* and zap the "dbox exists" field */
    object->dbox = NULL;

    /* release space occupied by the bodycopy record for unknown objects */
    if (object->def->templatename == unkobjectdef.templatename)
    {
        char *bodycopy = object->bodycopy;
        RefDefPtr refs = object->def->body.refs;

        /* free space occupied by any strings */
        while (refs->type != REF_END)
        {
            if (refs->type == REF_STR || refs->type == REF_MSG)
                free ( *( (char **) (bodycopy + refs->offset) ) );
            refs++;
        }

        /* and now free the bodycopy record - if any - itself */
        free (bodycopy);
        object->bodycopy = NULL;
    }

    return NULL;
}


/*
 * Reopen the object's properties dbox.
 *
 * Copies coordinates of current position to the prototype;
 *  see object_open_dbox(..) above.
 *
 * Called from:
 *   open_window_request(..) in c.main - when an OPEN_REQUEST event is
 *    received from the Wimp (as user moves properties dbox or other windows
 *    over it etc.)
 */

error * object_reopen_dbox (WindowPtr win, ObjectPtr object)
{
    WindowPtr proto = object->def->proto;
    WindowPtr dbox = object->dbox;

    dbox->visarea = proto->visarea = win->visarea;
    dbox->scrolloffset = proto->scrolloffset = win->scrolloffset;
    dbox->behind = win->behind;

    return swi (Wimp_OpenWindow, R1, dbox, END);
}


/*
 * Process mouse clicks on the object's properties dbox.
 *
 * Called from:
 *   mouse_click(..) in c.main - when the user clicks inside the dbox.
 */

error * object_dbox_mouse_click (
    MouseClickPtr mouse,
    unsigned int modifiers,
    ObjectPtr object
)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    WindowPtr dbox = object->dbox;
    ObjectDefPtr def = object->def;
    ClickDefPtr click = def->clicks;
    Bool adjustclick = buttons == MB_CLICK(MB_ADJUST);
    Bool menuclick = buttons == MB_CLICK(MB_MENU);
    error *err = NULL;

    /* only interested in clicks */
    if (buttons >= 16)
        return NULL;

    /* deal with any clicks on icons common to all object dboxes */
    if (!menuclick)
    {
        if (icon == def->icons.ok)                 /* OK action button */
        {
            Bool altered;

            err = object_apply_dbox (object, &altered);
            if (err != NULL)
                goto done;

            if (altered)
            {
                object->pendingclose = !adjustclick;
                err = protocol_send_resed_object_sending (object);
                goto done;
            }
        }

        if (icon == def->icons.cancel ||
            icon == def->icons.ok)                 /* Cancel action button */
        {
            if (adjustclick)
            {
                err = object_init_dbox (object);
                goto done;
            }
            else
                return objedit_close_object (object, TRUE);
        }
    }

    while (click->action != ACT_END)
    {
        if (click->icon == icon)
        {
            if (menuclick)                  /* menu clicks */
                switch (click->action)
                {
                case ACT_FADE:
                case ACT_UNFADE:
                case ACT_TOGGLEFADE:
                case ACT_RADIO:
                case ACT_ADJUST:
                case ACT_TITLE:
                    break;

                case ACT_SPECIAL:
                    {
                        SpecialClickPtr a = &click->params->special;

                        ER ( (a->f) (object, icon, mouse) );
                    }
                    break;
                    
                default:
                    return error_lookup ("UnkActType", click->action);
                }
            else                            /* adjust or select clicks */
                switch (click->action)
                {
                case ACT_FADE:
                    {
                        FadeClickPtr a = &click->params->fade;

                        dbox_shade (dbox, a->valicon, TRUE);
                    }
                    break;

                case ACT_UNFADE:
                    {
                        UnFadeClickPtr a = &click->params->unfade;

                        dbox_shade (dbox, a->valicon, FALSE);

                        /* place caret in unfaded icon - if it's writable */
                        if (IF_GET_FIELD (TYPE,
                                    dbox->icons[a->valicon].flags) == 15)
                            dbox_place_caret (dbox, a->valicon);
                    }
                    break;

                case ACT_TOGGLEFADE:
                    {
                        ToggleFadeClickPtr a = &click->params->togglefade;

                        GUI_TOGGLE_FADE (dbox, a->valicon);

                        /* place caret in icon - if it's unfaded writable */
                        if ( (dbox_getflags (dbox, a->valicon) &
                          ((IF_TYPE_MASK << IF_TYPE_SHFT) | IF_SHADED)) ==
                                     (15 << IF_TYPE_SHFT) )
                            dbox_place_caret (dbox, a->valicon);
                    }
                    break;

                case ACT_RADIO:
                    /* ensure that ADJ-click on a radio button does not
                        turn it off */
                    if (adjustclick && !dbox_getbutton (dbox, icon))
                        dbox_setbutton (dbox, icon, TRUE);
                    break;

                case ACT_ADJUST:
                    {
                        AdjustClickPtr a = &click->params->adjust;
                        Bool increase = a->increase;
                        int delta = 
                            (modifiers & MODIFIER_SHIFT) ? a->shiftincrement
                                                         : a->increment;

                        if (increase && adjustclick ||
                                 !increase && !adjustclick)
                            delta = -delta;

                        err = gui_adjust_len (dbox, a->lenicon,
                                                    a->valicon, delta, 0);
                    }
                    break;

                case ACT_TITLE:
                    {
                        TitleClickPtr a = &click->params->title;

                        if (icon == a->dflticon)
                        {
                            dbox_shade (dbox, a->valicon, TRUE);
                            dbox_shade (dbox, a->lenicon, TRUE);
                            dbox_shade (dbox, a->upicon, TRUE);
                            dbox_shade (dbox, a->downicon, TRUE);
                            if (adjustclick && !dbox_getbutton (dbox, icon))
                                dbox_setbutton (dbox, icon, TRUE);
                        }
                        else if (icon == a->othericon)
                        {
                            dbox_shade (dbox, a->valicon, FALSE);
                            dbox_shade (dbox, a->lenicon, FALSE);
                            dbox_shade (dbox, a->upicon, FALSE);
                            dbox_shade (dbox, a->downicon, FALSE);
                            if (adjustclick && !dbox_getbutton (dbox, icon))
                                dbox_setbutton (dbox, icon, TRUE);
                            dbox_place_caret (dbox, a->valicon);
                        }
                        else if (icon == a->upicon || icon == a->downicon)
                        {
                            Bool increase = (icon == a->upicon);
                            int delta =
                                (modifiers & MODIFIER_SHIFT) ? 10 : 1;

                            if (increase && adjustclick ||
                                     !increase && !adjustclick)
                                delta = -delta;
                            
                            err = gui_adjust_len (dbox, a->lenicon,
                                                       a->valicon, delta, 0);
                        }
                    }
                    break;

                case ACT_SPECIAL:
                    {
                        SpecialClickPtr a = &click->params->special;

                        ER ( (a->f) (object, icon, mouse) );
                    }
                    break;

                default:
                    return error_lookup ("UnkActType", click->action);
                }
         }

        click++;
    }

  done:
    /* ensure dbox has caret, and that it is not in a faded icon */
    dbox_set_caret_to (dbox, -1);

    return err;
}


/*
 * Process key presses for the object's properties dbox.
 *
 * Called from:
 *   key_pressed(..) in c.main - when a key is pressed and the dbox has input
 *    focus.
 */

error * object_dbox_key_pressed (
    ObjectPtr object,
    KeyPressPtr key,
    Bool *consumed
)
{
    error *err = NULL;
    Bool altered;

    switch (key->code)
    {
    case 13:                     /* RETURN */
        *consumed = TRUE;
        interactor_cancel(); /* in case any pop-up menu is still on-screen */

        err = object_apply_dbox (object, &altered);
        if (err != NULL)
            break;

        /* send altered object back to shell */
        if (altered)
        {
            object->pendingclose =
                            (wimp_read_modifiers() & MODIFIER_SHIFT) == 0;
            err = protocol_send_resed_object_sending (object);
            break;
        }

    case 0x1b:                   /* ESCAPE */
        *consumed = TRUE;
        if ((wimp_read_modifiers() & MODIFIER_SHIFT) != 0)
        {
            err = object_init_dbox (object);
            break;
        }
        else
            return objedit_close_object (object, TRUE);
    }

    /* ensure dbox has caret, and that it is not in a faded icon */
    dbox_set_caret_to (object->dbox, -1);

    return err;
}


/*
 * Update the object name in the dbox title bar.
 *
 * Called from:
 *   objedit_rename_object(..) - after a new name has been given to the
 *    object by the shell.
 */

error * object_dbox_update_window_name (
    ObjectPtr object,
    char *name
)
{
    char buf[256];
    WindowPtr dbox = object->dbox;
    ObjectDefPtr def = object->def;
    char *title = dbox_gettitle (def->proto);

    if (object->def->templatename == unkobjectdef.templatename)
        sprintf (buf, title, object->def->class, object->name);
    else
        sprintf (buf, title, object->name);
    ER ( dbox_settitle (dbox, buf, TRUE) );

    return NULL;
}


/*
 * An object has been dropped onto an appropriate icon in the dialogue box
 *  for 'object'; this function must execute the drop action if appropriate.
 */

error * object_object_drop (
    ObjectPtr object,         /* the object whose dbox was dropped upon */
    DropDetailsPtr dropdef,   /* defines the action to be taken */
    ObjectClass class,        /* class of the dropped object */
    char *name                /* name of the dropped object  */
)
{
    /* first check that the class of object dropped is acceptable */
    if (dropdef->class == -1 || dropdef->class == class)
    {
        WindowPtr dbox = object->dbox;
        int numextra = 0;

        switch (dropdef->action)
        {
        case DROP_SET:
            {
                SetDropPtr d = &dropdef->params->set;

                dbox_setstring (dbox, d->valicon, name);
            }
            break;

        case DROP_SETOPT3:  numextra++;
        case DROP_SETOPT2:  numextra++;
        case DROP_SETOPT:
            {
                SetOptDropPtr d = &dropdef->params->setopt;
                int *extras = (int *)(d + 1);

                dbox_setstring (dbox, d->valicon, name);
                dbox_setbutton (dbox, d->opticon, TRUE);

                /* unshade other fields only if option icon is not faded */
                if ((dbox_getflags (dbox, d->opticon) & IF_SHADED) == 0)
                {
                    dbox_shade (dbox, d->valicon, FALSE);
                    while (numextra > 0)
                    {
                        dbox_shade (dbox, *extras, FALSE);
                        extras++;
                        numextra--;
                    }
                }
            }
            break;
        }

        return NULL;
    }
    else
        return error_lookup ("BadDrop");
}


/*
 * Special processing for colour menu dialogues - initialisation.
 *
 * Sorts out the faded status and contents of the "initial colour" option
 *  icon and value icon.
 */

error * object_cm_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    int colour = object->body.colourmenu.colour;
    Bool noneallowed = 
        (object->body.colourmenu.flags & COLOURMENU_NONEENTRY) != 0;
    Bool hascolour;

    if (colour < -1 || colour > 16 || (colour == 16 && !noneallowed))
    {
        error_box (error_lookup ("UnkColour", colour));
        colour = 0;
    }

    hascolour = !(colour == -1);

    dbox_setbutton (dbox, I_COLOURMENU_HASCOLOUR, hascolour);
    dbox_shade (dbox, I_COLOURMENU_COLOUR, !hascolour);
    dbox_shade (dbox, I_COLOURMENU_COLOUR_POPUP, !hascolour);

    if (!hascolour)
        colour = 0;
    gui_put_colour (dbox, I_COLOURMENU_COLOUR, colour);

    return NULL;
}


/*
 * Special processing for colour menu dialogues - finalisation.
 *
 * Retrieves the initial colour value from the option and value icons.
 */

error * object_cm_apply (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    int colour;

    if (dbox_getbutton (dbox, I_COLOURMENU_HASCOLOUR))
    {
        colour = gui_get_colour (dbox, I_COLOURMENU_COLOUR);
        if (colour == 255)
            colour = 16;
    }
    else
        colour = -1;

    object->body.colourmenu.colour = colour;

    return NULL;
}


/*
 * Special processing for colour menu dialogues:
 *  click on 'Include "None" entry' icon.
 *
 * If the initial colour is set to "None", and the icon is now off, then
 *  the initial colour is reset to 0.
 */

error * object_cm_includenone (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    int buttons = mouse->buttons;

    if (buttons != MB_CLICK(MB_MENU))   /* ignore menu clicks */
    {
        WindowPtr dbox = object->dbox;
        Bool noneallowed = dbox_getbutton (dbox, I_COLOURMENU_INCLUDENONE);

        if (!noneallowed &&
                      (gui_get_colour (dbox, I_COLOURMENU_COLOUR) == 255))
            gui_put_colour (dbox, I_COLOURMENU_COLOUR, 0);
    }

    return NULL;
}


/*
 * Special processing for colour menu dialogues:
 *  click on initial colour writable or associated pop-up menu icon.
 *
 * Uses a colour menu to choose an initial colour.
 */

error * object_cm_colour (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    WindowPtr dbox = object->dbox;
    int buttons = mouse->buttons;

    if (buttons != MB_CLICK(MB_ADJUST))
        colours_choose (dbox,
                        I_COLOURMENU_COLOUR,
                        dbox_getbutton (dbox, I_COLOURMENU_INCLUDENONE),
                        I_COLOURMENU_COLOUR_POPUP);

    return NULL;
}


/*
 * Special processing for colour dbox dialogues - initialisation.
 *
 * Sorts out faded status of 'Select "None" button' option icon, and sets
 *  content of "Initial colour" display field.
 */

error * object_cd_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    Bool hasnone =
        (object->body.colourdbox.flags & COLOURDBOX_INCLUDENONEBUTTON) != 0;

    dbox_shade (dbox, I_COLOURDBOX_SELECTNONE, !hasnone);

    gui_put_rgb_colour (dbox, I_COLOURDBOX_COLOUR,
                              object->body.colourdbox.colour);

    return NULL;
}


/*
 * Special processing for colour dbox dialogues - finalisation.
 *
 * Retrieves content of "Initial colour" display field.
 */

error * object_cd_apply (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;

    gui_get_rgb_colour (dbox, I_COLOURDBOX_COLOUR,
                              &object->body.colourdbox.colour);

    return NULL;
}


/*
 * Interactor for the colour picker.
 */

static error * colourpicker_interactor (
    unsigned int event,
    int *buf,
    void *closure,
    Bool *consumed)
{
    ColourPickerClosPtr colourpickerclosure = (ColourPickerClosPtr) closure;
    int icon = colourpickerclosure->icon;
    WindowPtr dbox = colourpickerclosure->dbox;

    if (buf == NULL)                     /* we are being asked to cancel */
        return NULL;

    switch (event)
    {
    case EV_KEY_PRESSED:
        {
            KeyPressPtr key = (KeyPressPtr) buf;

            /* if I don't do this, both the Colour Picker and ResEd
                will call Wimp_ProcessKey(..), and freeze the m/c ... */
            if (key->caret.windowhandle == cpwinhandle)
                *consumed = TRUE;
        }
        break;

    case EV_USER_MESSAGE:
    case EV_USER_MESSAGE_RECORDED:
        {
            MessageHeaderPtr hdr = (MessageHeaderPtr) buf;

            switch (hdr->messageid)
            {
                
            case MESSAGE_COLOUR_PICKER_COLOUR_CHOICE:
                {
                    MessageColourPickerColourChoicePtr mess =
                         (MessageColourPickerColourChoicePtr) buf;

                    if (mess->handle == cphandle)
                    {
                        *consumed = TRUE;
                        gui_put_rgb_colour (dbox, icon, mess->rgb);
                    }
                }
                break;
            }
        }
        break;
    }

    return NULL;
}



error * object_cd_colour_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    int buttons = mouse->buttons;
    PointRec popuppos;

    /* must be menu or select click */
    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    /* find out where the colour picker should be displayed */
    where_to_popup (&popuppos, object->dbox, I_COLOURDBOX_COLOUR_POPUP);

    /* fill in colour picker block */
    cpblock.flags = 0;                /* no "None" button */
    cpblock.title = NULL;             /* use default title */
    cpblock.x = popuppos.x - 64;      /* see menu_post(..) ... */
    cpblock.reserved1 = 0x80000000;   /* like the PRM says ... */
    cpblock.reserved2 = 0x7fffffff;
    cpblock.y = popuppos.y;
    cpblock.reserved3 = 0;
    cpblock.reserved4 = 0;
    gui_get_rgb_colour (object->dbox, I_COLOURDBOX_COLOUR, &cpblock.rgb);
    cpblock.extensionsize = 0;

    /* open the colour picker */
    ER ( swi (ColourPicker_OpenDialogue,
                                   R0, 1,   /* open transiently as a menu */
                                   R1, &cpblock,
                              OUT, R0, &cphandle,
                                   R1, &cpwinhandle,
                              END) );

    /* create the interactor */
    colourpickerclosure.dbox = object->dbox;
    colourpickerclosure.icon = I_COLOURDBOX_COLOUR;
    interactor_install (colourpicker_interactor,
                        (void *) &colourpickerclosure);

    return NULL;
}


/*
 * Called from below to retick filetype menu and reset filetype dbox and
 *  redisplay after an ADJ-click on an action button.
 */

static error * redisplay_filetype_stuff (WindowPtr dbox, int icon)
{
    char type[12];  /* longest filetype name is 11 chars ("application") */
    int i;

    /* note current position of filetype transient dbox */
    swi (Wimp_GetWindowState, R1, filetypewin, END);

    /* re-tick filetype menu */
    gui_get_filetype_string (dbox, icon, type);

    /* tick appropriate menu item */
    for (i = 0; i < numfiletypes; i++)
    {
        if (strcmp (type, menu_get_entry (filetypemenu, i) ) == 0)
        {
            menu_tick_menu (filetypemenu, i);
            break;
        }
    }
    if (i == numfiletypes)   /* must be another filetype */
        menu_tick_menu (filetypemenu, numfiletypes);

    /* recreate the menu */
    ER ( swi (Wimp_CreateMenu,  R1, filetypemenu,
                                R2, 100,  R3, 100,  END) );

    /* but recreating the menu  will have destroyed the
        submenu dbox, so reload and redisplay that */
    dbox_setstring (filetypewin, I_FILETYPE_TYPE, type);
    ER ( swi (Wimp_CreateSubMenu,  R1, filetypewin->handle,
                                   /* scalex allows for width of border! */
                                   R2, filetypewin->visarea.minx - scalex,
                                   R3, filetypewin->visarea.maxy,  END) );

    return NULL;
}


/*
 * Interactor for the filetype window.  Expects to be pushed on top of the
 * menu.c interactor, so needs to be careful about popping itself correctly.
 */

static error * filetype_interactor (
    unsigned int event,
    int *buf,
    void *closure,
    Bool *consumed)
{
    MouseClickPtr mouse = (MouseClickPtr) buf;
    WindowPtr win = (WindowPtr) buf;         /* only half there */
    KeyPressPtr key = (KeyPressPtr) buf;
    FiletypeClosPtr filetypeclosure = (FiletypeClosPtr) closure;
    int icon = filetypeclosure->icon;
    WindowPtr dbox = filetypeclosure->dbox;
    error *err = NULL;

    if (buf == NULL)                     /* we are being asked to cancel */
    {
        return NULL;
    }

    switch (event)
    {
    case EV_OPEN_WINDOW_REQUEST:
        if (win->handle == filetypewin->handle)
        {
            *consumed = TRUE;
            filetypewin->visarea = win->visarea;
            filetypewin->scrolloffset = win->scrolloffset;
            filetypewin->behind = win->behind;
            ER ( swi (Wimp_OpenWindow, R1, filetypewin, END) );
        }
        break;

    case EV_KEY_PRESSED:
        if (key->caret.windowhandle == filetypewin->handle &&
            key->code == 0x0d)
        {
            *consumed = TRUE;
            err = gui_put_filetype_from_user (dbox, icon,
                                 dbox_getstring (filetypewin, I_FILETYPE_TYPE));
            if (err)
                error_box (err);
            else
            {
                if ((wimp_read_modifiers () & MODIFIER_SHIFT) != 0)
                {
                    ER ( redisplay_filetype_stuff (dbox, icon) );
                }
                else
                    interactor_cancel ();
            }
        }
        break;

    case EV_MOUSE_CLICK:
        if (mouse->windowhandle == filetypewin->handle)
        {
            *consumed = TRUE;
            switch (mouse->iconhandle)
            {
            case I_FILETYPE_OK:
                if (mouse->buttons == MB_CLICK(MB_SELECT) ||
                    mouse->buttons == MB_CLICK(MB_ADJUST))
                {
                    err = gui_put_filetype_from_user (dbox, icon,
                                 dbox_getstring (filetypewin, I_FILETYPE_TYPE));
                    if (err)
                        error_box (err);
                }
                else
                    break;
            case I_FILETYPE_CANCELT:
                if (mouse->buttons == MB_CLICK(MB_SELECT))
                    interactor_cancel();
                else
                if (mouse->buttons == MB_CLICK(MB_ADJUST))
                    ER ( redisplay_filetype_stuff (dbox, icon) );
                break;
            }
        }
        break;
    }

    return NULL;
}



/*
 * Called from the menu warning for Other =>
 *
 * Open the Filetype dialogue box as a submenu at the appointed position
 */

static error * show_filetypewin (
    MenuPtr menu,
    MessageMenuWarningPtr warn,
    void *closure
)
{
    FiletypeClosPtr filetypeclosure = (FiletypeClosPtr) closure;
    int icon = filetypeclosure->icon;
    WindowPtr dbox = filetypeclosure->dbox;
    char type[12];  /* longest filetype name is 11 chars ("application") */

    /* initialise the writable according to the display field in the dbox */
    ER ( gui_get_filetype_string (dbox, icon, type) );
    dbox_setstring (filetypewin, I_FILETYPE_TYPE, type);

    /* display the dbox transiently */
    ER ( swi (Wimp_CreateSubMenu,  R1, filetypewin->handle,
                                   R2, warn->position.x,
                                   R3, warn->position.y,  END) );

    /* create the interactor */
    interactor_push (filetype_interactor, (void *) closure);

    return NULL;
}


/*
 * Called during program initialisation to create the menu to select a
 *  filetype for a FileInfo or Saveas object.
 */

error * object_fisa_create_menu (void)
{
    int numitems;
    int i;

    /* read number of fixed entries from the Messages file */
    sscanf (message_lookup(&msgs, "FilLen"), "%d", &numfiletypes);

    /* and allow for the "Other" entry */
    numitems = numfiletypes + 1;

    /* create the filetype menu */
    ER ( menu_create ( numitems,
                       message_lookup(&msgs, "Filetype"), &filetypemenu) );

    /* add the fixed entries */
    for (i = 0; i < numitems - 1; i++)
    {
        char s[6];

        sprintf (s, "Fil%d", i + 1);
        ER ( menu_entry (filetypemenu, i, message_lookup(&msgs, s),
                                          0, 0, -1, -1, NULL) );
    }

    /* and a separator before final entry */
    filetypemenu->items[numitems - 2].flags |= MF_DOTTED;

    /* and add the "other" entry */
    ER ( menu_entry (filetypemenu, numitems - 1,
                                   message_lookup(&msgs, "FilOther"),
                         0, MF_MESSAGE, -1, -1, (void *) (int) show_filetypewin) );

    /* read in "Other" template, and create the window */
    ER ( wimp_load_template ("Filetype", &filetypewin) );
    ER ( swi (Wimp_CreateWindow, R1, &filetypewin->visarea,
                                 OUT, R0, &filetypewin->handle, END) );

    /* register the window (for interactive help) */
    ER ( registry_register_window (filetypewin->handle, FileTypeDbox,
                                   (void *) filetypewin) );

    /* and the menu (for interactive help) */
    ER ( menu_register (filetypemenu, FILETYPE_MENU) );

    return NULL;
}


/*
 * Filetype pop-up menu callback function
 */

static error * filetypemenu_cb (
    MenuPtr menu,
    int *choice,
    void *closure,
    Bool reopen
)
{
    FiletypeClosPtr filetypeclosure = (FiletypeClosPtr) closure;
    int icon = filetypeclosure->icon;
    WindowPtr dbox = filetypeclosure->dbox;

    if (choice != NULL && *choice >= 0 && *choice < numfiletypes)
    {
        ER ( gui_put_filetype_string (dbox, icon,
                                            menu_get_entry (menu, *choice)) );
        if (reopen)
            menu_tick_menu (menu, *choice); 
    }

    return NULL;
}


/*
 * Called from object_fi/sa_filetype_popup - common code for dealing with
 *  the filetype selection pop-up menu.
 */

static error * object_fisa_filetype_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse,
    int menuicon
)
{
    WindowPtr dbox = object->dbox;
    char type[12];  /* longest filetype name is 11 chars ("application") */
    int i;
    PointRec popuppos;

    gui_get_filetype_string (dbox, icon, type);

    /* tick appropriate menu item */
    for (i = 0; i < numfiletypes; i++)
    {
        if (strcmp (type, menu_get_entry (filetypemenu, i) ) == 0)
        {
            menu_tick_menu (filetypemenu, i);
            break;
        }
    }
    if (i == numfiletypes)   /* must be another filetype */
        menu_tick_menu (filetypemenu, numfiletypes);

    where_to_popup (&popuppos, dbox, menuicon);

    /* post the menu */
    filetypeclosure.dbox = dbox;
    filetypeclosure.icon = icon;
    return menu_post (filetypemenu,
                      &popuppos,
                      FALSE, filetypemenu_cb, (void *) &filetypeclosure);

}


/*
 * Called when the user clicks on the pop-up icon next to the Filetype icon.
 */

error * object_fi_filetype_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    int buttons = mouse->buttons;

    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    return object_fisa_filetype_popup (object, I_FILEINFO_FILETYPE,
                                        mouse, I_FILEINFO_FILETYPE_POPUP);
}


/*
 * Special processing for font dialogues - initialisation.
 *
 * Sorts out faded status of the initial font popup menu icon.
 */

error * object_fd_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    Bool noinitial = (object->body.fontdbox.initialfont == NULL);

    dbox_shade (dbox, I_FONTDBOX_INITIAL_POPUP, noinitial);

    return NULL;
}


/*
 * Called to re-tick the font menu.
 */

static void tick_font_menu_path (MenuPtr menu, int *choice)
{
    int i = 0;
    int tickone = (choice == NULL) ? -1 : choice[0];
    int ticktwo = (choice == NULL) ? -1 : (choice[1] == -1) ? 0 : choice[1];

    while (TRUE)
    {
        MenuItemPtr item = &menu->items[i];
        unsigned int sub = (unsigned int) item->sub;
        Bool thisone = (i == tickone);

        if (thisone)
            item->flags |= MF_TICKED;
        else       
            item->flags &= ~MF_TICKED;

        if ((sub & 1) == 0)
        {
            MenuPtr submenu = (MenuPtr) sub;
            int i = 0;

            while (TRUE)
            {
                MenuItemPtr item = &submenu->items[i];

                if (thisone && i == ticktwo)
                    item->flags |= MF_TICKED;
                else
                    item->flags &= ~MF_TICKED;

                if ((item->flags & MF_LAST) != 0)
                    break;
                i++;
            }
        }

        if ((item->flags & MF_LAST) != 0)
            break;
        i++;
    }

    return;
}


/*
 * Font pop-up menu callback function
 */

static error * fontmenu_cb (
    MenuPtr menu,
    int *choice,
    void *closure,
    Bool reopen
)
{
    FontClosPtr fontclosure = (FontClosPtr) closure;
    int icon = fontclosure->icon;
    WindowPtr dbox = fontclosure->dbox;
    Bool systemfont = fontclosure->systemfont;
    char *font;
    int buffsize;

    if (choice != NULL)
    {
        /* decode choice - if *choice is 0, it's the system font */
        if (systemfont && *choice == 0)
            dbox_setstring (dbox, icon, message_lookup (&msgs, "SysFont"));
        else
        {
            /* decode choice - first determine buffer size required */
            ER ( swi (Font_DecodeMenu,
                        R0, 0,
                        R1, menu,      /* font menu */
                        R2, choice,
                        R3, 0,         /* find size of buffer required */
                        R4, 0,
                   OUT, R4, &buffsize,
                   END) );

            /* allocate buffer */
            font = malloc (buffsize);
            if (font == NULL)
                return error_lookup ("NoMem");

            /* decode choice */
            ER ( swi (Font_DecodeMenu,
                        R0, 0,
                        R1, menu,      /* font menu */
                        R2, choice,
                        R3, font,      /* buffer */
                        R4, buffsize,
                   END) );

            /* extract font identifier from returned string:
                format expected is [\F]<fontid>[\<rest>] */
            {
                char *s = font;

                if (*s == '\\')
                {
                    char *t;

                    s += 2;
                    t = s;
                    while (*t != 0)
                        if (*t == '\\')
                            *t = 0;
                        else
                            t++;
                }

                /* and insert into writable icon */
                dbox_setstring (dbox, icon, s);
            }

            /* and free space temporarily used */
            free (font);
        }

        if (reopen)
            tick_font_menu_path (menu, choice);
    }

    return NULL;
}


/*
 * Called to create and post a font selection menu
 */

static error * object_fmfd_font_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse,
    Bool systemfont,       /* TRUE iff a "system" entry is to be included */
    int menuicon
)
{
    WindowPtr dbox = object->dbox;
    char *currentfont = dbox_getstring (dbox, icon);
    int size1, size2;
    PointRec popuppos;

    /* must set currentfont to 1 for System Font */
    if (strcmp (currentfont, message_lookup (&msgs, "SysFont")) == 0)
        currentfont = (char *) (1);

    /* find out buffer sizes needed for font menu */
    ER ( swi (Font_ListFonts,
                R1, 0,   /* find size of buffer for menu */
                R2, BIT(19) | (systemfont ? BIT(20) : 0) | BIT(21),
                         /* make menu, with(out) system font, ticked at R6 */
                R3, 0,
                R4, 0,   /* find size of buffer for indirected data */
                R5, 0,
                R6, currentfont,      /* font to tick */
           OUT, R3, &size1,           /* size of menu buffer */
                R5, &size2, END) );   /* size of indirected data buffer */

    /* (re)allocate buffers for the font menu if necessary */
    if (size1 > buff1size)
    {
        free (buff1);
        buff1 = malloc (size1);
        buff1size = size1;
        if (buff1 == NULL)
            return error_lookup ("NoMem");
    }
    if (size2 > buff2size)
    {
        free (buff2);
        buff2 = malloc (size2);
        buff2size = size2;
        if (buff2 == NULL)
            return error_lookup ("NoMem");
    }

    /* now create the font menu */
    ER ( swi (Font_ListFonts,
                R1, buff1,        /* buffer for menu */
                R2, BIT(19) | (systemfont ? BIT(20) : 0) | BIT(21),
                         /* make menu, with(out) system font, ticked at R6 */
                R3, buff1size,
                R4, buff2,        /* buffer for indirected data */
                R5, buff2size,
                R6, currentfont,  /* font to tick */
           END) );

    /* de-register previous font menu, if still registered */
    menu_deregister (fontmenu);

    /* note new font menu */
    fontmenu = (MenuPtr) buff1;

    /* register it (for interactive help) */
    ER ( menu_register (fontmenu, FONT_MENU) );

    where_to_popup (&popuppos, dbox, menuicon);

    /* post the font menu */
    fontclosure.dbox = dbox;
    fontclosure.icon = icon;
    fontclosure.systemfont = systemfont;
    return menu_post (fontmenu,
                      &popuppos,
                      FALSE, fontmenu_cb, (void *) &fontclosure);
}


/*
 * Called when the user clicks on the popup next to the Initial font field.
 */

error * object_fd_initial_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    int buttons = mouse->buttons;
    Bool systemfont;

    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    /* will the menu need a "System" entry? */
    systemfont = dbox_getbutton (object->dbox, I_FONTDBOX_ALLOWSYSTEM);

    return object_fmfd_font_popup (object, I_FONTDBOX_INITIAL, mouse, 
                               systemfont, I_FONTDBOX_INITIAL_POPUP);
}


/*
 * Called when the user clicks on the Allow system font option button.
 */

error * object_fd_allowsystem (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    WindowPtr dbox = object->dbox;
    Bool systemfont = dbox_getbutton (dbox, I_FONTDBOX_ALLOWSYSTEM);

    if (!systemfont &&
            strcmp (dbox_getstring (dbox, I_FONTDBOX_INITIAL),
                    message_lookup (&msgs, "SysFont")) == 0)
        dbox_setstring (dbox, I_FONTDBOX_INITIAL, "");

    return NULL;
}


/*
 * Special processing for font menus - initialisation.
 *
 * Sorts out faded status of the initial font popup menu icon.
 */

error * object_fm_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    Bool noinitial = (object->body.fontmenu.tickedfont == NULL);

    dbox_shade (dbox, I_FONTMENU_INITIAL_POPUP, noinitial);

    return NULL;
}


/*
 * Called when the user clicks on the pop-up icon next to the Initial font
 *  field.
 */

error * object_fm_ticked_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    int buttons = mouse->buttons;
    Bool systemfont;

    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    /* will the menu need a "System" entry? */
    systemfont = dbox_getbutton (object->dbox, I_FONTMENU_ALLOWSYSTEM);

    return object_fmfd_font_popup (object, I_FONTMENU_INITIAL, mouse,
                               systemfont, I_FONTMENU_INITIAL_POPUP);
}


/*
 * Called when the user clicks on the Allow system font option button.
 */

error * object_fm_allowsystem (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    WindowPtr dbox = object->dbox;
    Bool systemfont = dbox_getbutton (dbox, I_FONTMENU_ALLOWSYSTEM);

    if (!systemfont &&
            strcmp (dbox_getstring (dbox, I_FONTMENU_INITIAL),
                    message_lookup (&msgs, "SysFont")) == 0)
        dbox_setstring (dbox, I_FONTMENU_INITIAL, "");

    return NULL;
}


/*
 * Special processing for iconbar icon dialogues - initialisation.
 *
 * Sorts out faded status of 'Transient' option icons, and 'Priority' icons.
 */

error * object_ib_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    Bool noselectshow = (object->body.iconbar.selectshow == NULL);
    Bool noadjustshow = (object->body.iconbar.adjustshow == NULL);
    int position = object->body.iconbar.position;

    dbox_shade (dbox, I_ICONBAR_SELECT_TRANSIENT, noselectshow);
    dbox_shade (dbox, I_ICONBAR_SELECT_EVENT_BEFORE, noselectshow);
    dbox_shade (dbox, I_ICONBAR_ADJUST_TRANSIENT, noadjustshow);
    dbox_shade (dbox, I_ICONBAR_ADJUST_EVENT_BEFORE, noadjustshow);

    if (position < -8 || position > -5)
    {
        dbox_shade (dbox, I_ICONBAR_PRIORITY, TRUE);
        dbox_shade (dbox, I_ICONBAR_PRIORITY_ADJ_UP, TRUE);
        dbox_shade (dbox, I_ICONBAR_PRIORITY_ADJ_DOWN, TRUE);
    }

    return NULL;
}


/*
 * Called during program initialisation to create the menu to select an
 *  iconbar object's position.
 */

error * object_ib_create_menu (void)
{
    int i;

    /* create the position menu */
    ER ( menu_create ( POSITION_MENU_SIZE,
                       message_lookup(&msgs, "Position"), &positionmenu) );

    for (i = 0; i < POSITION_MENU_SIZE; i++)
    {
        char s[5];

        sprintf (s, "Pos%d", i + 1);
        ER ( menu_entry (positionmenu, i, message_lookup(&msgs, s),
                                         0, 0, -1, -1, NULL) );
    }

    /* register menu  (for interactive help) */
    ER ( menu_register (positionmenu, POSITION_MENU) );

    return NULL;
}


/*
 * Iconbar position pop-up menu callback function
 */

static error * positionmenu_cb (
    MenuPtr menu,
    int *choice,
    void *closure,
    Bool reopen
)
{
    WindowPtr dbox = (WindowPtr) closure;

    if (choice != NULL && *choice >= 0)
    {
        dbox_setint (dbox, I_ICONBAR_POSITION, -(*choice + 1));

        /* set faded status of Priority icons as necessary */
        dbox_shade (dbox, I_ICONBAR_PRIORITY, *choice < 4);
        dbox_shade (dbox, I_ICONBAR_PRIORITY_ADJ_UP, *choice < 4);
        dbox_shade (dbox, I_ICONBAR_PRIORITY_ADJ_DOWN, *choice < 4);

        if (reopen)
            menu_tick_menu (menu, *choice);
    }

    return NULL;
}


/*
 * Called when the user clicks on the pop-up icon next to the Position icon.
 */

error * object_ib_position_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    WindowPtr dbox = object->dbox;
    int iconpos = dbox_getint (dbox, I_ICONBAR_POSITION);
    int buttons = mouse->buttons;
    PointRec popuppos;

    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    /* tick appropriate menu item */
    iconpos = (-iconpos) - 1;
    if (iconpos >= 0 && iconpos < POSITION_MENU_SIZE)
        menu_tick_menu (positionmenu, iconpos);
    else
        menu_tick_menu (positionmenu, -1);  /* to clear all ticks */

    /* determine where to display the menu */
    where_to_popup (&popuppos, dbox, I_ICONBAR_POSITION_POPUP);

    /* post the menu */
    return menu_post (positionmenu,
                      &popuppos,
                      FALSE, positionmenu_cb, (void *) dbox);
}


/*
 * Special processing for print dbox dialogues - initialisation.
 *
 * Sorts out faded status of various icons.
 */

error * object_pd_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    unsigned flags = object->body.printdbox.flags;

    dbox_shade (dbox, I_PRINTDBOX_COPIES,
                (flags & PRINTDBOX_INCLUDECOPIES) == 0);

    dbox_shade (dbox, I_PRINTDBOX_SCALE,
                (flags & PRINTDBOX_INCLUDESCALE) == 0);

    {
        Bool hasrange = (flags & PRINTDBOX_INCLUDEALLFROMTO) != 0;
        Bool fromtoset = (flags & PRINTDBOX_SELECTFROMTO) != 0;

        dbox_shade (dbox, I_PRINTDBOX_ALL, !hasrange);
        dbox_shade (dbox, I_PRINTDBOX_RANGE, !hasrange);

        dbox_shade (dbox, I_PRINTDBOX_FIRST, !hasrange || !fromtoset);
        dbox_shade (dbox, I_PRINTDBOX_LAST, !hasrange || !fromtoset);
    }

    {
        Bool hasorientation = (flags & PRINTDBOX_INCLUDEORIENTATION) != 0;

        dbox_shade (dbox, I_PRINTDBOX_UPRIGHT, !hasorientation);
        dbox_shade (dbox, I_PRINTDBOX_SIDEWAYS, !hasorientation);
    }

    {
        Bool hasdraft = (flags & PRINTDBOX_INCLUDEDRAFT) != 0;

        dbox_shade (dbox, I_PRINTDBOX_ON, !hasdraft);
        dbox_shade (dbox, I_PRINTDBOX_OFF, !hasdraft);
    }

    {
        Bool hassetup = (flags & PRINTDBOX_INCLUDESETUP) != 0;

        dbox_shade (dbox, I_PRINTDBOX_SETUP, !hassetup);
        dbox_shade (dbox, I_PRINTDBOX_SETUP_EVENT_BEFORE, !hassetup);
    }

    return NULL;
}


/*
 * Special processing for print dbox dialogues - finalisation.
 *
 * Checks range of 'from'/'to' values if appropriate.
 */

error * object_pd_apply (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;

    if (dbox_getbutton (dbox, I_PRINTDBOX_HASPAGERANGE) &&
        dbox_getbutton (dbox, I_PRINTDBOX_RANGE))
    {
        int first = dbox_getint (dbox, I_PRINTDBOX_FIRST);
        int last = dbox_getint (dbox, I_PRINTDBOX_LAST);

        if (first < 1 || last < 1 || last < first)
            return error_lookup ("BadRange");
    }

    return NULL;
}


/*
 * Special processing for print dbox dialogues:
 *  click on page range option icon.
 *
 * Fades/unfades associated icons as necessary.
 */

error * object_pd_pagerange (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    if (mouse->buttons != MB_CLICK(MB_MENU))
    {
        WindowPtr dbox = object->dbox;
        Bool hasrange = dbox_getbutton (dbox, I_PRINTDBOX_HASPAGERANGE);

        dbox_shade (dbox, I_PRINTDBOX_ALL, !hasrange);
        dbox_shade (dbox, I_PRINTDBOX_RANGE, !hasrange);

        if (hasrange)
            hasrange = dbox_getbutton (dbox, I_PRINTDBOX_RANGE);

        dbox_shade (dbox, I_PRINTDBOX_FIRST, !hasrange);
        dbox_shade (dbox, I_PRINTDBOX_LAST, !hasrange);
        if (hasrange)
            dbox_place_caret (dbox, I_PRINTDBOX_FIRST);
    }

    return NULL;
}


/*
 * Special processing for proginfo dialogues - initialisation.
 *
 * Initialises and sets faded status for "licence type" icon.
 */

error * object_pi_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    Bool haslicence =
        (object->body.proginfo.flags & PROGINFO_INCLUDELICENCETYPE) != 0;
    int type = object->body.proginfo.licencetype;

    if (type >= 0 && type <= LICENCE_MENU_SIZE - 1)
        dbox_setstring (dbox, I_PROGINFO_LICENCE,
                              menu_get_entry (licencemenu, type));
    else
        dbox_setstring (dbox, I_PROGINFO_LICENCE, "");

    dbox_shade (dbox, I_PROGINFO_LICENCE, !haslicence);
    dbox_shade (dbox, I_PROGINFO_LICENCE_POPUP, !haslicence);

    return NULL;
}


/*
 * Special processing for proginfo dialogues - finalisation.
 *
 * Copies licence type value out of dbox.
 */

error * object_pi_apply (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    char *type = dbox_getstring (dbox, I_PROGINFO_LICENCE);
    Bool haslicence = dbox_getbutton (dbox, I_PROGINFO_HASLICENCE);
    int i;

    for (i = 0; i < LICENCE_MENU_SIZE; i++)
        if (strcmp (type, menu_get_entry (licencemenu, i) ) == 0)
            break;

    if (haslicence && i < LICENCE_MENU_SIZE)
        object->body.proginfo.licencetype = i;
    else
        object->body.proginfo.licencetype = -1;

    return NULL;
}


/*
 * Called during program initialisation to create the menu to select
 *  licence types.
 */

error * object_pi_create_menu (void)
{
    int i;

    /* create the licence type menu */
    ER ( menu_create ( LICENCE_MENU_SIZE,
                       message_lookup(&msgs, "Licence"), &licencemenu) );

    for (i = 0; i < LICENCE_MENU_SIZE; i++)
    {
        char s[5];

        sprintf (s, "Lic%d", i + 1);
        ER ( menu_entry (licencemenu, i, message_lookup(&msgs, s),
                                         0, 0, -1, -1, NULL) );
    }

    /* register menu (for interactive help) */
    ER ( menu_register (licencemenu, LICENCETYPE_MENU) );

    return NULL;
}


/*
 * Licence type pop-up menu callback function
 */

static error * licencemenu_cb (
    MenuPtr menu,
    int *choice,
    void *closure,
    Bool reopen
)
{
    WindowPtr dbox = (WindowPtr) closure;

    if (choice != NULL && *choice >= 0)
    {
        dbox_setstring (dbox, I_PROGINFO_LICENCE,
                              menu_get_entry (menu, *choice));

        if (reopen)
            menu_tick_menu (menu, *choice);
    }

    return NULL;
}


/*
 * Called when the user clicks on the pop-up icon next to the Licence type
 *  field.
 */

error * object_pi_licence_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    WindowPtr dbox = object->dbox;
    char *type = dbox_getstring (dbox, I_PROGINFO_LICENCE);
    int i;
    int buttons = mouse->buttons;
    PointRec popuppos;

    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    /* tick appropriate menu item */
    for (i = 0; i < LICENCE_MENU_SIZE; i++)
    {
        if (strcmp (type, menu_get_entry (licencemenu, i) ) == 0)
        {
            menu_tick_menu (licencemenu, i);
            break;
        }
    }
    if (i == LICENCE_MENU_SIZE)   /* clear all ticks if out of range */
        menu_tick_menu (licencemenu, -1);

    where_to_popup (&popuppos, dbox, I_PROGINFO_LICENCE_POPUP);

    /* post the menu */
    return menu_post (licencemenu,
                      &popuppos,
                      FALSE, licencemenu_cb, (void *) dbox);
}


/*
 * Special processing for saveas dialogues - initialisation.
 *
 * Set faded status for "supports RAM transfers" icon.
 */

error * object_sa_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    Bool clientparticipates =
        (object->body.proginfo.flags & SAVEAS_AUTOMATICRAMTRANSFER) == 0;

    dbox_shade (dbox, I_SAVEAS_RAM, !clientparticipates);

    return NULL;
}


/*
 * Called when the user clicks on the pop-up icon next to the Filetype icon.
 */

error * object_sa_filetype_popup (
    ObjectPtr object,
    int icon,
    MouseClickPtr mouse
)
{
    int buttons = mouse->buttons;

    if (buttons == MB_CLICK(MB_ADJUST))
        return NULL;

    return object_fisa_filetype_popup (object, I_SAVEAS_FILETYPE,
                                        mouse, I_SAVEAS_FILETYPE_POPUP);
}


/*
 * Special processing for scale dbox dialogues - finalisation.
 *
 * Checks various numeric values.
 */

error * object_sc_apply (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    int min = dbox_getint (dbox, I_SCALE_MINIMUM);
    int max = dbox_getint (dbox, I_SCALE_MAXIMUM);
    int step = dbox_getint (dbox, I_SCALE_STEPSIZE);
    int preset1 = dbox_getint (dbox, I_SCALE_PRESET_1);
    int preset2 = dbox_getint (dbox, I_SCALE_PRESET_2);
    int preset3 = dbox_getint (dbox, I_SCALE_PRESET_3);
    int preset4 = dbox_getint (dbox, I_SCALE_PRESET_4);

    if (min < 1 || max < 1 || min > max)
        return error_lookup ("ScaleMinMax");

    if (step < 1 || step > (max - min))
        return error_lookup ("ScaleStep");

    if ( preset1 < min || preset1 > max ||
         preset2 < min || preset2 > max ||
         preset3 < min || preset3 > max ||
         preset4 < min || preset4 > max )
        return error_lookup ("ScalePreset");

    return NULL;
}


/*
 * Returns the type of the field at 'offset' inside the unknown object whose 
 *  definition is 'def'; possible values are:
 *      REF_STR, REF_MSG, REF_SPRITE, REF_OFFSET, REF_END
 *  where REF_END means that the field is not relocatable (and hence is to
 *  be treated as an integer).
 */

static int uk_field_type (ObjectDefPtr def, int offset)
{
    RefDefPtr refs = def->body.refs;

    while (refs->type != REF_END)
    {
        if (refs->offset == offset)
            return refs->type;

        refs++;
    }

    return REF_END;
}


/*
 * Copies a value from the dialogue box into the field at 'offset' inside an
 *  unknown object.
 */

static error * save_uk_field (ObjectPtr object, int offset)
{
    char *field = object->bodycopy + offset;
    int type = uk_field_type (object->def, offset);
    WindowPtr dbox = object->dbox;
    int v;
    char *s = 0;

    switch (type)
    {
        case REF_STR:
            if (dbox_getbutton (dbox, I_UNKNOWN_HASSTRING))
                s = dbox_getstring (dbox, I_UNKNOWN_STRING);
            break;

        case REF_MSG:
            if (dbox_getbutton (dbox, I_UNKNOWN_HASMESSAGE))
                s = dbox_getstring (dbox, I_UNKNOWN_MESSAGE);
            break;

        case REF_SPRITE:
            if (dbox_getbutton (dbox, I_UNKNOWN_WIMP))
                v = WIMP_SPRITEAREA;
            else
                v = CLIENT_SPRITEAREA;
            break;

        case REF_OFFSET:
            if (dbox_getbutton (dbox, I_UNKNOWN_HASOBJOFFSET))
                v = dbox_getint (dbox, I_UNKNOWN_OBJOFFSET);
            else
                v = -1;  /* the NULL ObjectOffset reference value */
            break;
 
        case REF_END:
            v = dbox_getint (dbox, I_UNKNOWN_INTEGER);
            break;
    }

    switch (type)
    {
        case REF_STR:
        case REF_MSG:
            /* note that each of these operations also works for s = NULL! */
            free (* ((char **) field));
            * ((char **) field) = s;
            ER (clonestring ((char **) field));
            break;

        case REF_OFFSET:
        case REF_SPRITE:
        case REF_END:
            * ((int *) field) = v;
            break; 
    }

    return NULL;
}


/*
 * Loads the value of the unknown object's field at 'offset' into its
 *  properties dialogue box.
 */

static error * load_uk_field (ObjectPtr object, int offset)
{
    char *field = (char *) object->bodycopy + offset;
    int type = uk_field_type (object->def, offset);
    WindowPtr dbox = object->dbox;

    dbox_shade (dbox, I_UNKNOWN_INTEGER, type != REF_END);
    dbox_shade (dbox, I_UNKNOWN_HASSTRING, type != REF_STR);
    dbox_shade (dbox, I_UNKNOWN_STRING, type != REF_STR);
    dbox_shade (dbox, I_UNKNOWN_HASMESSAGE, type != REF_MSG);
    dbox_shade (dbox, I_UNKNOWN_MESSAGE, type != REF_MSG);
    dbox_shade (dbox, I_UNKNOWN_WIMP, type != REF_SPRITE);
    dbox_shade (dbox, I_UNKNOWN_CLIENT, type != REF_SPRITE);
    dbox_shade (dbox, I_UNKNOWN_HASOBJOFFSET, type != REF_OFFSET);
    dbox_shade (dbox, I_UNKNOWN_OBJOFFSET, type != REF_OFFSET);

    /* grey out labels only for wimps 3.50 onwards */
    if (wimpversion >= 350)
    {
        dbox_shade (dbox, I_UNKNOWN_INTEGERLABEL, type != REF_END);
        dbox_shade (dbox, I_UNKNOWN_SPRITELABEL, type != REF_SPRITE);
    }

    if (type != REF_END)
        dbox_setstring (dbox, I_UNKNOWN_INTEGER, "");
    if (type != REF_STR)
    {
        dbox_setstring (dbox, I_UNKNOWN_STRING, "");
        dbox_setbutton (dbox, I_UNKNOWN_HASSTRING, FALSE);
    }
    if (type != REF_MSG)
    {
        dbox_setstring (dbox, I_UNKNOWN_MESSAGE, "");
        dbox_setbutton (dbox, I_UNKNOWN_HASMESSAGE, FALSE);
    }
    if (type != REF_OFFSET)
    {
        dbox_setstring (dbox, I_UNKNOWN_OBJOFFSET, "");
        dbox_setbutton (dbox, I_UNKNOWN_HASOBJOFFSET, FALSE);
    }

    switch (type)
    {
        case REF_STR:
            {
                char *s = * ((char **) field);

                dbox_setstring (dbox, I_UNKNOWN_STRING, s);
                dbox_setbutton (dbox, I_UNKNOWN_HASSTRING, s != NULL);
                if (s == NULL)
                    dbox_shade (dbox, I_UNKNOWN_STRING, TRUE);
                else
                    dbox_place_caret (dbox, I_UNKNOWN_STRING);
            }
            break;

        case REF_MSG:
            {
                char *s = * ((char **) field);

                dbox_setstring (dbox, I_UNKNOWN_MESSAGE, s);
                dbox_setbutton (dbox, I_UNKNOWN_HASMESSAGE, s != NULL);
                if (s == NULL)
                    dbox_shade (dbox, I_UNKNOWN_MESSAGE, TRUE);
                else
                    dbox_place_caret (dbox, I_UNKNOWN_MESSAGE);
            }
            break;

        case REF_SPRITE:
            {
                Bool iswimp = (* ((int *) field) == WIMP_SPRITEAREA);

                dbox_setbutton (dbox, I_UNKNOWN_WIMP, iswimp);
                dbox_setbutton (dbox, I_UNKNOWN_CLIENT, !iswimp);
            }
            break;

        case REF_OFFSET:
            {
                int v = * ((int *) field);

                dbox_setbutton (dbox, I_UNKNOWN_HASOBJOFFSET, v != -1);
                if (v == -1)
                {
                    dbox_setstring (dbox, I_UNKNOWN_OBJOFFSET, "");
                    dbox_shade (dbox, I_UNKNOWN_OBJOFFSET, TRUE);
                }
                else
                {
                    dbox_setint (dbox, I_UNKNOWN_OBJOFFSET, v);
                    dbox_place_caret (dbox, I_UNKNOWN_OBJOFFSET);
                }
            }
            break;

        case REF_END:        /* ie an integer */
            dbox_setint (dbox, I_UNKNOWN_INTEGER, * ((int *) field));
            dbox_place_caret (dbox, I_UNKNOWN_INTEGER);
            break;
    }

    return NULL;
}


/*
 * Special processing for unknown objects - called from object_init_dbox(..)
 *  after initialising icons in the object's properties dbox.
 *
 * (Re)-initialises the copy of the object's body, and fills in the fields
 *  in the special dialogue box.
 */

error * object_uk_init (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    ObjectDefPtr def = object->def;
    char *copy = (char *) object->bodycopy;
    char *body = (char *) &object->body;


    /* special case: object with no body */
    if (copy == NULL)
    {
        /* grey out all fields other than header ones */
        static int fields[] = {
            I_UNKNOWN_OFFSET,
            I_UNKNOWN_INTEGER,
            I_UNKNOWN_HASSTRING,
            I_UNKNOWN_STRING,
            I_UNKNOWN_HASMESSAGE,
            I_UNKNOWN_MESSAGE,
            I_UNKNOWN_WIMP,
            I_UNKNOWN_CLIENT,
            I_UNKNOWN_HASOBJOFFSET,
            I_UNKNOWN_OBJOFFSET,
            I_UNKNOWN_OFFSET_ADJ_UP,
            I_UNKNOWN_OFFSET_ADJ_DOWN,
            I_UNKNOWN_OFFSETLABEL,
            I_UNKNOWN_INTEGERLABEL,
            I_UNKNOWN_SPRITELABEL,
            0 };
        int *pf = fields;

        /* don't grey out labels on pre-350 Wimps */
        if (wimpversion < 350)
            fields[12] = 0;

        while (*pf != 0)
        {
            dbox_shade (dbox, *pf, TRUE);
            pf++;
        }

        /* blank out the "Offset" value */
        dbox_setstring (dbox, I_UNKNOWN_OFFSET, "");

        return NULL;
    }


    /*
     * (Re)-initialise the bodycopy record:
     *   Free any strings already there.
     *   Copy the object's body to it.
     *   Clone any string fields inside it.
     */
    {
        RefDefPtr refs = def->body.refs;

        /* free strings already there */
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    free (* ((char **) (copy + refs->offset)));
                    break;
            }

            refs++;
        }

        /* copy across body */
        memcpy (copy, body, def->body.size);

        /* clone any strings */
        refs = def->body.refs;
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    ER ( clonestring ((char **) (copy + refs->offset)) );
                    break;
            }

            refs++;
        }
    }

    /* reload fields in "Other fields" section of dbox */
    {
        int offset = dbox_getint (dbox, I_UNKNOWN_OFFSET);

        return load_uk_field (object, offset);
    }
}


/*
 * User has requested that the "offset" be changed by 'delta':
 *  Check that offset remains in the range 0 <= offset < size.
 *  Save any change to the field at the current offset.
 *  Load the value of the field at the new offset.
 *  Update the "offset" field.
 */

static error * object_uk_offset_change (ObjectPtr object, int delta)
{
    WindowPtr dbox = object->dbox;
    int curr = dbox_getint (dbox, I_UNKNOWN_OFFSET);
    int new = curr + delta;

    /* ensure offset remains in range */
    if (new < 0)
        new = 0;

    if (new >= object->def->body.size)
        new = object->def->body.size - 4;

    /* return immediately if no change */
    if (curr == new)
        return NULL;

    /* display new offset */
    dbox_setint (dbox, I_UNKNOWN_OFFSET, new);

    /* save any changes to the bodycopy record before ... */
    ER ( save_uk_field (object, curr) );

    /* ... updating the dbox with the new field's value */
    ER ( load_uk_field (object, new) );

    return NULL;
}


/*
 * Special processing for unknown objects - called when the user clicks
 *  adjust or select on the "offset" up- or down-adjuster icon.
 *
 * This function determines what offset adjustment is required, and then
 *  calls object_uk_offset_change(..) to update the dialogue box.
 */

error * object_uk_offset (ObjectPtr object, int icon, MouseClickPtr mouse)
{
    Bool up = (icon == I_UNKNOWN_OFFSET_ADJ_UP);
    Bool adjust = (mouse->buttons == MB_CLICK(MB_ADJUST));
    Bool shift = (wimp_read_modifiers () & MODIFIER_SHIFT) != 0;
    int delta = 4;

    if (!up)
        delta = -delta;

    if (adjust)
        delta = -delta;

    if (shift)
        delta *= 10;

    return object_uk_offset_change (object, delta);
}


/*
 * Special processing for unknown objects - called from object_apply_dbox(..)
 *  prior to updating fields of the object's body.
 *
 * Makes sure that the copy of the body is up-to-date, and then copies the
 *  copy to the body.
 */

error * object_uk_apply (ObjectPtr object)
{
    WindowPtr dbox = object->dbox;
    ObjectDefPtr def = object->def;
    char *copy = (char *) object->bodycopy;
    char *body = (char *) &object->body;


    /* special case - object has zero body size */
    if (copy == NULL)
        return NULL;


    /* update the bodycopy record from the dbox */
    {
        int offset = dbox_getint (dbox, I_UNKNOWN_OFFSET);

        ER ( save_uk_field (object, offset) );
    }

    /*
     * Update the object's body from the bodycopy record:
     *   Free any strings already there.
     *   Copy the bodycopy record to it.
     *   Clone any string fields inside it.
     */
    {
        RefDefPtr refs = def->body.refs;

        /* free strings already there */
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    free (* ((char **) (body + refs->offset)));
                    break;
            }

            refs++;
        }

        /* copy across bodycopy record */
        memcpy (body, copy, def->body.size);

        /* clone any strings */
        refs = def->body.refs;
        while (refs->type != REF_END)
        {
            switch (refs->type)
            {
                case REF_STR:
                case REF_MSG:
                    ER ( clonestring ((char **) (body + refs->offset)) );
                    break;
            }

            refs++;
        }
    }
    
    return NULL;
}
