/*-*-C-*-
 * Window colours 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 "protocol.h"


char *transpval = NULL;           /* msgtrns'd "Transparent" */
int uncolour[16];      /* which colour to use on which coloured background */


static WindowPtr coloursdboxproto;
static int coloursdboxsize;

typedef struct
{
    WindowPtr dbox;
    int icon;
} ColClosRec, *ColClosPtr;

static MenuPtr coloursmenu = NULL;
static MenuPtr coloursplusmenu = NULL;
static ColClosRec colourclosure;  /* used to pass information to the colour
                                     menu callback fuction */


/*
 * Two kinds of dbox for setting colours are used: the first - for desktop
 *  colours - has a display field and popup menu for each item, and the
 *  second - for "real" colours - has a writable field for each item.
 *
 * To help re-use code, we use just a single template, whose icons are
 *  altered according to the requirement.
 *
 * For "desktop colours", each icon has a validation string of the form:
 *   N<n>/<icon-name>;R2
 * and for "real colours" each icon is given a string of the form:
 *   N<n>/<icon-name>;Pptr_write;KAT;a0-9
 *
 * And the popup menu icons are always present - but shaded for "real
 *  colours".
 *
 * This code expects the validation string buffer to be large enough for
 *  either form in the prototype.
 *
 * Careless editing of the template could topple this precarious edifice,
 *  hence the checks when loading the prototype.
 */

static char *validdesktop = "R2";
static char *validreal = "Pptr_write;KAT;a0-9";

#define NUM_ICONS  (7)

static int valicons[] = {
    I_COLOURS_TITLE_FG,
    I_COLOURS_TITLE_BG,
    I_COLOURS_TITLE_IF,
    I_COLOURS_WORK_FG,
    I_COLOURS_WORK_BG,
    I_COLOURS_SCROLL_FG,
    I_COLOURS_SCROLL_BG
};

static int popupicons[] = {
    I_COLOURS_TITLE_FG_POPUP,
    I_COLOURS_TITLE_BG_POPUP,
    I_COLOURS_TITLE_IF_POPUP,
    I_COLOURS_WORK_FG_POPUP,
    I_COLOURS_WORK_BG_POPUP,
    I_COLOURS_SCROLL_FG_POPUP,
    I_COLOURS_SCROLL_BG_POPUP
};



/*
 * Look at the palette, and decide which contrasting colours
 * to use for menu entries.  Store in uncolour[].
 */

static void set_uncolours ()
{
    unsigned int pal[20];
    int bright[16], c, d;

    if (swi (Wimp_ReadPalette,  R1, &pal,  END))
        return;
    
    for (c = 0; c < 16; c++)
    {
        int b = (pal[c] & 0xff000000) >> 24;
        int g = (pal[c] & 0x00ff0000) >> 16;
        int r = (pal[c] & 0x0000ff00) >>  8;
        bright[c] = r + g + g + b;
    }

    for (c = 0; c < 16; c++)
    {
        int maxe = -1, maxd;
        for (d = 0; d < 16; d++)
        {
            int e = abs (bright[d] - bright[c]);
            if (e > maxe)
            {
                maxe = e;
                maxd = d;
            }
        }
        uncolour[c] = maxd;
    }
}


/*
 * Load any templates required - called from load_prototypes(..) in main.c
 *
 * The two colour menus are also created here; they are made global so as to
 * be available for other dbox modules to use.
 */

error * colours_load_prototypes (void)

