/*-*-C-*-
 * Menu CSE Properties Handling
 */

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

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

#include "format.h"
#include "menuedit.h"
#include "icondefs.h"
#include "gui.h"
#include "props.h"
#include "protocol.h"


static WindowPtr menudboxproto, entrydboxproto;
static int menudboxsize, entrydboxsize;



/*
 * Load window prototypes
 */

error * props_load_prototypes ()
{
    ER ( wimp_load_template_returning_size ("menudbox", &menudboxproto, &menudboxsize) );
    return wimp_load_template_returning_size ("entrydbox", &entrydboxproto, &entrydboxsize);
}


/*
 * Check the argument string for multiple spaces.  If any are found,
 * replace them with a single space (unless at the end of the string,
 * when replace them with nothing).
 */

static void remove_multiple_spaces (char *buf)
{
    char *rd = buf, *wt = buf, c;
    while ((c = *rd++) != 0)
    {
        if (c == ' ' && *rd == ' ')
            continue;
        *wt++ = c;
    }
    *wt = 0;
}
    

/*
 *************************
 *** MENU DIALOGUE BOX ***
 *************************
 */


/*
 * Fill in the menu's dialogue box and refresh the screen if the
 * dbox is open.  Only update the contents if 'contents' is TRUE.
 * Only update the titlebar if 'titlebar' is TRUE.
 */

error * props_update_menu_dbox (MenuObjPtr menu, Bool contents, Bool title)
{
    if (contents)
    {
        ER ( gui_put_len_str (
                menu->dbox,
                I_MENUDBOX_TITLE, I_MENUDBOX_TITLEMAX,
                menu->p.title, menu->p.maxtitle) );
        ER ( gui_put_len_opt_str (
                menu->dbox,
                I_MENUDBOX_HASHELP, I_MENUDBOX_HELP, I_MENUDBOX_HELPMAX,
                menu->p.helpmessage, menu->p.maxhelp) );
        gui_put_opt_event (menu->dbox,
                           I_MENUDBOX_BEFORE_DEFAULT,
                           I_MENUDBOX_BEFORE_OTHER,
                           I_MENUDBOX_BEFORE_NONE,
                           I_MENUDBOX_BEFORE_EVENT,
                           menu->p.showevent,
                           (menu->p.flags & MENU_GENERATESHOWEVENT) == 0);
        gui_put_opt_event (menu->dbox,
                           I_MENUDBOX_HIDDEN_DEFAULT,
                           I_MENUDBOX_HIDDEN_OTHER,
                           I_MENUDBOX_HIDDEN_NONE,
                           I_MENUDBOX_HIDDEN_EVENT,
                           menu->p.hideevent,
                           (menu->p.flags & MENU_GENERATEHIDEEVENT) == 0);
    }

    if (title)
    {
        /* Get the printf pattern from the prototype window's titlebar which
         * should have %s in it where the name is wanted
         */
        char buf[256];
        sprintf (buf, dbox_gettitle (menudboxproto), menu->name);
        ER ( dbox_settitle (menu->dbox, buf, TRUE) );
    }

    return NULL;
}


/*
 * Open/raise props dbox for the menu.  Create it if necessary, giving it the
 * same position that any previous incarnation of the box was dragged to.
 * (We do this by updating the visarea coordinates of the prototype every
 * time we get an open_window_request on any of its copies.)
 * If the window exists but is not open, open it in its last position.
 * If the window exists and is open, just raise it to the top of the window stack.
 *
 * If the dbox was not already open, update its contents.
 */

error * props_open_menu_dbox (MenuObjPtr menu)
{
    if (menu->dbox == NULL)
    {
        ER ( wimp_copy_template (menudboxproto, &menu->dbox, menudboxsize) );
        ER ( swi (Wimp_CreateWindow,  R1, &menu->dbox->visarea,
                  OUT,  R0, &menu->dbox->handle,  END) );
        ER ( registry_register_window (menu->dbox->handle, MenuDbox, (void *) menu) );
        menu->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, menu->dbox,  END) );
        ER ( props_update_menu_dbox (menu, TRUE, TRUE) );
    }
    else
    {
        menu->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, menu->dbox,  END) );
    }   

    return dbox_set_caret_to (menu->dbox, I_MENUDBOX_TITLE);
}


/*
 * Creates a copy of the properties which may be altered by the Menu
 *  Properties dialogue.
 *
 * Result is NULL if insufficient memory is available.
 */

static MenuPropsPtr menu_props_copy (MenuObjPtr menu)
{
    MenuPropsPtr copy = (MenuPropsPtr) malloc (sizeof (MenuPropsRec));
    error *err = NULL;

    if (copy == NULL)
        return NULL;

    /* copy the record as a whole */
    *copy = menu->p;

    /* and now clone each of the strings if necessary */
    EG ( nomem, clonestring (&copy->title) );
    EG ( nomem, clonestring (&copy->helpmessage) );

    return copy;

  nomem:
    free (copy->title);
    free (copy->helpmessage);
    free (copy);
    return NULL;
}


/*
 * Compares the original menu property values with the new values; result is
 *  TRUE iff they are all the same.
 *
 * The space occupied by the copy ('old') is released.
 */

