/*-*-C-*-
 * Window properties dialogue box for Windows 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 "windowedit.h"
#include "colours.h"
#include "gui.h"
#include "icondefs.h"
#include "props.h"
#include "protocol.h"


typedef struct
{
    WindowPtr dbox;
    int icon;
} ButtClosRec, *ButtClosPtr;

static MenuPtr buttonmenu = NULL;
static ButtClosRec buttonclosure;  /* used to pass information to the button
                                      type menu callback fuction */

static WindowPtr mainpropsdboxproto, otherpropsdboxproto;
static int mainpropsdboxsize, otherpropsdboxsize;


/*
 * Load any templates required - called from load_prototypes(..) in main.c
 *
 * Also creates a menu for "button types".
 */

error * props_load_prototypes (void)

{
    /* create button type menu */
    {
        int i;
        ER ( menu_create (16, message_lookup (&msgs, "ButType"),
                                                     &buttonmenu) );
        for (i = 0; i < 16; i++)
        {
            char tag[20], buf[100];
            sprintf (tag, "ButType%d", i);
            sprintf (buf, "%2d %s", i, message_lookup (&msgs, tag));
            ER ( menu_entry (buttonmenu, i, buf, 0, 0, -1, -1, NULL) );
        }

        /* register menu for interactive help */
        ER ( menu_register (buttonmenu, ICON_BUTTON_MENU) );
    }

    /* and load Properties templates */
    ER ( wimp_load_template_returning_size (
                  "MainProps", &mainpropsdboxproto, &mainpropsdboxsize) );
    return wimp_load_template_returning_size (
                  "OtherProps", &otherpropsdboxproto, &otherpropsdboxsize);
}


/*
 * Creates a copy of the properties which can be altered by either of the
 *  Properties dialogues.
 *
 * Result is NULL if insufficient memory is available.
 */

static PropsPtr props_copy (WindowObjPtr window)
{
    PropsPtr copy = (PropsPtr) malloc (sizeof (PropsRec));
    error *err = NULL;

    if (copy == NULL)
        return NULL;

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

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

    return copy;

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


/*
 * Compares the original 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_props (PropsPtr old, WindowObjPtr window)
{
    Bool ok = TRUE;

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

    if (!equalstrings (old->pointershape, window->p.pointershape))
        ok = FALSE;
    free (old->pointershape);
    old->pointershape = window->p.pointershape; /* ready for bulk compare! */

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

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

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

    return ok;
}


/*
 * Called by props_main_dbox_mouse_click(..) or 
 *           props_main_dbox_key_pressed(..) after
 * the user has indicated that he wishes to apply the contents of the dbox.
 */