{
   /* colours menu */
    {
        int i;
        char *s;

        /* determine string to be used for "transparent" menu entry */
        s = message_lookup (&msgs, "Transp");
        transpval = malloc (strlen(s)+1);
        if (transpval == NULL)
            return error_lookup ("NoMem");
        strcpy (transpval, s);

        /* set up the "uncolour" array which defines contrasting colours */
        set_uncolours ();

        /* create the menus, setting appropriate foreground and background
           colours for each menu item */
        ER ( menu_create (16, message_lookup(&msgs, "Colour"),
                              &coloursmenu) );
        ER ( menu_create (17, message_lookup(&msgs, "Colour"),
                              &coloursplusmenu) );
        for (i = 0; i < 16; i++)
        {
            char colour[3];
            sprintf(colour, "%d", i);
            ER ( menu_entry (coloursmenu, i, colour,
                                             0, 0, -1, -1, NULL) );
            ER ( menu_alter_entry (coloursmenu, i, 0, 0, uncolour[i], i) );
            ER ( menu_entry (coloursplusmenu, i, colour,
                                             0, 0, -1, -1, NULL) );
            ER ( menu_alter_entry (coloursplusmenu, i,
                                             0, 0, uncolour[i], i) );
        } 
        ER ( menu_entry (coloursplusmenu, 16, transpval,
                                             0, 0, -1, -1, NULL) );
        /* use colour 0 for transparency */
        ER ( menu_alter_entry (coloursplusmenu, 16, 0, 0, uncolour[0], 0) );

        /* register menus for interactive help use */
        ER ( menu_register (coloursmenu, COLOUR_MENU) );
        ER ( menu_register (coloursplusmenu, COLOUR_MENU) );
    }

    /* now load Colours dbox template */ 
    ER ( wimp_load_template_returning_size (
                  "Colours", &coloursdboxproto, &coloursdboxsize) );

    /* and check that the icon validation strings are long enough */
    {
        int i;

        for (i = 0; i < NUM_ICONS; i++)
        {
            char *valid =
                (char *) coloursdboxproto->icons[valicons[i]].data[1];
            int len = strlen (valid);
            char *s = strchr (valid, ';');
            int max = strlen (validreal);

            if (max < strlen (validdesktop))
                max = strlen (validdesktop);

            if (s == NULL)
                error_exit (error_lookup ("BadColDbox"));

            if (len < (s + 1 - valid) + max)
                error_exit (error_lookup ("BadColDbox"));
        }
    }

    return NULL;
}


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

static error * apply_dbox (WindowObjPtr window, Bool stayopen)
{
    WindowPtr dbox = window->coloursdbox;
    WindowPtr win = window->window;
    WindowColoursRec oldcolours = win->colours;

    /* oldcolours is a copy of the pre-apply colour state */ 
    oldcolours.workBG = window->p.workBG;

    /* copy new colours to window structure */
    win->colours.titleFG = gui_get_colour (dbox, I_COLOURS_TITLE_FG);
    win->colours.titleBG = gui_get_colour (dbox, I_COLOURS_TITLE_BG);
    win->colours.focusBG = gui_get_colour (dbox, I_COLOURS_TITLE_IF);
    win->colours.workFG = gui_get_colour (dbox, I_COLOURS_WORK_FG);

    /* a "transparent" work area background has to be displayed as white */
    win->colours.workBG = gui_get_colour (dbox, I_COLOURS_WORK_BG);
    window->p.workBG = win->colours.workBG;
    if (window->p.workBG == 0xff)    /* transparent */
        win->colours.workBG = 0;   /* white       */

    win->colours.scrollFG = gui_get_colour (dbox, I_COLOURS_SCROLL_FG);
    win->colours.scrollBG = gui_get_colour (dbox, I_COLOURS_SCROLL_BG);

    /* delete and recreate window if any changes made */
    if (oldcolours.titleFG != win->colours.titleFG ||
        oldcolours.titleBG != win->colours.titleBG ||
        oldcolours.focusBG != win->colours.focusBG ||
        oldcolours.workFG != win->colours.workFG ||
        oldcolours.workBG != window->p.workBG ||
        oldcolours.scrollFG != win->colours.scrollFG ||
        oldcolours.scrollBG != win->colours.scrollBG)
    {
        windowedit_redisplay_window (window);
        protocol_send_resed_object_modified (window);
    }

    /* redisplay Colours dbox if ADJ click - otherwise destroy it */
    if (stayopen)
        return colours_update_dbox (window, TRUE, FALSE);
    else
        return colours_close_dbox (window);
}


/*
 * 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_dbox(..) - if the colours dbox is to remain on view
 *        from colours_open_dbox(..) - after the dbox is created
 *        from colours_dbox_mouse_click(..) - after an ADJ-CANCEL click
 */

error * colours_update_dbox (
    WindowObjPtr window,
    Bool contents,
    Bool title
)
{
    WindowPtr dbox = window->coloursdbox;
    WindowPtr win = window->window;
    Bool realcolours = (window->window->flags & WF_GCOL) != 0;

    if (contents)
    {
        gui_put_colour (dbox, I_COLOURS_TITLE_FG,
                              win->colours.titleFG, realcolours);
        gui_put_colour (dbox, I_COLOURS_TITLE_BG,
                              win->colours.titleBG, realcolours);
        gui_put_colour (dbox, I_COLOURS_TITLE_IF,
                              win->colours.focusBG, realcolours);
        gui_put_colour (dbox, I_COLOURS_WORK_FG,
                              win->colours.workFG, realcolours);

        /* must set dbox from true work area background colour */
        gui_put_colour (dbox, I_COLOURS_WORK_BG,
                              window->p.workBG, realcolours);

        gui_put_colour (dbox, I_COLOURS_SCROLL_FG,
                              win->colours.scrollFG, realcolours);
        gui_put_colour (dbox, I_COLOURS_SCROLL_BG,
                              win->colours.scrollBG, realcolours);
    }

    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 (coloursdboxproto), window->name);
        ER ( dbox_settitle (dbox, buf, TRUE) );
    }

    return NULL;
}