static Bool compare_menu_props (MenuPropsPtr old, MenuObjPtr menu)
{
    Bool ok = TRUE;

    /* compare and free string fields */
    if (!equalstrings (old->title, menu->p.title))
        ok = FALSE;
    free (old->title);
    old->title = menu->p.title;   /* ready for bulk compare! */

    if (!equalstrings (old->helpmessage, menu->p.helpmessage))
        ok = FALSE;
    free (old->helpmessage);
    old->helpmessage = menu->p.helpmessage;

    /* bulk comparison and free */
    if (memcmp ((char *) old, (char *) &menu->p, sizeof (MenuPropsRec)) != 0)
        ok = FALSE;
    free (old);

    return ok;
}


/*
 * Apply the changes in a Menu dbox and redraw the menu window
 */

static error * apply_menu_dbox (MenuObjPtr menu, Bool stayopen)
{
    WindowPtr dbox = menu->dbox;
    MenuPropsPtr copy = menu_props_copy(menu);
    error *err = NULL;

    /* check that the copy was successfully made */
    if (copy == NULL)
        return error_lookup ("NoMem");

    EG ( fail, gui_get_len_str (
            dbox,
            I_MENUDBOX_TITLE, I_MENUDBOX_TITLEMAX,
            &menu->p.title, &menu->p.maxtitle) );
    EG ( fail, gui_get_len_opt_str (
            dbox,
            I_MENUDBOX_HASHELP, I_MENUDBOX_HELP, I_MENUDBOX_HELPMAX,
            &menu->p.helpmessage, &menu->p.maxhelp) );

    gui_get_opt_event (dbox,
                       I_MENUDBOX_BEFORE_DEFAULT,
                       I_MENUDBOX_BEFORE_NONE,
                       I_MENUDBOX_BEFORE_EVENT,
                       &menu->p.showevent,
                       &menu->p.flags,
                       MENU_GENERATESHOWEVENT);
    gui_get_opt_event (dbox,
                       I_MENUDBOX_HIDDEN_DEFAULT,
                       I_MENUDBOX_HIDDEN_NONE,
                       I_MENUDBOX_HIDDEN_EVENT,
                       &menu->p.hideevent,
                       &menu->p.flags,
                       MENU_GENERATEHIDEEVENT);

    EG ( fail, menuedit_fix_extent (menu) );

    if (!compare_menu_props (copy, menu))
        protocol_send_resed_object_modified (menu);

    if (stayopen)
        return props_update_menu_dbox (menu, TRUE, FALSE);
    else
        return props_close_menu_dbox (menu);

  fail:
    /* release space occupied by the copy */
    compare_menu_props (copy, menu);
    return err;
}


/*
 * Respond to mouse clicks in a Menu Dbox
 */