static error * apply_main_dbox (WindowObjPtr window, Bool stayopen)
{
    WindowPtr dbox = window->mainpropsdbox;
    WindowPtr win = window->window;
    PropsPtr copy = props_copy (window);
    unsigned int oldtitleflags = win->titleflags;
    error *err = NULL;

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

    /* title */
    EG ( fail, gui_get_len_opt_str (dbox, I_MAINPROPS_HASTITLE,
                                    I_MAINPROPS_TITLE,
                                    I_MAINPROPS_TITLEMAX,
                                    &window->p.title, &window->p.maxtitle) );
    if (window->p.title == NULL)
        window->p.windowflags &= ~WF_TITLE;
    else
        window->p.windowflags |= WF_TITLE;
 
    /* wimp windowflags */
    GUI_GET_FLAG (dbox, I_MAINPROPS_BACK, window->p.windowflags, WF_BACK);
    GUI_GET_FLAG (dbox, I_MAINPROPS_CLOSE, window->p.windowflags, WF_CLOSE);
    GUI_GET_FLAG (dbox, I_MAINPROPS_TOGGLE, window->p.windowflags, WF_TOGGLE);
    GUI_GET_FLAG (dbox, I_MAINPROPS_VSCROLL, window->p.windowflags,
                        WF_VSCROLL);
    GUI_GET_FLAG (dbox, I_MAINPROPS_HSCROLL, window->p.windowflags,
                        WF_HSCROLL);
    GUI_GET_FLAG (dbox, I_MAINPROPS_SIZE, window->p.windowflags, WF_RESIZE);

    /* menu */
    EG ( fail, gui_get_opt_str (dbox, I_MAINPROPS_HASMENU,
                                I_MAINPROPS_MENU, &window->p.menu) );

    /* toolbox window flags */
    GUI_GET_FLAG (dbox, I_MAINPROPS_AUTOOPEN, window->p.flags,
                        WINDOW_AUTOOPEN);
    GUI_GET_FLAG (dbox, I_MAINPROPS_AUTOCLOSE, window->p.flags,
                        WINDOW_AUTOCLOSE);

    /* toolbox show/hide event options */
    gui_get_opt_event (dbox, I_MAINPROPS_BEFORE_DEFAULT,
                             I_MAINPROPS_BEFORE_NONE,
                             I_MAINPROPS_BEFORE_EVENT,
                             &window->p.showevent,
                             &window->p.flags,
                             WINDOW_GENERATEABOUTTOBESHOWN);
    gui_get_opt_event (dbox, I_MAINPROPS_HIDDEN_DEFAULT,
                             I_MAINPROPS_HIDDEN_NONE,
                             I_MAINPROPS_HIDDEN_EVENT,
                             &window->p.hideevent,
                             &window->p.flags,
                             WINDOW_GENERATEHASBEENHIDDEN);

    /* default focus */
    if (dbox_getbutton (dbox, I_MAINPROPS_FOCUS_NONE))
        window->p.defaultfocus = DEFAULTFOCUS_NONE;
    else if (dbox_getbutton (dbox, I_MAINPROPS_FOCUS_INVISIBLE))
        window->p.defaultfocus = DEFAULTFOCUS_INVISIBLE;
    else
        window->p.defaultfocus = dbox_getint (dbox, I_MAINPROPS_FOCUS_ID);

    /* help message */
    EG ( fail, gui_get_len_opt_str (dbox, I_MAINPROPS_HASHELP,
                                    I_MAINPROPS_HELP,
                                    I_MAINPROPS_HELPMAX,
                                    &window->p.helpmessage,
                                    &window->p.maxhelp) );

    /* title justification (titleflags) */
    GUI_GET_FLAG (dbox, I_MAINPROPS_JUSTIFY_RIGHT, win->titleflags,
                        IF_RJUST);
    GUI_GET_FLAG (dbox, I_MAINPROPS_JUSTIFY_CENTRE, win->titleflags,
                        IF_HCENT);


    /* copy appropriate new flag values etc. from window fields into the
       wimp window */
    win->flags = (window->p.windowflags & ~WF_FORCE0_FOR_RESED) |
                                                   WF_FORCE1_FOR_RESED;
    if (window->p.title != NULL)
    {
        win->titledata[0] = (int) window->p.title;
        win->titledata[2] = window->p.maxtitle;
    }
    else
    {
        win->titledata[0] = (int) "";
        win->titledata[2] = 1;
    }

    if (!compare_props (copy, window) || oldtitleflags != win->titleflags)
    {
        /* delete and recreate window to reflect changes made */
        windowedit_redisplay_window (window);

        /* inform shell that window object has been modified */
        protocol_send_resed_object_modified (window);
    }

    /* redisplay Properties dbox if ADJ click - otherwise destroy it */
    if (stayopen)
        return props_update_main_dbox (window, TRUE, FALSE);
    else
        return props_close_main_dbox (window);

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


/*
 * Called by props_other_dbox_mouse_click(..) or 
 *           props_other_dbox_key_pressed(..) after
 * the user has indicated that he wishes to apply the contents of the dbox.
 */

static error * apply_other_dbox (WindowObjPtr window, Bool stayopen)
{
    WindowPtr dbox = window->otherpropsdbox;
    WindowPtr win = window->window;
    Bool realcoloursflagchanged = FALSE;
    PropsPtr copy = props_copy (window);
    error *err = NULL;

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

    /* wimp windowflags */
    GUI_GET_FLAG (dbox, I_OTHERPROPS_PANE, window->p.windowflags, WF_PANE);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_MOVEABLE, window->p.windowflags,
                        WF_MOVEABLE);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_BACKDROP, window->p.windowflags,
                        WF_BACKDROP);

    /* must change any open colours dbox if this flag changes */
    {
        unsigned int oldval = (window->p.windowflags & WF_GCOL);

        GUI_GET_FLAG (dbox, I_OTHERPROPS_REALCOLOURS, window->p.windowflags,
                            WF_GCOL);

        realcoloursflagchanged = oldval != (window->p.windowflags & WF_GCOL);
    }

    GUI_GET_FLAG (dbox, I_OTHERPROPS_HOTKEYS, window->p.windowflags,
                        WF_HOTKEYS);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_AUTOREDRAW, window->p.windowflags,
                        WF_AUTO_REDRAW);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_OFFSCREEN, window->p.windowflags,
                        WF_ALLOW_OFF);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_ONSCREEN, window->p.windowflags,
                        WF_KEEP_ON);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_FLEXIBLE_X, window->p.windowflags,
                        WF_FREE_RIGHT);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_FLEXIBLE_Y, window->p.windowflags,
                        WF_FREE_BOTTOM);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_USERSCROLL_AUTO, window->p.windowflags,
                        WF_USER_SCROLL);
    GUI_GET_FLAG (dbox, I_OTHERPROPS_USERSCROLL_DEBOUNCE,
                        window->p.windowflags, WF_NO_REPEAT);

    /* button type */
    window->p.buttontype = dbox_getint (dbox, I_OTHERPROPS_BUTTONTYPE);

    /* pointershape */
    EG (fail, gui_get_len_opt_str (dbox, I_OTHERPROPS_HASPOINTER,
                                    I_OTHERPROPS_POINTER,
                                    I_OTHERPROPS_POINTERMAX,
                                    &window->p.pointershape,
                                    &window->p.maxpointershape) );
    if (window->p.pointershape != NULL)
    {
        window->p.pointerxhot = dbox_getint (dbox, I_OTHERPROPS_HOTSPOT_X);
        window->p.pointeryhot = dbox_getint (dbox, I_OTHERPROPS_HOTSPOT_Y);
    }

    /* sprite area */
    if (dbox_getbutton (dbox, I_OTHERPROPS_SPRITEAREA_CLIENT))
        window->p.spritearea = (void *) CLIENT_SPRITEAREA;
    if (dbox_getbutton (dbox, I_OTHERPROPS_SPRITEAREA_WIMP))
        window->p.spritearea = (void *) WIMP_SPRITEAREA;

    /* copy appropriate new flag values etc. from window fields into the
       wimp window */
    win->flags = (window->p.windowflags & ~WF_FORCE0_FOR_RESED) |
                                                   WF_FORCE1_FOR_RESED;

    if (!compare_props (copy, window))
    {
        /* delete and recreate window to reflect changes made */
        windowedit_redisplay_window (window);

        /* inform shell that window object has been modified */
        protocol_send_resed_object_modified (window);
    }

    /* redisplay Properties dbox if ADJ click - otherwise destroy it */
    if (stayopen)
    {
        ER ( props_update_other_dbox (window, TRUE, FALSE) );
    }
    else
    {
        ER ( props_close_other_dbox (window) );
    }

    /* and - finally - update any colours dbox on display if necessary */
    if (realcoloursflagchanged && window->coloursdbox != NULL)
        return colours_redesign_dbox (window);
    else
        return NULL;

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