/*
 * Called from:
 *  windowedit_menu_cb(..) - when "Colours..." is chosen;
 *
 * If the corresponding colours dbox is already open, it is simply raised
 *  to the top of the window stack.
 *
 * Otherwise, a new colours 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 "colours" template;
 *  this is achieved by copying co-ordinates to the template whenever an
 *  existing window is reopened - see colours_reopen_dbox(..).
 */

error * colours_open_dbox (WindowObjPtr window)
{
    Bool realcolours = (window->window->flags & WF_GCOL) != 0;

    if (window->coloursdbox == NULL)
    {
        int i;

        /* adjust validation strings according to desktop or real colours */
        for (i = 0; i < NUM_ICONS; i++)
        {
            char *valid =
                (char *) coloursdboxproto->icons[valicons[i]].data[1];

            valid = strchr (valid, ';') + 1;
            strcpy (valid, realcolours ? validreal : validdesktop);
        }

        /* adjust icon flags according to desktop or real colours */
        for (i = 0; i < NUM_ICONS; i++)
        {
            unsigned int *flags =
                &coloursdboxproto->icons[valicons[i]].flags;

            *flags = (*flags & ~(IF_FIELD(FG, IF_FG_MASK) |
                                 IF_FIELD(BG, IF_BG_MASK) |
                                 IF_FIELD(TYPE, IF_TYPE_MASK)) )
                             |  (IF_FIELD(FG, 7) | 
                                 IF_FIELD(BG, 0) |
                                 IF_FIELD(TYPE, realcolours ? 15 : 6));
                                                /* writable or click/drag */
        }

        /* shade popup icons according to desktop or real colours */
        for (i = 0; i < NUM_ICONS; i++)
        {
            unsigned int *flags =
                &coloursdboxproto->icons[popupicons[i]].flags;

            *flags = (*flags & ~IF_SHADED)
                             |  (realcolours ? IF_SHADED : 0);
        }

        ER ( wimp_copy_template (coloursdboxproto,
                                 &window->coloursdbox, coloursdboxsize) );
        ER ( swi (Wimp_CreateWindow,  R1, &window->coloursdbox->visarea,
                        OUT,  R0, &window->coloursdbox->handle,  END) );
        ER ( registry_register_window (window->coloursdbox->handle,
                                       ColoursDbox, (void *) window) );
        ER ( swi (Wimp_OpenWindow,  R1, window->coloursdbox,  END) );
        ER ( colours_update_dbox (window, TRUE, TRUE) );
    }
    else
    {
        window->coloursdbox->behind = -1;
        ER ( swi (Wimp_OpenWindow,  R1, window->coloursdbox,  END) );
    }   

    return dbox_set_caret_to (window->coloursdbox,
                              realcolours ? I_COLOURS_TITLE_FG : -1);
}


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

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

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

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

    return NULL;
}


/*
 * This function uses a colours menu to choose a colour.
 *
 * It is called from colours_dbox_mouse_click(..) below, and also from
 *  gadget_dbox_mouse_click(..) to handle ACT_COLOUR actions.
 */

error * colours_choose (
    WindowPtr dbox,         /* the dialogue box containing ... */
    int icon,               /*  ... the display icon for the chosen colour */
    Bool allowtransparent,  /* TRUE iff 'transparent' is an option */
    int menuicon            /* menu is to be displayed next to this icon */
)
{
    MenuPtr menu = allowtransparent ? coloursplusmenu : coloursmenu;
    PointRec position;

    /* 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 */
    {
        int item = gui_get_colour (dbox, icon);

        if (item == 0xff)   /* transparent is the 16th entry on the menu */
            item = 16;
        menu_tick_menu (menu, item);
    }

    /* post the menu */
    colourclosure.dbox = dbox;
    colourclosure.icon = icon;
    menu_post (menu, &position, FALSE,
                     coloursmenu_cb, (void *) &colourclosure);

    return NULL;
}


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