error * props_menu_dbox_mouse_click (MouseClickPtr mouse, unsigned int modifiers, MenuObjPtr menu)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    WindowPtr dbox = menu->dbox;
    int dir = buttons == MB_CLICK(MB_ADJUST) ? -1 : 1;
    error *err = NULL;

    if (buttons == MB_CLICK(MB_SELECT) || buttons == MB_CLICK(MB_ADJUST))
    {
        switch (icon)
        {
        case I_MENUDBOX_CANCEL:
            if (dir == 1)
                return props_close_menu_dbox (menu);
            else
                err = props_update_menu_dbox (menu, TRUE, FALSE);
            break;
        case I_MENUDBOX_OK:
            err = apply_menu_dbox (menu, dir == -1);
            if (dir == 1)
                return err;
            break;
        case I_MENUDBOX_HASHELP:
            GUI_TOGGLE_FADE (dbox, I_MENUDBOX_HELP);
            if ( (dbox_getflags (dbox, I_MENUDBOX_HELP) & IF_SHADED)
                                   == 0 )
                dbox_place_caret (dbox, I_MENUDBOX_HELP);
            break;
        case I_MENUDBOX_TITLEMAX_ADJ_DOWN:
            err = gui_adjust_len (dbox,
                                  I_MENUDBOX_TITLEMAX,
                                  I_MENUDBOX_TITLE, -dir, modifiers);
            break;
        case I_MENUDBOX_TITLEMAX_ADJ_UP:
            err = gui_adjust_len (dbox,
                                  I_MENUDBOX_TITLEMAX,
                                  I_MENUDBOX_TITLE,  dir, modifiers);
            break;
        case I_MENUDBOX_HELPMAX_ADJ_DOWN:
            err = gui_adjust_len (dbox,
                                  I_MENUDBOX_HELPMAX,
                                  I_MENUDBOX_HELP, -dir, modifiers);
            break;
        case I_MENUDBOX_HELPMAX_ADJ_UP:
            err = gui_adjust_len (dbox,
                                  I_MENUDBOX_HELPMAX,
                                  I_MENUDBOX_HELP,  dir, modifiers);
            break;

        case I_MENUDBOX_BEFORE_DEFAULT:
        case I_MENUDBOX_BEFORE_NONE:
        case I_MENUDBOX_BEFORE_OTHER:
            if (dir == -1 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            dbox_shade (dbox, I_MENUDBOX_BEFORE_EVENT,
                        icon != I_MENUDBOX_BEFORE_OTHER);
            if (icon == I_MENUDBOX_BEFORE_OTHER)
                dbox_place_caret (dbox, I_MENUDBOX_BEFORE_EVENT);
            break;
        case I_MENUDBOX_HIDDEN_DEFAULT:
        case I_MENUDBOX_HIDDEN_NONE:
        case I_MENUDBOX_HIDDEN_OTHER:
            if (dir == -1 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            dbox_shade (dbox, I_MENUDBOX_HIDDEN_EVENT,
                        icon != I_MENUDBOX_HIDDEN_OTHER);
            if (icon == I_MENUDBOX_HIDDEN_OTHER)
                dbox_place_caret (dbox, I_MENUDBOX_HIDDEN_EVENT);
            break;
        }
    }

    /* give input focus to the dbox regardless - but not in a faded icon */
    dbox_set_caret_to (dbox, -1);

    return err;
}


/*
 * Respond to an Open_Window_request on a menu editing dbox
 */

error * props_reopen_menu_dbox (WindowPtr win, MenuObjPtr menu)
{
    if (menu->dbox == NULL)
        return NULL;
    menu->dbox->visarea = menudboxproto->visarea = win->visarea;
    menu->dbox->scrolloffset = menudboxproto->scrolloffset = win->scrolloffset;
    menu->dbox->behind = menudboxproto->behind = win->behind;
    return swi (Wimp_OpenWindow, R1, menu->dbox, END);
}


/*
 * Respond to a close window request on a menu editing dbox.  Also used
 * to implement the Cancel button.  Put the input focus and caret
 * into the window that owns us.
 */

error * props_close_menu_dbox (MenuObjPtr menu)
{
    if (menu->dbox)
    {
        /* deregister */
        ER ( registry_deregister_window (menu->dbox->handle) );

        /*
         * 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, menu->dbox->handle,
                                  R1, 0x44ec5,
                                  R2, taskhandle, END) );

        ER ( swi (Wimp_DeleteWindow,  R1, menu->dbox,  END) );
        free (menu->dbox);
        menu->dbox = NULL;
    }

    return menuedit_focus_claim (menu);
}


/*
 * A keystroke occurred in a Menu Dbox
 */

error * props_menu_dbox_key_pressed (MenuObjPtr menu, KeyPressPtr key, Bool *consumed)
{
    Bool keepopen = (wimp_read_modifiers() & MODIFIER_SHIFT) != 0;
    error *err = NULL;

    if (key->code == 13)         /* RETURN */
    {
        *consumed = TRUE;
        err = apply_menu_dbox (menu, keepopen);
        if (!keepopen)
            return err;
    }

    else if (key->code == 0x1b)  /* ESCAPE */
    {
        *consumed = TRUE;
        if (!keepopen)
            return props_close_menu_dbox (menu);
        else
            err = props_update_menu_dbox (menu, TRUE, FALSE);
    }

    /* ensure caret is not inside a faded icon */
    dbox_set_caret_to (menu->dbox, -1);

    return err;
}



/*
 **************************
 *** ENTRY DIALOGUE BOX ***
 **************************
 */


/*
 * Open/raise editing window for a menu entry.  Behaves like
 * open_menu_dbox();
 */

error * props_open_entry_dbox (MenuObjPtr menu, MenuEntryPtr entry)
{
    if (entry->separator)
        return NULL;
    else if (entry->dbox == NULL)
    {
        ER ( wimp_copy_template (entrydboxproto, &entry->dbox, entrydboxsize) );
        ER ( swi (Wimp_CreateWindow,  R1, &entry->dbox->visarea,
                  OUT,  R0, &entry->dbox->handle,  END) );
        ER ( registry_register_window (entry->dbox->handle, EntryDbox, (void *) entry) );
        entry->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, entry->dbox,  END) );
        ER ( props_update_entry_dbox (entry, TRUE, TRUE) );
    }
    else
    {
        entry->dbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, entry->dbox,  END) );
    }   

    return dbox_set_caret_to (entry->dbox, I_ENTRYDBOX_TEXT);
}


/*
 * Fill in the menu entry's dialogue box and refresh the screen if the
 * dbox is open.  Only do the contents if 'contents' is TRUE.  Only
 * do the titlebar is 'title' is TRUE.
 */