/*
 * Update contents of dialogue box - only called if dialogue box exists.
 *
 * Called from windowedit_rename_window(..) - when the object is renamed.
 *        from windowedit_load(..) - when a new version of the object is
 *                                    force loaded [not at present]
 *        from apply_main_dbox(..) - if the properties box is to remain on
 *                                    view
 *        from props_open_main_dbox(..) - after the dbox is created
 *        from props_main_dbox_mouse_click(..) - after an ADJ-CANCEL click
 */

error * props_update_main_dbox (
    WindowObjPtr window,
    Bool contents,
    Bool title
)
{
    WindowPtr dbox = window->mainpropsdbox;

    if (contents)
    {
        unsigned int flags = window->p.windowflags;
        unsigned int tflags = window->p.flags;
        unsigned int titleflags = window->window->titleflags;

        /* title */
        gui_put_len_opt_str (dbox, I_MAINPROPS_HASTITLE,
                                   I_MAINPROPS_TITLE,
                                   I_MAINPROPS_TITLEMAX,
                                   window->p.title, window->p.maxtitle);

        /* wimp windowflags */
        GUI_PUT_FLAG (dbox, I_MAINPROPS_BACK, flags, WF_BACK);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_CLOSE, flags, WF_CLOSE);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_TOGGLE, flags, WF_TOGGLE);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_VSCROLL, flags, WF_VSCROLL);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_HSCROLL, flags, WF_HSCROLL);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_SIZE, flags, WF_RESIZE);

        /* menu */
        gui_put_opt_str (dbox, I_MAINPROPS_HASMENU,
                               I_MAINPROPS_MENU, window->p.menu);

        /* toolbox window flags */
        GUI_PUT_FLAG (dbox, I_MAINPROPS_AUTOOPEN, tflags, WINDOW_AUTOOPEN);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_AUTOCLOSE,
                              tflags, WINDOW_AUTOCLOSE);

        /* toolbox show/hide event options */
        gui_put_opt_event (dbox,
                           I_MAINPROPS_BEFORE_DEFAULT,
                           I_MAINPROPS_BEFORE_OTHER,
                           I_MAINPROPS_BEFORE_NONE,
                           I_MAINPROPS_BEFORE_EVENT,
                           window->p.showevent,
                           (tflags & WINDOW_GENERATEABOUTTOBESHOWN) == 0);
        gui_put_opt_event (dbox,
                           I_MAINPROPS_HIDDEN_DEFAULT,
                           I_MAINPROPS_HIDDEN_OTHER,
                           I_MAINPROPS_HIDDEN_NONE,
                           I_MAINPROPS_HIDDEN_EVENT,
                           window->p.hideevent,
                           (tflags & WINDOW_GENERATEHASBEENHIDDEN) == 0);

        /* default focus */
        {
            int focus = window->p.defaultfocus;

            dbox_setbutton (dbox, I_MAINPROPS_FOCUS_NONE,
                                  focus == DEFAULTFOCUS_NONE);
            dbox_setbutton (dbox, I_MAINPROPS_FOCUS_INVISIBLE,
                                  focus == DEFAULTFOCUS_INVISIBLE);
            dbox_setbutton (dbox, I_MAINPROPS_FOCUS_GADGET,
                                  focus >= 0);
            dbox_shade (dbox, I_MAINPROPS_FOCUS_ID, focus < 0);
            if (focus < 0)
                dbox_setstring (dbox, I_MAINPROPS_FOCUS_ID, "");
            else
                dbox_sethex (dbox, I_MAINPROPS_FOCUS_ID, focus);
        }

        /* help message */
        gui_put_len_opt_str (dbox, I_MAINPROPS_HASHELP,
                                   I_MAINPROPS_HELP,
                                   I_MAINPROPS_HELPMAX,
                                   window->p.helpmessage, window->p.maxhelp);

        /* title justification (titleflags) */
        dbox_setbutton (dbox, I_MAINPROPS_JUSTIFY_LEFT,
                             !(titleflags & (IF_RJUST | IF_HCENT)));
        GUI_PUT_FLAG (dbox, I_MAINPROPS_JUSTIFY_RIGHT,
                             titleflags, IF_RJUST);
        GUI_PUT_FLAG (dbox, I_MAINPROPS_JUSTIFY_CENTRE,
                             titleflags, IF_HCENT);

        /* grey-out inappropriate options if it's a toolbar */
        if ((window->p.flags & WINDOW_ISTOOLBAR) != 0)
        {
            static int icons[] =
            {
                I_MAINPROPS_HASTITLE,
                I_MAINPROPS_TITLE,
                I_MAINPROPS_TITLEMAX,
                I_MAINPROPS_TITLEMAX_ADJ_UP,
                I_MAINPROPS_TITLEMAX_ADJ_DOWN,
                I_MAINPROPS_JUSTIFY_LEFT,
                I_MAINPROPS_JUSTIFY_CENTRE,
                I_MAINPROPS_JUSTIFY_RIGHT,
                I_MAINPROPS_BACK,
                I_MAINPROPS_CLOSE,
                I_MAINPROPS_TOGGLE,
                I_MAINPROPS_HSCROLL,
                I_MAINPROPS_VSCROLL,
                I_MAINPROPS_SIZE,
                I_MAINPROPS_FOCUS_NONE,
                I_MAINPROPS_FOCUS_INVISIBLE,
                I_MAINPROPS_FOCUS_GADGET,
                I_MAINPROPS_FOCUS_ID,
                I_MAINPROPS_AUTOOPEN,
                I_MAINPROPS_AUTOCLOSE,
                I_MAINPROPS_BEFORE_DEFAULT,
                I_MAINPROPS_BEFORE_NONE,
                I_MAINPROPS_BEFORE_OTHER,
                I_MAINPROPS_BEFORE_EVENT,
                I_MAINPROPS_HIDDEN_DEFAULT,
                I_MAINPROPS_HIDDEN_NONE,
                I_MAINPROPS_HIDDEN_OTHER,
                I_MAINPROPS_HIDDEN_EVENT,
                -1
            };
            int i = 0;

            while (icons[i] >= 0)
                dbox_shade (dbox, icons[i++], TRUE);
        }

        /* grey-out currently unavailable icons if not a toolbar */
        else
        {
            Bool hastitle = (window->p.title != NULL);
            Bool hashscroll = ((flags & WF_HSCROLL) != 0);
            Bool hasvscroll = ((flags & WF_VSCROLL) != 0);

            dbox_shade (dbox, I_MAINPROPS_CLOSE, !hastitle);
            dbox_shade (dbox, I_MAINPROPS_BACK, !hastitle);
            dbox_shade (dbox, I_MAINPROPS_JUSTIFY_LEFT, !hastitle);
            dbox_shade (dbox, I_MAINPROPS_JUSTIFY_CENTRE, !hastitle);
            dbox_shade (dbox, I_MAINPROPS_JUSTIFY_RIGHT, !hastitle);
            dbox_shade (dbox, I_MAINPROPS_SIZE, !(hashscroll || hasvscroll));
            dbox_shade (dbox, I_MAINPROPS_TOGGLE, !(hastitle || hasvscroll));
        }
    }

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

    return NULL;
}


