/**
 * File:
 *   include/mail/wj.ycp
 *
 * Package:
 *   Configuration of mail
 *
 * Summary:
 *   Widget Juggler
 *
 * Authors:
 *   Martin Vidner <mvidner@suse.cz>
 *
 * $Id: wj.ycp 20274 2004-11-10 10:48:36Z mvidner $
 *
 * <ol>
 * <li> Widget functions to make moving widgets between dialogs easier.
 * <li> Table editing helpers
 * </ol>
 */

{

textdomain "mail"; // shoudn't be necessary

include "mail/widgets.ycp";

/* ---------------------------------------------------------------- */

/**
 * @param data	a list of structs
 * @param keys	which members to put in the table
 * @return	an item list
 */
define list<term> makeItems (list<map> data, list<string> keys) ``{
    integer i = 0;
    return maplist (map d, data, ``{
	term t = `item (`id (i));
	i = i + 1;
	foreach (string k, keys, ``{
	    t = add (t, d[k]:"");
	});
	return t;
    });
}

boolean edit_touched = false;

/**
 * A generic handler for editing tables.
 * The current item of table_widget
 * makeItems (new_data, keys) is used to fill table_widget
 * @param action	`add, `edit or `delete
 * @param data		the data edited using the table
 * @param keys		keys of respective columns
 * @param editEntry	a function to edit an entry:
 *			gets the current entry and list of othe entries as parameters
 * @param table_widget	id of the table (usually a symbol)
 * @return		the edited data
 */