error * props_update_entry_dbox (MenuEntryPtr entry, Bool contents, Bool title)
{
    char buf[256];

    if (entry->separator)
        return NULL;
    if (contents)
    {
        Bool sprite = entry->p.flags & MENU_ENTRY_ISSPRITE;
        char *key, *padding;
        char *text = (entry->p.text) ? entry->p.text : "";

        ER ( dbox_sethex (entry->dbox, I_ENTRYDBOX_COMPONENT, entry->p.componentID) );

        ER ( dbox_setbutton (entry->dbox, I_ENTRYDBOX_ISTEXT, !sprite) );
        ER ( dbox_setbutton (entry->dbox, I_ENTRYDBOX_ISSPRITE, sprite) );
    
        if (sprite)
        {
            ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_TEXT, "") );
            ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_KEY, "") );
        }
        else
        {
            strcpy (buf, text);
            key = menuedit_find_keyname (buf, &padding);
            if (key)
                *padding = 0;
            ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_TEXT, buf) );
            ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_KEY, key) );
        }
        if (entry->p.maxtext == -1)
        {
            ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_TEXTMAX, "*") );
        }
        else
        {
            ER ( dbox_setint (entry->dbox, I_ENTRYDBOX_TEXTMAX, entry->p.maxtext) );
        }

        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_TEXT, sprite) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_KEY, sprite) );

        ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_SPRITE, sprite ? text : "") );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SPRITE, !sprite) );

        ER ( gui_put_len_opt_str (
                entry->dbox,
                I_ENTRYDBOX_HASHELP, I_ENTRYDBOX_HELP, I_ENTRYDBOX_HELPMAX,
                entry->p.helpmessage, entry->p.maxentryhelp) );

        GUI_PUT_FLAG (entry->dbox, I_ENTRYDBOX_TICKED,
                      entry->p.flags, MENU_ENTRY_TICKED);
        GUI_PUT_FLAG (entry->dbox, I_ENTRYDBOX_FADED,
                      entry->p.flags, MENU_ENTRY_FADED);
        GUI_PUT_FLAG (entry->dbox, I_ENTRYDBOX_SUBMENU,
                      entry->p.flags, MENU_ENTRY_SUBMENU);

        ER ( gui_put_mand_event (
                entry->dbox,
                I_ENTRYDBOX_CLICKEVENT_DEFAULT, I_ENTRYDBOX_CLICKEVENT_OTHER,
                I_ENTRYDBOX_CLICKEVENT_EVENT, entry->p.clickevent) );
        ER ( gui_put_opt_str (
                entry->dbox,
                I_ENTRYDBOX_CLICKSHOW_BUTTON, I_ENTRYDBOX_CLICKSHOW_OBJECT,
                entry->p.clickshow) );

        GUI_PUT_FLAG (entry->dbox, I_ENTRYDBOX_CLICKSHOW_TRANSIENT,
                      entry->p.flags, MENU_ENTRY_CLICKSHOWTRANSIENT);
        dbox_shade (entry->dbox, I_ENTRYDBOX_CLICKSHOW_TRANSIENT,
                    !dbox_getbutton (entry->dbox,
                                     I_ENTRYDBOX_CLICKSHOW_BUTTON));

        {
            Bool nosubmenu = (entry->p.flags & MENU_ENTRY_SUBMENU) == 0;

            dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_NONE, nosubmenu);
            dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_DEFAULT, nosubmenu);
            dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_OTHER, nosubmenu);
            dbox_shade (entry->dbox, I_ENTRYDBOX_SUBSHOW_BUTTON, nosubmenu);

            ER ( gui_put_opt_event (
                    entry->dbox,
                    I_ENTRYDBOX_SUBEVENT_DEFAULT, I_ENTRYDBOX_SUBEVENT_OTHER,
                    I_ENTRYDBOX_SUBEVENT_NONE, I_ENTRYDBOX_SUBEVENT_EVENT,
                    entry->p.submenuevent,
                    (entry->p.flags & MENU_ENTRY_SUBMENUEVENT) == 0) );
            ER ( gui_put_opt_str (
                    entry->dbox,
                    I_ENTRYDBOX_SUBSHOW_BUTTON, I_ENTRYDBOX_SUBSHOW_OBJECT,
                    entry->p.submenushow) );

            if (nosubmenu)
            {
                dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_EVENT, TRUE);
                dbox_shade (entry->dbox, I_ENTRYDBOX_SUBSHOW_OBJECT, TRUE);
            }
        }
    }
    if (title)
    {
        /* Get the printf pattern from the prototype window's titlebar which
         * should have %d (or &%x) %s in it, in that order, for the component ID
         * and the menu name.
         */
        sprintf (buf, dbox_gettitle (entrydboxproto), entry->p.componentID, entry->owner->name);
        return dbox_settitle (entry->dbox, buf, TRUE);
    }
    return NULL;
}


/*
 * Creates a copy of the properties which may be altered by the Menu entry
 *  Properties dialogue.
 *
 * Result is NULL if insufficient memory is available.
 */

static EntryPropsPtr entry_props_copy (MenuEntryPtr entry)
{
    EntryPropsPtr copy = (EntryPropsPtr) malloc (sizeof (EntryPropsRec));
    error *err = NULL;

    if (copy == NULL)
        return NULL;

    /* copy the record as a whole */
    *copy = entry->p;

    /* and now clone each of the strings if necessary */
    EG ( nomem, clonestring (&copy->text) );
    EG ( nomem, clonestring (&copy->clickshow) );
    EG ( nomem, clonestring (&copy->submenushow) );
    EG ( nomem, clonestring (&copy->helpmessage) );

    return copy;

  nomem:
    free (copy->text);
    free (copy->clickshow);
    free (copy->submenushow);
    free (copy->helpmessage);
    free (copy);
    return NULL;
}


/*
 * Compares the original menu entry property values with the new values;
 *  result is TRUE iff they are all the same.
 *
 * The space occupied by the copy ('old') is released.
 */