/*
 * Update contents of dialogue box - only called if dialogue box exists.
 *
 * Called from windowedit_rename_window(..) - when the object is renamed.
 *        from windowedit_load(..) - when a new version of the object is
 *                                    force loaded [not at present]
 *        from apply_other_dbox(..) - if the properties box is to remain on
 *                                    view
 *        from props_open_other_dbox(..) - after the dbox is created
 *        from props_other_dbox_mouse_click(..) - after an ADJ-CANCEL click
 */

error * props_update_other_dbox (
    WindowObjPtr window,
    Bool contents,
    Bool title
)
{
    WindowPtr dbox = window->otherpropsdbox;

    if (contents)
    {
        unsigned int flags = window->p.windowflags;

        /* wimp windowflags */
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_PANE, flags, WF_PANE);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_MOVEABLE, flags, WF_MOVEABLE);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_BACKDROP, flags, WF_BACKDROP);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_REALCOLOURS, flags, WF_GCOL);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_HOTKEYS, flags, WF_HOTKEYS);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_AUTOREDRAW, flags, WF_AUTO_REDRAW);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_OFFSCREEN, flags, WF_ALLOW_OFF);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_ONSCREEN, flags, WF_KEEP_ON);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_FLEXIBLE_X, flags, WF_FREE_RIGHT);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_FLEXIBLE_Y, flags, WF_FREE_BOTTOM);
        dbox_setbutton (dbox, I_OTHERPROPS_USERSCROLL_OFF,
                             !(flags & (WF_USER_SCROLL | WF_NO_REPEAT)));
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_USERSCROLL_AUTO,
                                            flags, WF_USER_SCROLL);
        GUI_PUT_FLAG (dbox, I_OTHERPROPS_USERSCROLL_DEBOUNCE,
                                            flags, WF_NO_REPEAT);

        /* button type */
        dbox_setint (dbox, I_OTHERPROPS_BUTTONTYPE, window->p.buttontype);

        /* pointershape */
        gui_put_len_opt_str (dbox, I_OTHERPROPS_HASPOINTER,
                                   I_OTHERPROPS_POINTER,
                                   I_OTHERPROPS_POINTERMAX,
                                   window->p.pointershape,
                                   window->p.maxpointershape);
        if (window->p.pointershape != NULL)
        {
            dbox_setint (dbox, I_OTHERPROPS_HOTSPOT_X, window->p.pointerxhot);
            dbox_setint (dbox, I_OTHERPROPS_HOTSPOT_Y, window->p.pointeryhot);
        }
        else
        {
            dbox_shade (dbox, I_OTHERPROPS_HOTSPOT_X, TRUE);
            dbox_shade (dbox, I_OTHERPROPS_HOTSPOT_Y, TRUE);
        }

        /* sprite area */
        dbox_setbutton (dbox, I_OTHERPROPS_SPRITEAREA_CLIENT,
                         window->p.spritearea == (void *) CLIENT_SPRITEAREA);
        dbox_setbutton (dbox, I_OTHERPROPS_SPRITEAREA_WIMP,
                         window->p.spritearea == (void *) WIMP_SPRITEAREA);

        /* grey-out inappropriate options if it's a toolbar */
        if ((window->p.flags & WINDOW_ISTOOLBAR) != 0)
        {
            static int icons[] =
            {
                I_OTHERPROPS_PANE,
                I_OTHERPROPS_MOVEABLE,
                I_OTHERPROPS_BACKDROP,
                I_OTHERPROPS_OFFSCREEN,
                I_OTHERPROPS_HOTKEYS,
                I_OTHERPROPS_ONSCREEN,
                I_OTHERPROPS_FLEXIBLE_X,
                I_OTHERPROPS_FLEXIBLE_Y,
                I_OTHERPROPS_USERSCROLL_OFF,
                I_OTHERPROPS_USERSCROLL_AUTO,
                I_OTHERPROPS_USERSCROLL_DEBOUNCE,
                -1
            };
            int i = 0;

            while (icons[i] >= 0)
                dbox_shade (dbox, icons[i++], TRUE);
        }
    }

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

    return NULL;
}