error * colours_dbox_mouse_click (
    MouseClickPtr mouse,
    unsigned int modifiers,
    WindowObjPtr window
)
{
    int buttons = mouse->buttons;
    int icon = mouse->iconhandle;
    int menuicon = icon;
    WindowPtr dbox = window->coloursdbox;
    int dir = buttons == MB_CLICK(MB_ADJUST) ? -1 : 1;

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

    /* is it an action button click? - drop through if not */
    if (buttons == MB_CLICK(MB_SELECT) || buttons == MB_CLICK(MB_ADJUST))
    {
        switch (icon)
        {
        case I_COLOURS_CANCEL:
            if (dir == 1)
                return colours_close_dbox (window);
            else
                return colours_update_dbox (window, TRUE, FALSE);

        case I_COLOURS_OK:
            return apply_dbox (window, dir == -1);
        }
    }

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

    /* is it a pop-up menu request? - drop through if so */
    switch (icon)
    {
    case I_COLOURS_TITLE_FG_POPUP:
        icon = I_COLOURS_TITLE_FG;
        goto L;
    case I_COLOURS_TITLE_BG_POPUP:
        icon = I_COLOURS_TITLE_BG;
        goto L;
    case I_COLOURS_TITLE_IF_POPUP:
        icon = I_COLOURS_TITLE_IF;
        goto L;
    case I_COLOURS_WORK_FG_POPUP:
        icon = I_COLOURS_WORK_FG;
        goto L;
    case I_COLOURS_WORK_BG_POPUP:
        icon = I_COLOURS_WORK_BG;
        goto L;
    case I_COLOURS_SCROLL_FG_POPUP:
        icon = I_COLOURS_SCROLL_FG;
        goto L;
    case I_COLOURS_SCROLL_BG_POPUP:
        icon = I_COLOURS_SCROLL_BG;
    L:
        if (buttons == MB_CLICK(MB_ADJUST))
            return NULL;
        break;

    default:
        return NULL;
    }

    /* use a colour menu to choose a colour */
    return colours_choose (dbox,
                icon,
                (icon == I_COLOURS_TITLE_FG || icon == I_COLOURS_WORK_BG),
                menuicon);
}


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

error * colours_reopen_dbox (WindowPtr win, WindowObjPtr window)
{
    window->coloursdbox->visarea = coloursdboxproto->visarea = win->visarea;
    window->coloursdbox->scrolloffset =
                       coloursdboxproto->scrolloffset = win->scrolloffset;
    window->coloursdbox->behind = win->behind;
    return swi (Wimp_OpenWindow, R1, window->coloursdbox, END);

    return NULL;
}


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

error * colours_close_dbox (WindowObjPtr window)
{
    WindowPtr coloursdbox = window->coloursdbox;

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

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

    free ((char *) coloursdbox);
    window->coloursdbox = NULL;

    return windowedit_focus_claim (window);
}


/*
 * Called from apply_other_dbox(..) inside c.props when the value fo the
 *  "real colours" windowflag changes.
 *
 * The colour dbox has to be closed and reopened, in order to change its
 *  icons.
 */

error * colours_redesign_dbox (WindowObjPtr window)
{
    WindowPtr coloursdbox = window->coloursdbox;
    RectRec visarea;
    PointRec scrolloffset;
    int behind;

    /* find current position of dbox */
    swi (Wimp_GetWindowState, R1, coloursdbox, END);

    /* record it in template - so that reopen will be in same place - but
       also keep record of revious position to restore later */
    visarea = coloursdboxproto->visarea;
    coloursdboxproto->visarea = coloursdbox->visarea;
    scrolloffset = coloursdboxproto->scrolloffset;
    coloursdboxproto->scrolloffset = coloursdbox->scrolloffset;
    behind = coloursdboxproto->behind;
    coloursdboxproto->behind = coloursdbox->behind;

    ER ( colours_close_dbox (window) );
    ER ( colours_open_dbox (window) );

    /* restore previous position to template */
    coloursdboxproto->visarea = visarea;
    coloursdboxproto->scrolloffset = scrolloffset;
    coloursdboxproto->behind = behind;

    return NULL;
}


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

error * colours_dbox_key_pressed (
    WindowObjPtr window,
    KeyPressPtr key,
    Bool *consumed
)
{
    if (key->code == 13)         /* RETURN */
    {
        *consumed = TRUE;
        interactor_cancel();  /* in case any pop-up menu is still on-screen */
        apply_dbox (window, (wimp_read_modifiers() & MODIFIER_SHIFT) != 0);
    }

    else if (key->code == 0x1b)  /* ESCAPE */
    {
        *consumed = TRUE;
        if ((wimp_read_modifiers() & MODIFIER_SHIFT) == 0)
            colours_close_dbox (window);
        else
            colours_update_dbox (window, TRUE, FALSE);
    }

    return NULL;
}