static Bool compare_entry_props (EntryPropsPtr old, MenuEntryPtr entry)
{
    Bool ok = TRUE;

    /* compare and free string fields */
    if (!equalstrings (old->text, entry->p.text))
        ok = FALSE;
    free (old->text);
    old->text = entry->p.text;   /* ready for bulk compare! */

    if (!equalstrings (old->clickshow, entry->p.clickshow))
        ok = FALSE;
    free (old->clickshow);
    old->clickshow = entry->p.clickshow;

    if (!equalstrings (old->submenushow, entry->p.submenushow))
        ok = FALSE;
    free (old->submenushow);
    old->submenushow = entry->p.submenushow;

    if (!equalstrings (old->helpmessage, entry->p.helpmessage))
        ok = FALSE;
    free (old->helpmessage);
    old->helpmessage = entry->p.helpmessage;

    /* bulk comparison and free */
    if (memcmp ((char *)old, (char *)&entry->p, sizeof(EntryPropsRec)) != 0)
        ok = FALSE;
    free (old);

    return ok;
}


/*
 * Apply the changes in a MenuEntry dbox and redraw the menu window
 */

static error * apply_entry_dbox (MenuEntryPtr entry, Bool stayopen)
{
    int len, max;
    char *val, *key;
    MenuEntryPtr check;
    int oldID = entry->p.componentID;
    int componentID = dbox_getint (entry->dbox, I_ENTRYDBOX_COMPONENT);
    WindowPtr dbox = entry->dbox;
    EntryPropsPtr copy = entry_props_copy (entry);
    error *err = NULL;

    /* check that the copy was successfully made */
    if (copy == NULL)
        return error_lookup ("NoMem");

    if (componentID != oldID)
    {
        for (check = entry->owner->entries; check; check = check->next)
            if (check != entry && check->p.componentID == componentID)
            {
                err = error_lookup ("CompID", dbox_getstring (dbox, I_ENTRYDBOX_COMPONENT));
                goto fail;
            }
        entry->p.componentID = componentID;
    }

    if (dbox_getbutton (dbox, I_ENTRYDBOX_ISTEXT))
    {
        val = dbox_getstring (dbox, I_ENTRYDBOX_TEXT);
        key = dbox_getstring (dbox, I_ENTRYDBOX_KEY);
        remove_multiple_spaces (val);
        if (strlen (key) != 0)
            len = strlen (val) + 2 + strlen (key) + 1;
        else
            len = strlen (val) + 1;
        if (entry->p.text == NULL || strlen (entry->p.text) + 1 < len)
        {
            char *newval = malloc (len);
            if (newval == NULL)
            {
                err = error_lookup ("NoMem");
                goto fail;
            }
            free (entry->p.text);
            entry->p.text = newval;
        }
        strcpy (entry->p.text, val);
        if (strlen (key) != 0)
        {
            strcat (entry->p.text, "  ");  /* Insert two spaces for now.  menuedit_justify_keycuts will */
            strcat (entry->p.text, key);   /* sort out the correct number for us */
        }
        entry->p.flags &= ~MENU_ENTRY_ISSPRITE;
    }
    else
    {
        val = dbox_getstring (dbox, I_ENTRYDBOX_SPRITE);
        len = strlen (val) + 1;
        if (entry->p.text == NULL || strlen (entry->p.text) + 1 < len)
        {
            char *newval = malloc (len);
            if (newval == NULL)
            {
                err = error_lookup ("NoMem");
                goto fail;
            }
            free (entry->p.text);
            entry->p.text = newval;
        }
        strcpy (entry->p.text, val);
        entry->p.flags |= MENU_ENTRY_ISSPRITE;
    }

    /* replace any empty string by NULL */
    if (*(entry->p.text) == 0)
    {
        free (entry->p.text);
        entry->p.text = NULL;
    }

    {
        char *s = dbox_getstring (dbox, I_ENTRYDBOX_TEXTMAX);

        if (*s == '*')
            entry->p.maxtext = -1;
        else
        {
            max = dbox_getint (dbox, I_ENTRYDBOX_TEXTMAX);
            if (len > max)
                max = len;
            entry->p.maxtext = max;
        }
    }
        
    EG ( fail, gui_get_len_opt_str (
            dbox,
            I_ENTRYDBOX_HASHELP, I_ENTRYDBOX_HELP, I_ENTRYDBOX_HELPMAX,
            &entry->p.helpmessage, &entry->p.maxentryhelp) );

    GUI_GET_FLAG (dbox,
                  I_ENTRYDBOX_TICKED,
                  entry->p.flags, MENU_ENTRY_TICKED);
    GUI_GET_FLAG (dbox,
                  I_ENTRYDBOX_SUBMENU,
                  entry->p.flags, MENU_ENTRY_SUBMENU);
    GUI_GET_FLAG (dbox,
                  I_ENTRYDBOX_FADED,
                  entry->p.flags, MENU_ENTRY_FADED);
    GUI_GET_FLAG (dbox,
                  I_ENTRYDBOX_CLICKSHOW_TRANSIENT,
                  entry->p.flags, MENU_ENTRY_CLICKSHOWTRANSIENT);

    EG ( fail, gui_get_mand_event (
            dbox,
            I_ENTRYDBOX_CLICKEVENT_DEFAULT, I_ENTRYDBOX_CLICKEVENT_EVENT,
            &entry->p.clickevent) );
    EG ( fail, gui_get_opt_str (
            dbox,
            I_ENTRYDBOX_CLICKSHOW_BUTTON, I_ENTRYDBOX_CLICKSHOW_OBJECT,
            &entry->p.clickshow) );

    EG ( fail, gui_get_opt_event (
            dbox,
            I_ENTRYDBOX_SUBEVENT_DEFAULT, I_ENTRYDBOX_SUBEVENT_NONE,
            I_ENTRYDBOX_SUBEVENT_EVENT, &entry->p.submenuevent,
            &entry->p.flags, MENU_ENTRY_SUBMENUEVENT) );
    EG ( fail, gui_get_opt_str (
            dbox,
            I_ENTRYDBOX_SUBSHOW_BUTTON, I_ENTRYDBOX_SUBSHOW_OBJECT,
            &entry->p.submenushow) );

    EG ( fail, menuedit_justify_keycuts (entry->owner) );
    EG ( fail, menuedit_fix_extent (entry->owner) );

    if (!compare_entry_props (copy, entry))
        protocol_send_resed_object_modified (entry->owner);

    if (stayopen)
        return props_update_entry_dbox (entry, TRUE, componentID != oldID);
    else
        return props_close_entry_dbox (entry);

  fail:
    /* release space occupied by the copy */
    compare_entry_props (copy, entry);
    return err;
}