/*
 * Called from:
 *  windowedit_menu_cb(..) - when "Main properties..." is chosen;
 *  windowedit_mouse_click(..) - when the user double-clicks on the editing
 *                               window's background
 *  windowedit_key_pressed(..) - when ^W is pressed
 *
 * If the corresponding properties dbox is already open, it is simply raised
 *  to the top of the window stack.
 *
 * Otherwise, a new main properties window is created and opened.
 *
 * Any new window will be displayed at the same position as the most recent
 *  position of any other window created from the "MainProps" template;
 *  this is achieved by copying co-ordinates to the template whenever an
 *  existing window is reopened - see props_reopen_main_dbox(..).
 */

error * props_open_main_dbox (WindowObjPtr window)
{
    if (window->mainpropsdbox == NULL)
    {
        ER ( wimp_copy_template (mainpropsdboxproto,
                                 &window->mainpropsdbox,
                                 mainpropsdboxsize) );
        ER ( swi (Wimp_CreateWindow,  R1, &window->mainpropsdbox->visarea,
                        OUT,  R0, &window->mainpropsdbox->handle,  END) );
        ER ( registry_register_window (window->mainpropsdbox->handle,
                                       MainpropsDbox, (void *) window) );
        window->mainpropsdbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, window->mainpropsdbox,  END) );
        ER ( props_update_main_dbox (window, TRUE, TRUE) );
    }
    else
    {
        window->mainpropsdbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, window->mainpropsdbox,  END) );
    }   

    return dbox_set_caret_to (window->mainpropsdbox, I_MAINPROPS_TITLE);
}


/*
 * Called from:
 *  windowedit_menu_cb(..) - when "Other properties..." is chosen.
 *
 * If the corresponding properties dbox is already open, it is simply raised
 *  to the top of the window stack.
 *
 * Otherwise, a new other properties window is created and opened.
 *
 * Any new window will be displayed at the same position as the most recent
 *  position of any other window created from the "OtherProps" template;
 *  this is achieved by copying co-ordinates to the template whenever an
 *  existing window is reopened - see props_reopen_other_dbox(..).
 */

error * props_open_other_dbox (WindowObjPtr window)
{
    if (window->otherpropsdbox == NULL)
    {
        ER ( wimp_copy_template (otherpropsdboxproto,
                                 &window->otherpropsdbox,
                                 otherpropsdboxsize) );
        ER ( swi (Wimp_CreateWindow,  R1, &window->otherpropsdbox->visarea,
                        OUT,  R0, &window->otherpropsdbox->handle,  END) );
        ER ( registry_register_window (window->otherpropsdbox->handle,
                                       OtherpropsDbox, (void *) window) );
        window->otherpropsdbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, window->otherpropsdbox,  END) );
        ER ( props_update_other_dbox (window, TRUE, TRUE) );
    }
    else
    {
        window->otherpropsdbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, window->otherpropsdbox,  END) );
    }   

    return dbox_set_caret_to (window->otherpropsdbox, I_OTHERPROPS_POINTER);
}


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

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

    if (choice != NULL && *choice >= 0)
    {
        dbox_setint (dbox, icon, *choice);

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

    return NULL;
}


/*
 * This function uses a pop-up menu to choose a button type (for an icon).
 *
 * It is called from props_other_dbox_mouse_click(..) below, and also from
 *  gadget_dbox_mouse_click(..) to handle ACT_BUTTON actions.
 *
 * The menu's registered type is also provided - so that the correct
 *  interactive help is supplied.
 */