define list<map> EditTable (symbol action,
				   list<map> data,
				   list<string> keys,
				   map (map,list<map>) editEntryP,
				   any table_widget) ``{
    list<map> new_data = nil;
    integer entryno = (integer)UI::QueryWidget(`id (table_widget),`CurrentItem);
    boolean touched = false;

    map (map, list<map>) editEntry = editEntryP;//FIXME

    if (action == `add)
    {
	map entry = editEntry ($[], data);
	if (size (entry) > 0)
	{
	    new_data = add (data, entry);
	    touched = true;
	}
	else
	{
	    new_data = data;
	}
    }
    else if (action == `edit)
    {
	// edit known fields, preserve unknown fields
	map old_entry = data[entryno]:$[];
	map entry = editEntry (old_entry, remove (data, entryno));
	if (size (entry) > 0)
	{
	    integer i = 0;
	    new_data = maplist (map e, data, ``{
		i = i + 1;
		return (i - 1 == entryno)? union (old_entry, entry): e;
	    });
	    touched = true;
	}
	else
	{
	    new_data = data;
	}
    }
    else if (action == `delete)
    {
	new_data = remove (data, entryno);
	touched = true;
    }
    else
    {
	y2error ("Unknown EditTable action %1.", action);
	new_data = data; // be nice
    }

    if (touched)
    {
	UI::ChangeWidget (`id (table_widget), `Items, makeItems (new_data, keys));
	edit_touched = true;
    }
    return new_data;
}

/* ---------------------------------------------------------------- */

/**
 * Evaluate a function pointer, working around interpreter limitations
 */
any evaluate_fp (any fp) {
    if (is (fp, boolean ()))
    {
	boolean () boolean_fp = (boolean ()) fp;
	return boolean_fp ();
    }
    if (is (fp, list ()))
    {
	list () list_fp = (list ()) fp;
	return list_fp ();
    }
    if (is (fp, string ()))
    {
	string () string_fp = (string ()) fp;
	return string_fp ();
    }
    if (is (fp, symbol ()))
    {
	symbol () symbol_fp = (symbol ()) fp;
	return symbol_fp ();
    }
    // report as the caller
    y2error (1, "evaluate_fp: Unexpected function pointer: %1", fp);
    return nil;
}

/**
 * Constructs a widget term.
 * Uses a global Widgets map.
 * Keys are symbols (ids), values are
 * @struct widget_def
 *    `foo: $[
 *	"widget": `TextEntry (),
 *	"opt": `opt (`notify), // optional
 *	"label": "&Foo",
 *	// if there are choices, they are used to construct the widget,
 *	// otherwise get is used
 *	"choices": Choices_foo          // list ()
 *	"help": _("&lt;p&gt;Foo!&lt;/p&gt;"), // used by WJ_MakeHelp, optional
 *	"get": Get_foo,			// gets value from module to widget
 *	"set": Set_foo,			// sets value from widget to module
 *	// popups an error and returns false if invalid
 *	"validate": ``(Validate_foo ()),// optional
 *	],
 * @param id which widget
 * @return a widget term
 */
define term WJ_MakeWidget (symbol id) ``{
    map w_def = Widgets[id]:$[];
    term widget = w_def["widget"]:`CheckBox ();
    if (widget == nil)
    {
	return nil;
    }
    widget = add (widget, `id (id));
    if (haskey (w_def, "opt"))
    {
	widget = add (widget, w_def["opt"]:`Empty());
    }
    widget = add (widget, w_def["label"]:"LABEL");
    // if choices are defined (combo boxes ...), use them
    // otherwise set the value directly
    // TODO: verified only for TextEntry. Password, CheckBox, ComboBox

    // "choices" returns list, "get" returns string, symbol or boolean
    any init = evaluate_fp (w_def["choices"]:w_def["get"]:nil);
    if (init != nil)
    {
	return add (widget, init);
    }
    return widget;
}

/**
 * Changes widget value using "get" (useful for widgets with choices).
 * Uses a global Widgets map. @ref widget_def
 * @param id which widget
 */
define void WJ_GetWidget (symbol id) ``{
    // "get" returns string or boolean:
    any e = evaluate_fp (Widgets[id, "get"]:nil);
    UI::ChangeWidget (`id (id), `Value, e);
}

/**
 * Make a help string by concatenating
 * individual widget helps and other strings.
 * Uses a global Widgets map. @ref widget_def
 * @param items an item is<br>
 *   either	a string - use it<br>
 *   or		a symbol - lookup its help string in Widgets
 * @return concatenated strings
 */
define string WJ_MakeHelp (list items) ``{
    string ret = "";
    foreach (any item, items, ``{
	if (!is (item, string))
	{
	    item = Widgets[item, "help"]:"";
	}
	ret = ret + (string) item;
    });
    return ret;
}

/**
 * A helper for WJ_Validate.
 * @param id a widget id
 * @return always true
 */
define boolean WJ_Validate_True (symbol id) ``{
    return true;
}

/**
 * Call the validation functions for a list of widgets.
 * Widgets with an unspecified function are assumed to be valid.
 * Uses a global Widgets map. @ref widget_def
 * @param	widgets which widgets to validate
 * @return	true if all widgets are ok
 */
define boolean WJ_Validate (list widgets) ``{
    boolean ok = true;
    foreach (any id, widgets, ``{
	if (ok)
	{
	    if (is(id,symbol))
	    {
		symbol id_sym = (symbol) id;
		map w_def = (map) Widgets[id]:nil;
		if (w_def== nil)
		{
		    // report as the caller
		    y2error (1, "WJ: Missing widget definition for %1", id);
		    ok = false;
		}
		else
		{
		    boolean (symbol) validate = w_def["validate"]:WJ_Validate_True;
		    ok = validate (id_sym);
		}
	    }
	    else
	    {
		ok = false;
	    }
	}
	else
	{
	    return; //break
	}
    });
    return ok;
}

define void WJ_Set_Missing (symbol id) ``{
    // report as the caller
    y2error (1, "WJ: Missing Set definition for %1", id);
}

/**
 * Call the "set" functions for a list of widgets
 * to commit the UI values to a module.
 * Uses a global Widgets map. @ref widget_def
 * @param	widgets which widgets to commit
 */
define void WJ_Set (list widgets) ``{
    foreach (any id, widgets, ``{

	if (is(id,symbol))
	{
	    symbol id_sym = (symbol) id;

	    void (symbol) set_it = Widgets[id_sym, "set"]:WJ_Set_Missing;
	    set_it (id_sym);
	}
    });
}

/* ---------------------------------------------------------------- */
// Layout
// Helpers for frequently used layout idioms

/**
 * Make a RadioButtonGroup containing Left aligned widgets
 * in a VBox that has VSpacing (0.2) as top/bottom padding.
 * @param	g_id	group id
 * @param	rbs	a list of widgets, usually RadioButtons
 * @return	widget
 */
define term RadioButtonVBox (symbol g_id,
				    list<term> rbs) ``{
    term spacing = `VSpacing (0.2);
    term rb_vbox = `VBox (spacing);
    foreach (term rb, rbs, ``{
	term left_rb = `Left (rb);
	rb_vbox = add (rb_vbox, left_rb);
    });
    rb_vbox = add (rb_vbox, spacing);

    return `RadioButtonGroup (
	`id (g_id),
	rb_vbox
	);
}

}