/*
 * Respond to mouse clicks in an Entry Dbox
 */

error * props_entry_dbox_mouse_click (MouseClickPtr mouse, unsigned int modifiers, MenuEntryPtr entry)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    WindowPtr dbox = entry->dbox;
    int dir = buttons == MB_CLICK(MB_ADJUST) ? -1 : 1;
    Bool state;
    error *err = NULL;

    /* give input focus to the dbox regardless */
    dbox_set_caret_to (dbox, -1);

    if (buttons == MB_CLICK(MB_SELECT) || buttons == MB_CLICK(MB_ADJUST))
    {
        switch (icon)
        {
        case I_ENTRYDBOX_CANCEL:
            if (dir == 1)
                return props_close_entry_dbox (entry);
            else
                err = props_update_entry_dbox (entry, TRUE, FALSE);
            break;
        case I_ENTRYDBOX_OK:
            err = apply_entry_dbox (entry, dir == -1);
            if (dir == 1)
                return err;
            break;

        case I_ENTRYDBOX_COMPONENT_ADJ_DOWN:
            err = gui_adjust_len (dbox,
                                  I_ENTRYDBOX_COMPONENT,
                                  -1,  -dir, modifiers);
            break;
        case I_ENTRYDBOX_COMPONENT_ADJ_UP:
            err = gui_adjust_len (dbox,
                                  I_ENTRYDBOX_COMPONENT,
                                  -1,   dir, modifiers);
            break;
        case I_ENTRYDBOX_TEXTMAX_ADJ_DOWN:
            err = gui_adjust_len (dbox,
                                  I_ENTRYDBOX_TEXTMAX,
                       dbox_getbutton (dbox, I_ENTRYDBOX_ISTEXT) ?
                                  I_ENTRYDBOX_TEXT : I_ENTRYDBOX_SPRITE,
                                  -dir, modifiers);
            break;
        case I_ENTRYDBOX_TEXTMAX_ADJ_UP:
            err = gui_adjust_len (dbox,
                                  I_ENTRYDBOX_TEXTMAX,
                       dbox_getbutton (dbox, I_ENTRYDBOX_ISTEXT) ?
                                  I_ENTRYDBOX_TEXT : I_ENTRYDBOX_SPRITE,
                                   dir, modifiers);
            break;
        case I_ENTRYDBOX_HELPMAX_ADJ_DOWN:
            err = gui_adjust_len (dbox,
                                  I_ENTRYDBOX_HELPMAX,
                                  I_ENTRYDBOX_HELP, -dir, modifiers);
            break;
        case I_ENTRYDBOX_HELPMAX_ADJ_UP:
            err = gui_adjust_len (dbox,
                                  I_ENTRYDBOX_HELPMAX,
                                  I_ENTRYDBOX_HELP,  dir, modifiers);
            break;

        case I_ENTRYDBOX_ISTEXT:
        case I_ENTRYDBOX_ISSPRITE:
            state = dbox_getbutton (dbox, I_ENTRYDBOX_ISSPRITE);
            dbox_shade (dbox, I_ENTRYDBOX_TEXT, state);
            dbox_shade (dbox, I_ENTRYDBOX_KEY, state);
            dbox_shade (dbox, I_ENTRYDBOX_SPRITE, !state);
            dbox_place_caret (dbox,
                              state ? I_ENTRYDBOX_SPRITE : I_ENTRYDBOX_TEXT);
            break;

        case I_ENTRYDBOX_HASHELP:
            GUI_TOGGLE_FADE (dbox, I_ENTRYDBOX_HELP);
            if ( (dbox_getflags (dbox, I_ENTRYDBOX_HELP) & IF_SHADED) == 0 )
                dbox_place_caret (dbox, I_ENTRYDBOX_HELP);
            break;

        case I_ENTRYDBOX_SUBMENU:
            state = dbox_getbutton (dbox, I_ENTRYDBOX_SUBMENU);
            dbox_shade (dbox, I_ENTRYDBOX_SUBEVENT_DEFAULT, !state);
            dbox_shade (dbox, I_ENTRYDBOX_SUBEVENT_NONE, !state);
            dbox_shade (dbox, I_ENTRYDBOX_SUBEVENT_OTHER, !state);
            {
                Bool shadeevent = !state || 
                  dbox_getbutton (dbox, I_ENTRYDBOX_SUBEVENT_OTHER) == FALSE;
                Bool shadeobject = !state ||
                  dbox_getbutton (dbox, I_ENTRYDBOX_SUBSHOW_BUTTON) == FALSE;

                dbox_shade (dbox, I_ENTRYDBOX_SUBEVENT_EVENT, shadeevent);
                dbox_shade (dbox, I_ENTRYDBOX_SUBSHOW_BUTTON, !state);
                dbox_shade (dbox, I_ENTRYDBOX_SUBSHOW_OBJECT, shadeobject);

                if (!shadeevent)
                    dbox_place_caret (dbox, I_ENTRYDBOX_SUBEVENT_EVENT);
                else
                if (!shadeobject)
                    dbox_place_caret (dbox, I_ENTRYDBOX_SUBSHOW_OBJECT);
            }
            break;

        case I_ENTRYDBOX_CLICKEVENT_OTHER:
        case I_ENTRYDBOX_CLICKEVENT_DEFAULT:
            if (dir == -1 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            dbox_shade (dbox, I_ENTRYDBOX_CLICKEVENT_EVENT,
                        icon != I_ENTRYDBOX_CLICKEVENT_OTHER);
            if (icon == I_ENTRYDBOX_CLICKEVENT_OTHER)
                dbox_place_caret (dbox, I_ENTRYDBOX_CLICKEVENT_EVENT);
            break;

        case I_ENTRYDBOX_CLICKSHOW_BUTTON:
            GUI_TOGGLE_FADE (dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT);
            GUI_TOGGLE_FADE (dbox, I_ENTRYDBOX_CLICKSHOW_TRANSIENT);
            if ( (dbox_getflags (dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT) &
                                                          IF_SHADED) == 0 )
                dbox_place_caret (dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT);
            break;

        case I_ENTRYDBOX_SUBEVENT_OTHER:
        case I_ENTRYDBOX_SUBEVENT_DEFAULT:
        case I_ENTRYDBOX_SUBEVENT_NONE:
            if (dir == -1 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            dbox_shade (dbox, I_ENTRYDBOX_SUBEVENT_EVENT,
                        icon != I_ENTRYDBOX_SUBEVENT_OTHER);
            if (icon == I_ENTRYDBOX_SUBEVENT_OTHER)
                dbox_place_caret (dbox, I_ENTRYDBOX_SUBEVENT_EVENT);
            break;

        case I_ENTRYDBOX_SUBSHOW_BUTTON:
            GUI_TOGGLE_FADE (dbox, I_ENTRYDBOX_SUBSHOW_OBJECT);
            if ( (dbox_getflags (dbox, I_ENTRYDBOX_SUBSHOW_OBJECT) &
                                                        IF_SHADED) == 0 )
                dbox_place_caret (dbox, I_ENTRYDBOX_SUBSHOW_OBJECT);
            break;
        }
    }

    /* give input focus to the dbox regardless - but not in a faded icon */
    dbox_set_caret_to (dbox, -1);

    return err;
}


/*
 * Respond to an Open_Window_request on an entry editing dbox
 */

error * props_reopen_entry_dbox (WindowPtr win, MenuEntryPtr entry)
{
    if (entry->dbox == NULL)
        return NULL;
    entry->dbox->visarea = entrydboxproto->visarea = win->visarea;
    entry->dbox->scrolloffset = entrydboxproto->scrolloffset = win->scrolloffset;
    entry->dbox->behind = entrydboxproto->behind = win->behind;
    return swi (Wimp_OpenWindow, R1, entry->dbox, END);
}


/*
 * Respond to a close window request on a entry editing dbox.  Also used
 * to implement the Cancel button.
 */

error * props_close_entry_dbox (MenuEntryPtr entry)
{
    if (entry->dbox)
    {
        /* deregister */
        ER ( registry_deregister_window (entry->dbox->handle) );

        /*
         * 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, entry->dbox->handle,
                                  R1, 0x44ec5,
                                  R2, taskhandle, END) );

        ER ( swi (Wimp_DeleteWindow,  R1, entry->dbox,  END) );
        free (entry->dbox);
        entry->dbox = NULL;
    }

    return menuedit_focus_claim (entry->owner);
}


/*
 * Returns TRUE iff the icon is one onto which objects can be dropped.
 *
 * Called from protocol_send_resed_object_name_request(..).
 */

Bool props_drop_icon (int icon)
{
    switch (icon)
    {
    case I_ENTRYDBOX_CLICKSHOW_BUTTON:
    case I_ENTRYDBOX_CLICKSHOW_OBJECT:

    case I_ENTRYDBOX_SUBMENU:
    case I_ENTRYDBOX_SUBSHOW_BUTTON:
    case I_ENTRYDBOX_SUBSHOW_OBJECT:
        return TRUE;

    default:
        return FALSE;
    }
}


/*
 * We have been sent RESED_OBJECT_NAME in response to our RESED_OBJECT_NAME_REQUEST.
 * The user is trying to fill in an object name in one of our dboxes by drag-and-drop from
 * the shell.  Using  windowhandle and iconhandle to identify where the object was
 * dropped, amend the appropriate writable field of the appropriate dbox to show
 * the name.  The class parameter can be used to reject unsuitable objects, in which case
 * the operation should be ignored (NYI: we just ignore class parameter at the moment).
 */

error * props_fill_in_object_name (MenuObjPtr menu, int windowhandle,
                                   int iconhandle, ObjectClass class, char *name)
{
    MenuEntryPtr entry;
    if (registry_lookup_window (windowhandle, (void **) &entry) != EntryDbox)
        return NULL;
        
    switch (iconhandle)
    {
    case I_ENTRYDBOX_CLICKSHOW_BUTTON:
    case I_ENTRYDBOX_CLICKSHOW_OBJECT:
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT, FALSE) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_CLICKSHOW_TRANSIENT, FALSE) );
        ER ( dbox_setbutton (entry->dbox, I_ENTRYDBOX_CLICKSHOW_BUTTON, TRUE) );
        ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT, name) );
        break;
    case I_ENTRYDBOX_SUBMENU:
    case I_ENTRYDBOX_SUBSHOW_BUTTON:
    case I_ENTRYDBOX_SUBSHOW_OBJECT:
        ER ( dbox_setbutton (entry->dbox, I_ENTRYDBOX_SUBMENU, TRUE) );

        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_NONE, FALSE) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_DEFAULT, FALSE) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_OTHER, FALSE) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SUBEVENT_EVENT,
                         dbox_getbutton (entry->dbox, I_ENTRYDBOX_SUBEVENT_OTHER) == FALSE) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SUBSHOW_BUTTON, FALSE) );
        ER ( dbox_shade (entry->dbox, I_ENTRYDBOX_SUBSHOW_OBJECT, FALSE) );
                         
        ER ( dbox_setbutton (entry->dbox, I_ENTRYDBOX_SUBSHOW_BUTTON, TRUE) );
        ER ( dbox_setstring (entry->dbox, I_ENTRYDBOX_SUBSHOW_OBJECT, name) );
        break;
    }

    return NULL;
}