error * props_button_choose (
    WindowPtr dbox,         /* the dialogue box containing ... */
    int icon,               /*  ... the display icon for the button type */
    int menuicon,           /* menu is to be displayed next to this icon */
    int menutype            /* WINDOW_BUTTON_MENU or ICON_BUTTON_MENU */
)
{
    PointRec position;

    /* ensure registration is correct for interactive help */
    menu_deregister (buttonmenu);
    menu_register (buttonmenu, menutype);

    /* determine position to display menu */
    {
        IconStateRec state;

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

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

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

    /* tick appropriate menu item */
    menu_tick_menu (buttonmenu, dbox_getint (dbox, icon));

    /* post the menu */
    buttonclosure.dbox = dbox;
    buttonclosure.icon = icon;
    menu_post (buttonmenu, &position, FALSE,
                           buttonmenu_cb, (void *) &buttonclosure);

    return NULL;
}


/*
 * Called from mouse_click(..) in main.c when the user clicks inside the
 *  "main properties" dialogue box.
 */

error * props_main_dbox_mouse_click (
    MouseClickPtr mouse,
    unsigned int modifiers,
    WindowObjPtr window
)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    WindowPtr dbox = window->mainpropsdbox;
    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_MAINPROPS_CANCEL:
            if (dir == 1)
                return props_close_main_dbox (window);
            else
                err = props_update_main_dbox (window, TRUE, FALSE);
            break;
        case I_MAINPROPS_OK:
            err = apply_main_dbox (window, dir == -1);
            if (dir == 1)
                return err;
            break;

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

        case I_MAINPROPS_HASTITLE:
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_TITLE);
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_JUSTIFY_LEFT);
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_JUSTIFY_CENTRE);
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_JUSTIFY_RIGHT);
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_BACK);
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_CLOSE);
            {
                Bool hastitle = dbox_getbutton (dbox, I_MAINPROPS_HASTITLE);
                Bool hasvscroll = dbox_getbutton (dbox, I_MAINPROPS_VSCROLL);

                dbox_shade (dbox, I_MAINPROPS_TOGGLE,
                                  !(hastitle || hasvscroll));
                if (hastitle)
                    dbox_place_caret (dbox, I_MAINPROPS_TITLE);
            }
            break;
        case I_MAINPROPS_TITLEMAX_ADJ_DOWN:
            dir = -dir;
        case I_MAINPROPS_TITLEMAX_ADJ_UP:
            gui_adjust_len (dbox, I_MAINPROPS_TITLEMAX,
                                  I_MAINPROPS_TITLE, dir, modifiers);
            break;

        case I_MAINPROPS_HSCROLL:
        case I_MAINPROPS_VSCROLL:
            {
                Bool hastitle = dbox_getbutton (dbox, I_MAINPROPS_HASTITLE);
                Bool hashscroll = dbox_getbutton (dbox, I_MAINPROPS_HSCROLL);
                Bool hasvscroll = dbox_getbutton (dbox, I_MAINPROPS_VSCROLL);

                dbox_shade (dbox, I_MAINPROPS_TOGGLE,
                                  !(hastitle || hasvscroll));
                dbox_shade (dbox, I_MAINPROPS_SIZE,
                                  !(hashscroll || hasvscroll));
            }
            break;

        case I_MAINPROPS_HASHELP:
            GUI_TOGGLE_FADE (dbox, I_MAINPROPS_HELP);
            if ( (dbox_getflags (dbox, I_MAINPROPS_HELP) & IF_SHADED) == 0 )
                dbox_place_caret (dbox, I_MAINPROPS_HELP);
            break;
        case I_MAINPROPS_HELPMAX_ADJ_DOWN:
            dir = -dir;
        case I_MAINPROPS_HELPMAX_ADJ_UP:
            gui_adjust_len (dbox, I_MAINPROPS_HELPMAX,
                                  I_MAINPROPS_HELP, dir, modifiers);
            break;

        /* ensure that ADJ-click on a radio button does not turn it off */
        case I_MAINPROPS_FOCUS_GADGET:
        case I_MAINPROPS_FOCUS_NONE:
        case I_MAINPROPS_FOCUS_INVISIBLE:
            dbox_shade (dbox, I_MAINPROPS_FOCUS_ID,
                              icon != I_MAINPROPS_FOCUS_GADGET);
            if (icon == I_MAINPROPS_FOCUS_GADGET)
                dbox_place_caret (dbox, I_MAINPROPS_FOCUS_ID);
        case I_MAINPROPS_JUSTIFY_LEFT:
        case I_MAINPROPS_JUSTIFY_CENTRE:
        case I_MAINPROPS_JUSTIFY_RIGHT:
            if (dir < 0 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            break;

        case I_MAINPROPS_BEFORE_OTHER:
        case I_MAINPROPS_BEFORE_DEFAULT:
        case I_MAINPROPS_BEFORE_NONE:
            if (dir < 0 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            dbox_shade (dbox, I_MAINPROPS_BEFORE_EVENT,
                        icon != I_MAINPROPS_BEFORE_OTHER);
            if (icon == I_MAINPROPS_BEFORE_OTHER)
                dbox_place_caret (dbox, I_MAINPROPS_BEFORE_EVENT);
            break;

        case I_MAINPROPS_HIDDEN_OTHER:
        case I_MAINPROPS_HIDDEN_DEFAULT:
        case I_MAINPROPS_HIDDEN_NONE:
            if (dir < 0 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            dbox_shade (dbox, I_MAINPROPS_HIDDEN_EVENT,
                        icon != I_MAINPROPS_HIDDEN_OTHER);
            if (icon == I_MAINPROPS_HIDDEN_OTHER)
                dbox_place_caret (dbox, I_MAINPROPS_HIDDEN_EVENT);
            break;
        }
    }

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

    return err;
}


/*
 * Called from mouse_click(..) in main.c when the user clicks inside the
 *  "other properties" dialogue box.
 */

error * props_other_dbox_mouse_click (
    MouseClickPtr mouse,
    unsigned int modifiers,
    WindowObjPtr window
)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    WindowPtr dbox = window->otherpropsdbox;
    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_OTHERPROPS_CANCEL:
            if (dir == 1)
                return props_close_other_dbox (window);
            else
                err = props_update_other_dbox (window, TRUE, FALSE);
            break;
        case I_OTHERPROPS_OK:
            err = apply_other_dbox (window, dir == -1);
            if (dir == 1)
                return err;
            break;

        case I_OTHERPROPS_HASPOINTER:
            GUI_TOGGLE_FADE (dbox, I_OTHERPROPS_POINTER);
            GUI_TOGGLE_FADE (dbox, I_OTHERPROPS_HOTSPOT_X);
            GUI_TOGGLE_FADE (dbox, I_OTHERPROPS_HOTSPOT_Y);
            if ( (dbox_getflags (dbox, I_OTHERPROPS_POINTER) & IF_SHADED)
                             == 0 )
                dbox_place_caret (dbox, I_OTHERPROPS_POINTER);
            break;
        case I_OTHERPROPS_POINTERMAX_ADJ_DOWN:
            dir = -dir;
        case I_OTHERPROPS_POINTERMAX_ADJ_UP:
            gui_adjust_len (dbox, I_OTHERPROPS_POINTERMAX,
                                  I_OTHERPROPS_POINTER, dir, modifiers);
            break;

        /* ensure that ADJ-click on a radio button does not turn it off */
        case I_OTHERPROPS_USERSCROLL_OFF:
        case I_OTHERPROPS_USERSCROLL_AUTO:
        case I_OTHERPROPS_USERSCROLL_DEBOUNCE:
        case I_OTHERPROPS_SPRITEAREA_CLIENT:
        case I_OTHERPROPS_SPRITEAREA_WIMP:
            if (dir < 0 && !dbox_getbutton (dbox, icon))
                dbox_setbutton (dbox, icon, TRUE);
            break;
        }
    }

    /* Look for button type pop-up request */
    if ( icon == I_OTHERPROPS_BUTTONTYPE_POPUP &&
         (buttons == MB_CLICK(MB_MENU) || buttons == MB_CLICK(MB_SELECT)) )
        props_button_choose (dbox,
                             I_OTHERPROPS_BUTTONTYPE,
                             I_OTHERPROPS_BUTTONTYPE_POPUP,
                             WINDOW_BUTTON_MENU);

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

    return err;
}


/*
 * Called from open_window_request(..) in main.c.
 *
 * Copies coordinates of current position to the prototype;
 *  see props_open_main_dbox(..) above.
 */

error * props_reopen_main_dbox (WindowPtr win, WindowObjPtr window)
{
    window->mainpropsdbox->visarea =
                       mainpropsdboxproto->visarea = win->visarea;
    window->mainpropsdbox->scrolloffset =
                       mainpropsdboxproto->scrolloffset = win->scrolloffset;
    window->mainpropsdbox->behind = win->behind;
    return swi (Wimp_OpenWindow, R1, window->mainpropsdbox, END);
}


/*
 * Called from open_window_request(..) in main.c.
 *
 * Copies coordinates of current position to the prototype;
 *  see props_open_other_dbox(..) above.
 */

error * props_reopen_other_dbox (WindowPtr win, WindowObjPtr window)
{
    window->otherpropsdbox->visarea =
                       otherpropsdboxproto->visarea = win->visarea;
    window->otherpropsdbox->scrolloffset =
                       otherpropsdboxproto->scrolloffset = win->scrolloffset;
    window->otherpropsdbox->behind = win->behind;
    return swi (Wimp_OpenWindow, R1, window->otherpropsdbox, END);
}


/*
 * Called from:
 *   close_window_request(..) in main.c - when the user clicks on the
 *          dialogue box's close icon.
 *   apply_main_dbox(..) -  after the new contents have been processed.
 *   props_main_dbox_mouse_click(..) -  after a SEL-CANCEL click.
 *   windowedit_close_window(..) - when closing the parent editing window.
 *
 * The dialogue box is deleted.
 */

error * props_close_main_dbox (WindowObjPtr window)
{
    WindowPtr propsdbox = window->mainpropsdbox;

    ER ( registry_deregister_window (propsdbox->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, propsdbox->handle,
                              R1, 0x44ec5,
                              R2, taskhandle, END) );

    ER ( swi (Wimp_DeleteWindow,  R1, propsdbox,  END) );
    free ((char *) propsdbox);
    window->mainpropsdbox = NULL;

    return windowedit_focus_claim (window);
}