/*
 * Called to update keyboard shortcut details when a KEYCUT_DETAILS message
 *  has been received.
 */

error * props_enter_keycut_details (
    MenuEntryPtr entry,
    MessageResEdKeycutDetailsPtr keycut
)
{
    WindowPtr dbox = entry->dbox;

    if (dbox_getbutton (dbox, I_ENTRYDBOX_ISTEXT)) /* must be a text entry */
    {
        Bool hasevent = (keycut->flags & KEYCUT_DETAILS_DELIVER_EVENT) != 0;
        Bool hasshow = (keycut->flags & KEYCUT_DETAILS_SHOW_OBJECT) != 0;
        Bool transient =
            (keycut->flags & KEYCUT_DETAILS_SHOW_AS_TRANSIENT) != 0;
        char *objname = keycut->names + strlen (keycut->names) + 1;

        dbox_setstring (dbox, I_ENTRYDBOX_KEY, keycut->names);
        dbox_setbutton (dbox, I_ENTRYDBOX_CLICKEVENT_DEFAULT, !hasevent);
        dbox_setbutton (dbox, I_ENTRYDBOX_CLICKEVENT_OTHER, hasevent);
        dbox_shade (dbox, I_ENTRYDBOX_CLICKEVENT_EVENT, !hasevent);
        if (hasevent)
            dbox_sethex (dbox, I_ENTRYDBOX_CLICKEVENT_EVENT,
                                             keycut->eventcode);
        dbox_setbutton (dbox, I_ENTRYDBOX_CLICKSHOW_BUTTON, hasshow);
        dbox_shade (dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT, !hasshow);
        dbox_shade (dbox, I_ENTRYDBOX_CLICKSHOW_TRANSIENT, !hasshow);
        if (hasshow)
        {
            dbox_setstring (dbox, I_ENTRYDBOX_CLICKSHOW_OBJECT, objname);
            dbox_setbutton (dbox,
                            I_ENTRYDBOX_CLICKSHOW_TRANSIENT, transient);
        }
    }

    return NULL;
}

 
/*
 * A keystroke occurred in an Entry Dbox
 */

error * props_entry_dbox_key_pressed (MenuEntryPtr entry, KeyPressPtr key, Bool *consumed)
{
    Bool keepopen = (wimp_read_modifiers() & MODIFIER_SHIFT) != 0;
    error *err = NULL;

    if (key->code == 13)         /* RETURN */
    {
        *consumed = TRUE;
        err = apply_entry_dbox (entry, keepopen);
        if (!keepopen)
            return err;
    }

    else if (key->code == 0x1b)  /* ESCAPE */
    {
        *consumed = TRUE;
        if (!keepopen)
            props_close_entry_dbox (entry);
        else
            err = props_update_entry_dbox (entry, TRUE, FALSE);
    }

    /* ensure caret is not inside a faded icon */
    dbox_set_caret_to (entry->dbox, -1);

    return err;
}