/*
 * Called from:
 *   close_window_request(..) in main.c - when the user clicks on the
 *          dialogue box's close icon.
 *   apply_other_dbox(..) -  after the new contents have been processed.
 *   props_other_dbox_mouse_click(..) -  after a SEL-CANCEL click.
 *   windowedit_close_window(..) - when closing the parent editing window.
 *
 * The dialogue box is deleted.
 */

error * props_close_other_dbox (WindowObjPtr window)
{
    WindowPtr propsdbox = window->otherpropsdbox;

    ER ( registry_deregister_window (propsdbox->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, propsdbox->handle,
                              R1, 0x44ec5,
                              R2, taskhandle, END) );

    ER ( swi (Wimp_DeleteWindow,  R1, propsdbox,  END) );
    free ((char *) propsdbox);
    window->otherpropsdbox = NULL;

    return windowedit_focus_claim (window);
}


/*
 * Called from key_pressed(..) in main(..), when the user depresses a key
 *  when the "main properties" dialogue box has the input focus.
 */

error * props_main_dbox_key_pressed (
    WindowObjPtr window,
    KeyPressPtr key,
    Bool *consumed
)
{
    Bool keepopen = (wimp_read_modifiers() & MODIFIER_SHIFT) != 0;
    error *err = NULL;

    if (key->code == 13)    /* RETURN */
    {
        *consumed = TRUE;
        interactor_cancel();  /* in case any pop-up menu is still visible */
        err = apply_main_dbox (window, keepopen);
        if (!keepopen)
            return err;
    }

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

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

    return err;
}


/*
 * Called from key_pressed(..) in main(..), when the user depresses a key
 *  when the "other properties" dialogue box has the input focus.
 */

error * props_other_dbox_key_pressed (
    WindowObjPtr window,
    KeyPressPtr key,
    Bool *consumed
)
{
    Bool keepopen = (wimp_read_modifiers() & MODIFIER_SHIFT) != 0;
    error *err = NULL;

    if (key->code == 13)    /* RETURN */
    {
        *consumed = TRUE;
        interactor_cancel();  /* in case any pop-up menu is still visible */
        err = apply_other_dbox (window, keepopen);
        if (!keepopen)
            return err;
    }

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

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

    return err;
}


/*
 * Returns TRUE if 'icon' is an acceptable place to drop an object onto in
 *  a window main properties dialogue box.
 *
 * Called from protocol_send_resed_object_name_request(..).
 */

Bool props_main_drop_icon (
    int icon
)
{
    return (icon == I_MAINPROPS_HASMENU || icon == I_MAINPROPS_MENU);
}


/*
 * Called to process an object of class 'class', name 'name', dropped onto
 *  icon 'icon' in the window main properties dbox of 'window'.
 *
 * The class of the object is *not* checked - since in fact *any* object
 *  can be specified, and will be "shown transiently" by the Toolbox when
 *  menu is clicked over the window (eg a FontMenu object).
 *
 * Called from received_resed_object_name(..) in c.protocol.
 */

error * props_main_object_drop (
    WindowObjPtr window,
    int icon,
    ObjectClass class,
    char *name
)
{
    WindowPtr dbox = window->mainpropsdbox;

    dbox_setbutton (dbox, I_MAINPROPS_HASMENU, TRUE);
    dbox_setstring (dbox, I_MAINPROPS_MENU, name);
    dbox_shade (dbox, I_MAINPROPS_MENU, FALSE);

    return NULL;
}


/*
 * Called to process the drop of a single gadget onto a window main
 *  properties dialogue box.
 *
 * 'window' is the window upon whose properties dbox 'gadget' has been
 *  dropped; 'icon' identifies the icon upon which the gadget was dropped. 
 */

error * props_main_gadget_drop (
    WindowObjPtr window,
    int icon,
    GadgetPtr gadget
)
{
    WindowPtr dbox = window->mainpropsdbox;

    /* has the gadget been dropped in an appropriate place? */
    switch (icon)
    {
    case I_MAINPROPS_FOCUS_GADGET:
    case I_MAINPROPS_FOCUS_ID:

        /* dropped gadget must be of an appropriate type */
        switch (gadget->hdr.type)
        {
        case GADGET_WRITABLE_FIELD:
        case GADGET_NUMBER_RANGE:
        case GADGET_STRING_SET:

            /* both gadgets must be in the same window */
            if (gadget->owner != window)
                return error_lookup ("IncompGadDrop");

            dbox_setbutton (dbox, I_MAINPROPS_FOCUS_GADGET, TRUE);
            dbox_sethex (dbox, I_MAINPROPS_FOCUS_ID,
                                       gadget->hdr.componentID);
            dbox_shade (dbox, I_MAINPROPS_FOCUS_ID, FALSE);
            dbox_setbutton (dbox, I_MAINPROPS_FOCUS_INVISIBLE, FALSE);
            dbox_setbutton (dbox, I_MAINPROPS_FOCUS_NONE, FALSE);
            break;

        default:
            return error_lookup ("BadGadDrop");
        }
    }

    return NULL;
}
