/**
 * File:	modules/TablePopup.ycp
 * Package:	Table/Popup dialogs backend
 * Summary:	Routines for Table/Popup interface
 * Authors:	Jiri Srain <jsrain@suse.cz>
 *
 * $Id: TablePopup.ycp 19953 2004-10-26 12:06:47Z jsrain $
 *
 */

{

    module "TablePopup";
    textdomain "base";

    import "CWM";
    import "Label";
    import "Mode";
    import "Report";


// variables

    /**
      * Item, that is the last selected
      * Used to decide if selected item should be moved up or down if separator
      *  clicked
      * Loss of contents is no problem
      */
    any previous_selected_item = nil;

// local functions

    /**
      * Get list of IDs of entries of the table
      * @param descr map table description map
      * @return list of IDs of the table
      */
    define list getIdList (map<string, any> descr) ``{
        list(map) toEval = (list(map)) (descr["ids"]:nil);
        if (toEval != nil)
        {
	    return toEval (descr);
        }
	return [];
    }

/**
 * Validate table options specifyign attributesA
 * @param attr a map of table attributes
 * @return boolean true if validation succeeded
 */
define boolean ValidateTableAttr (map<string,any> attr) {
    map<string,string> types = $[
        "add_delete_buttons" : "boolean",
        "changed_column" : "boolean",
        "up_down_buttons" : "boolean",
	"unique_keys" : "boolean",
    ];
    boolean ret = true;
    foreach (string k, any v, attr, {
	string type = (string)types[k]:nil;
	if (type == nil)
	{
	    y2error ("Unknown attribute %1", k);
	    ret = false;
	}
	else
	{
	    ret = CWM::ValidateBasicType (v, type) && ret;
	}
    });
    return ret;
}


/**
 * Validate type of entry of the option description map
 * Also checks option description maps if present
 * @param key string key of the map entry
 * @param value any value of the map entry
 * @param widget any name of the widget/option
 * @param popup boolean true if is option of a popup
 * @return boolean true if validation succeeded
 */
define boolean ValidateValueType (string key, any value, string widget, boolean popup) {
    boolean success = true;
    if (popup)
    {
	if (key == "init")
	    success = is (value, void(any,string));
	else if (key == "handle")
	    success = is (value, void(any,string,map))
		|| is (value, symbol);
	else if (key == "store")
	    success = is (value, void(any,string));
	else if (key == "cleanup")
	    success = is (value, void(any,string));
	else if (key == "validate_function")
	    success = is (value, boolean(any,string,map));
	else if (key == "optional")
	    success = is (value, boolean);
	else if (key == "label_func")
	    success = is (value, string(any,string));
	else
	    return CWM::ValidateValueType (key, value, widget);
    }
    else if (key == "id2key")
	success = is (value, string(map,any));
    else if (key == "ids")
	success = is (value, list(map));
    else if (key == "option_delete")
	success = is (value, boolean(any,string));
    else if (key == "summary")
	success = is (value, string(any,string));
    else if (key == "label_func")
	success = is (value, string(any,string));
    else if (key == "option_move")
	success = is (value, any(any, string, symbol));
    else if (key == "options")
	success = is (value, map<string,any>);
    else if (key == "add_items")
	success = is (value, list);

    if (! success)
	y2error ("Wrong type of option %1 in description map of %2",
	    key, widget);
}

/**
 * Validate the table description
 * @param descr a map containing the table description
 * @return boolean true if validation succeeded
 */
define boolean ValidateTableDescr (string key, map<string,any> descr) {
    boolean ret = true;
    foreach (string k, any v, descr, {
	ret = ValidateValueType (k, v, key, false) && ret;
    });
    map<string,any> options = descr["options"]:$[];
    foreach (string w_key, any v, options, {
	map<string,map<string,any> > des = (map<string,map<string,any> >)v;
	foreach (string group, map<string,any> d, des, {
	    if (group != "table" && group != "popup")
	    {
		y2error ("Unknown entry in option %1: %2", w_key, group);
	    }
	    foreach (string key, any value, d, {
		ValidateValueType (key, value, w_key, true);
	    });
	});
    });
    return ret;
}

    /**
      * Get option key from the option id
      * global only because of testsuites
      * @param descr map description of the table
      * @param opt_id any id of the option
      * @return string option key
      */
    global define string id2key (map<string, any> descr, any opt_id) ``{
	if (opt_id != nil && is (opt_id, string) && size ((string)opt_id) >= 7
	    && substring ((string)opt_id, 0, 7) == "____sep")
	{
	    return "____sep";
	}
	string (map, any) toEval = (string(map, any)) (descr["id2key"]:nil);
	if (toEval != nil)
	    return toEval (descr, opt_id);
	else
	    return (string)opt_id;
    }

    /**
      * Get option description map from the key
      * global only because of testsuites
      * @param descr map description of the table
      * @param opt_key string option key
      * @return map option description map
      */
    global define map<string, any> key2descr (map<string, any> descr, string opt_key) ``{
	map options = (map)(descr["options"]:$[]);
	map<string, any> opt_descr = (map<string, any>)(options[opt_key]:$[]);
	// a copy wanted here
	opt_descr = (map<string, any>) add (opt_descr, "_cwm_key", opt_key);
	// a deep copy
	opt_descr["table"] = add ((map)(opt_descr["table"]:$[]), "_cwm_key", opt_key);
	opt_descr["popup"] = add ((map)(opt_descr["popup"]:$[]), "_cwm_key", opt_key);
	if (opt_descr["popup", "label"]:nil == nil)
	    opt_descr["popup", "label"] = opt_descr["table", "label"]:opt_key;
	return opt_descr;
    }

    /**
      * Update the option description map in order to contain handlers of
      *  all needed functions
      * global only because of testsuites
      * @param opt_descr map option description map
      * @param fallbacks map of fallback handlers
      * @return map updated option description map
      */
    global define map<string, any> updateOptionMap (map<string, any> opt_descr, map fallbacks) ``{
	// ensure that the submaps exist
	opt_descr["table"] = opt_descr["table"]:$[];
	opt_descr["popup"] = opt_descr["popup"]:$[];
	foreach (string k, ["init", "store" ], ``{
	    if (! haskey (opt_descr["popup"]:$[], k) && haskey (fallbacks, k))
		opt_descr["popup", k] = fallbacks[k]:nil;
	});
	if (! haskey (opt_descr["table"]:$[], "summary")
	    && haskey (fallbacks, "summary"))
	{
	    opt_descr["table", "summary"] = fallbacks["summary"]:nil;
	}
	if (! haskey (opt_descr["table"]:$[], "label_func")
	    && haskey (fallbacks, "label_func"))
	{
	    opt_descr["table", "label_func"] = fallbacks["label_func"]:nil;
	}
	if (! haskey (opt_descr["table"]:$[], "changed")
	    && haskey (fallbacks, "changed"))
	{
	    opt_descr["table", "changed"] = fallbacks["changed"]:nil;
	}
	if (opt_descr["_cwm_key"]:"" == "____sep"
	    && opt_descr["table", "label"]:"" == "")
	{
	    opt_descr["table", "label"] = "--------------------";
	}
	return opt_descr;
    }

    /**
      * Get the left column of the table
      * @param opt_id any option id
      * @param opt_descr map option description map
      * @return string text to the table
      */
    define string tableEntryKey (any opt_id, map<string, any> opt_descr) ``{
	string opt_key = opt_descr["_cwm_key"]:"";
	string label = opt_descr["table", "label"]:sformat ("%1", opt_key);
	if (haskey (opt_descr["table"]:$[], "label_func"))
	{
	    string(any,string) label_func = (string(any,string))
		opt_descr["table", "label_func"]:nil;
	    label = label_func (opt_id, opt_key);
	}
	return label;
    }

    /**
      * Get value to the table entry
      * @param opt_id any option id
      * @param opt_descr map option description map
      * @return string text to the table
      */
    define string tableEntryValue (any opt_id, map<string, any> opt_descr) ``{
	string opt_key = (string)(opt_descr["_cwm_key"]:"");
	string (any, string) toEval = (string(any, string)) (opt_descr["table", "summary"]:nil);
	if (toEval != nil)
	{
	    return toEval (opt_id, opt_key);
	}
	return "";
    }

    /**
      * Realize if table entry was changed
      * @param opt_id any option id
      * @param opt_descr map option description map
      * @return boolean true if was changed
      */
    global define boolean tableEntryChanged (any opt_id, map<string, any> opt_descr) ``{
	string opt_key = (string)(opt_descr["_cwm_key"]:"");
	boolean (any, string) toEval = (boolean(any, string)) (opt_descr["table", "changed"]:nil);
	if (toEval != nil)
	{
	    return toEval (opt_id, opt_key);
	}
	return false;
    }

    /**
      * Delete an item from the table
      * Just a wrapper for module-specific function
      * @param opt_id any option id
      * @param descr map table description map
      * @return boolean true if was really deleted
      */
    global define boolean deleteTableItem (any opt_id, map<string, any> descr) ``{
	boolean(any, string) toEval = (boolean(any,string)) (descr["option_delete"]:nil);
	if (nil != toEval)
	{
	    string opt_key = id2key (descr, opt_id);
	    return toEval (opt_id, opt_key);
	}
	return false;
   }

    /**
      * Enable or disable the Delete and up/down buttons
      * @param descr map table description map
      * @param opt_descr map selected option description map
      */
    global define void updateButtons (map<string,any> descr, map<string,any> opt_descr) ``{
	if (descr["_cwm_attrib", "add_delete_buttons"]:true)
	{
	    UI::ChangeWidget (`id (`_tp_delete), `Enabled,
		opt_descr["table", "optional"]:true);
	}
	UI::ChangeWidget (`id (`_tp_edit), `Enabled,
	    ! opt_descr["table", "immutable"]:false);
	if (descr["_cwm_attrib", "up_down_buttons"]:false)
	{
	    UI::ChangeWidget (`id (`_tp_up), `Enabled,
		opt_descr["table", "ordering"]:true);
	    UI::ChangeWidget (`id (`_tp_down), `Enabled,
		opt_descr["table", "ordering"]:true);
	}
    }

    /**
      * Move table item up or down
      * Just a wrapper for module-specific function
      * @param opt_id any option id
      * @param descr map table description map
      * @param dir symbol `up or `down (according to the button user pressed)
      * @return any new id of selected option, nil if wasn't reordered
      */
    define any moveTableItem (any opt_id, map<string, any> descr, symbol dir) ``{
	any(any, string, symbol) toEval = (any(any, string, symbol)) (descr["option_move"]:nil);
	if (nil != toEval)
	{
	    return toEval (opt_id, id2key (descr, opt_id), dir);
	}
	return nil;
    }

    /**
      * Redraw completely the table
      * @param descr map description map of the whole table
      * @param update_buttons boolean true if buttons status (enabled/disabled)
      *  should be updated according to currently selected item
      */
    define void TableRedraw (map<string, any> descr, boolean update_buttons) ``{
	list id_list = getIdList (descr);
	if (previous_selected_item == nil)
	{
	    previous_selected_item = id_list[0]:nil;
	}
	list<term> entries = maplist (any opt_id, id_list, ``{
	    string opt_val = "";
	    string val = "";
	    boolean opt_changed = false;
	    string opt_key = id2key (descr, opt_id);
	    map<string, any> opt_descr = key2descr (descr, opt_key);
	    opt_descr = updateOptionMap (opt_descr, descr["fallback"]:$[]);
	    string label = tableEntryKey (opt_id, opt_descr);
	    if (opt_key != "____sep")
	    {
		opt_val = tableEntryValue (opt_id, opt_descr);
		opt_changed = tableEntryChanged (opt_id, opt_descr);
	    }
	    if (update_buttons && opt_id == previous_selected_item)
		updateButtons (descr, opt_descr);
	    if (descr["_cwm_attrib", "changed_column"]:false)
	    {
		return (`item (
		    `id (opt_id),
		    opt_changed ? "*" : "",
		    label,
		    sformat ("%1", opt_val)));

	    }
	    return (`item (
		`id (opt_id),
		label,
		sformat ("%1", opt_val)));
	});
	UI::ChangeWidget (`id (`_tp_table), `Items, entries);
	UI::SetFocus (`id (`_tp_table));
    }

    /**
      * Displaye popup for option to edit choosing
      * @param possible a list of strings or items of all possible options
      *   to provide
      * @param editable boolean true means that it is possible to add non-listed
      *   options
      * @param descr a map table description map
      * @return string option identifies, nil if canceled
      */
    global define string askForNewOption (list possible, boolean editable, map<string,any> descr) ``{
	boolean do_sort = ! descr["add_items_keep_order"]:false;
	if (do_sort)
	    possible = sort (possible);
	map<string,string> val2key = $[];
	map<string,boolean> known_keys = $[];
	possible = maplist (any p, possible, ``{
	    if (!is (p, string)) continue;
	    map<string, any> opt_descr = key2descr (descr, (string)p);
	    string label = opt_descr["table", "label"]:sformat ("%1", p);
	    known_keys[(string)p] = true;
	    val2key[label] = (string)p;
	    return `item (`id (p), label);
	});
	term widget = `HBox (`HSpacing (1), `VBox (
	    `VSpacing (1),
	    `ComboBox (`id (`optname), editable ? `opt (`editable) : `opt (),
		// combobox header
		_("&Selected Option"), possible),
	    `VSpacing (1),
	    `HBox (
		    `HStretch (),
		    `PushButton (`id (`_tp_ok), `opt (`key_F10, `default),
		    Label::OKButton ()),
		`HSpacing (1),
		`PushButton (`id (`_tp_cancel), `opt (`key_F9),
		    Label::CancelButton ()),
		`HStretch ()
	    ),
	    `VSpacing (1)
	), `HSpacing (1));
	UI::OpenDialog (widget);
	UI::SetFocus (`id (`optname));
	any ret = nil;
	string option = nil;
	while (ret != `_tp_ok && ret != `_tp_cancel)
	{
	    ret = UI::UserInput ();
	    if (ret == `_tp_ok)
	    {
		option = (string)(UI::QueryWidget (`id (`optname), `Value));
	    }
	}
	UI::CloseDialog ();
	if (ret == `_tp_cancel)
	    return nil;
	if (known_keys[option]:false)
	    return option;
	return val2key[option]:option;
    }

    /**
      * Display and handle the popup for option
      * @param option map one option description map that is modified in order
      *   to contain the option name and more percise option identification
      * @return symbol `_tp_ok or `_tp_cancel
      */
    global define symbol singleOptionEditPopup (map<string,any> option)``{
	string opt_key = option["_cwm_key"]:"";
	any opt_id = option["_cwm_id"]:nil;

	string label = sformat ("%1", option["table", "label"]:opt_key);
	term header = `HBox (
	    // heading / label
	    `Heading (_("Current Option: ")),
	    `Label (label),
	    `HStretch ()
	);
	map<string, any> popup_descr = CWM::prepareWidget (option["popup"]:$[]);
	term widget = (term)(popup_descr["widget"]:`VBox ());
	string help = (string)(popup_descr["help"]:"");
	if (help == nil)
	{
	    help = "";
	}
	term contents = `HBox (`HSpacing (1), `VBox (
	    `VSpacing (1),
	    `Left (header),
	    `VSpacing (1),
	    help == "" ? `VSpacing (0)
		: `Left (`Label (help)),
	    `VSpacing (help == "" ? 0 : 1),
	    `Left (`ReplacePoint(`id (`value_rp), widget)),
	    `VSpacing (1),
	    `HBox (
		`HStretch(),
		`PushButton (`id (`_tp_ok), `opt (`key_F10, `default),
		    Label::OKButton ()),
		`HSpacing (1),
		`PushButton (`id (`_tp_cancel), `opt (`key_F9),
		    Label::CancelButton ()),
		`HStretch ()
	    ),
	    `VSpacing (1)
	), `HSpacing (1));
	UI::OpenDialog (contents);
	if (popup_descr["init"]:nil != nil)
	{
	    void(any, string) toEval = (void(any, string)) (popup_descr["init"]:nil);
	    toEval (opt_id, opt_key);
	}
	any ret = nil;
	map<string, any> event_descr = $[];
	while (ret != `_tp_ok && ret != `_tp_cancel)
	{
	    map<string, any> event_descr = (map<string, any>)(UI::WaitForEvent ());
	    if (Mode::test ())
		event_descr = $[ "ID" : `_tp_ok];
	    ret = event_descr["ID"]:nil;
	    if (popup_descr["handle"]:nil != nil)
	    {
		void(any, string, map) toEval = (void(any, string, map)) (popup_descr["handle"]:nil);
		toEval (opt_id, opt_key, event_descr);
	    }
	    if (ret == `_tp_ok)
	    {
		symbol val_type = (symbol) (popup_descr["validate_type"]:nil);
		if (val_type == `function)
		{
		    boolean (any, string, map) toEval
			= (boolean(any, string, map)) (popup_descr["validate_function"]:nil);
		    if (toEval != nil)
		    {
			if (! toEval (opt_id, opt_key, event_descr))
			{
			    ret = nil;
			}
		    }
		}
		else if (! CWM::validateWidget (popup_descr, event_descr,
		    opt_key))
		{
		    ret = nil;
		}
	    }
	}
	if (ret == `_tp_ok && popup_descr["store"]:nil != nil)
	{
	    void(any, string) toEval = (void(any, string)) (popup_descr["store"]:nil);
	    toEval (opt_id, opt_key);
	}

	UI::CloseDialog ();
	return (symbol)ret;
    }




// functions

    /**
      * Disable whole table
      * @param descr map table widget description map
      */
    global define void DisableTable (map<string,any> descr) ``{
	UI::ChangeWidget (`id (`_tp_table), `Enabled, false);
	UI::ChangeWidget (`id (`_tp_edit), `Enabled, false);
	if (descr["_cwm_attrib", "add_delete_buttons"]:true)
	{
	    UI::ChangeWidget (`id (`_tp_delete), `Enabled, false);
	    UI::ChangeWidget (`id (`_tp_add), `Enabled, false);
	}
	if (descr["_cwm_attrib", "up_down_buttons"]:false)
	{
	    UI::ChangeWidget (`id (`_tp_up), `Enabled, false);
	    UI::ChangeWidget (`id (`_tp_down), `Enabled, false);
	}
    }

    /**
      * Enable whole table (except buttons that should be grayed according to
      * currently selected table row
      * @param descr map table widget description map
      */
    global define void EnableTable (map<string, any> descr) ``{
	UI::ChangeWidget (`id (`_tp_table), `Enabled, true);
	UI::ChangeWidget (`id (`_tp_edit), `Enabled, true);
	if (descr["_cwm_attrib", "add_delete_buttons"]:true)
	{
	    UI::ChangeWidget (`id (`_tp_add), `Enabled, false);
	}

	any opt_id = UI::QueryWidget (`id (`_tp_table), `CurrentItem);
	string opt_key = id2key (descr, opt_id);
	map<string,any> option_map = key2descr (descr, opt_key);
	updateButtons (descr, option_map);
    }

    /**
      * Initialize the displayed table
      * @param descr map description map of the whole table
      * @param key table widget key
      */
    global define void TableInit (map<string, any> descr, string key) ``{
	previous_selected_item = nil;
	descr["_cwm_key"] = key;
	TableRedraw (descr, true);
    }

    /**
      * Handle the event that happened on the table
      * @param descr map description of the table
      * @param key table widget key
      * @param event_descr map event to handle
      * @return symbol modified event if needed
      */
    global define symbol TableHandle (map<string, any> descr, string key, map event_descr) ``{
	any event_id = event_descr["ID"]:nil;
	UI::SetFocus (`id (`_tp_table));
	if (event_id == `_tp_table)
	{
	    if (event_descr["EventReason"]:"" == "Activated"
		&& event_descr["EventType"]:"" == "WidgetEvent")
	    {
		event_id = `_tp_edit;
	    }
	}
        if (event_id == `_tp_edit || event_id == `_tp_add)
        {
            string opt_key = nil;
            any opt_id = nil;

            if (event_id == `_tp_add)
            {
		boolean add_unlisted = descr["add_unlisted"]:true;
		if (! add_unlisted && size (descr["add_items"]:[]) == 1)
		{
		    opt_key = descr["add_items", 0]:"";
		}
		else
		{
		    list<string> add_opts = descr["add_items"]:[];
		    list ids = getIdList (descr);
		    list<string> present = maplist (any i, ids, ``(
			id2key (descr, i)
		    ));
		    if (! descr["_cwm_attrib", "unique_keys"]:false)
		    {
			present = filter (string i, present, ``{
			    map opt_descr = key2descr (descr, i);
			    return ! opt_descr["table", "optional"]:true;
			});
		    }
		    add_opts = filter (string o, add_opts, ``(
			! contains (present, o)
		    ));
		    boolean selected = false;
		    while (! selected)
		    {
			opt_key = askForNewOption (add_opts, add_unlisted,
			    descr);
			if (opt_key == nil)
			    return nil;
			if (contains (present, opt_key))
			    Report::Error (
				// error report
				_("The selected option is already present"));
			else
			    selected = true;
		    }

		}
                if (opt_key == nil)
                    return nil;
            }
            else if (event_id == `_tp_edit)
            {
                opt_id = UI::QueryWidget (`id (`_tp_table), `CurrentItem);
                opt_key = id2key (descr, opt_id);
            }
            map<string, any> option_map = key2descr (descr, opt_key);
	    any toEval = option_map["table", "handle"]:nil;
	    if (toEval != nil)
	    {
//		if (is (toEval, symbol))
		if (! is (toEval, symbol(any, string, map)))
		{
		    symbol ret = (symbol)toEval;
		    return ret;
		}
		else
		{
		    symbol(any, string, map) toEval_c
			= (symbol(any, string, map)) (option_map["table", "handle"]:nil);
		    symbol ret = toEval_c (opt_id, opt_key, event_descr);
		    if (ret != `_tp_normal)
			return ret;
		}
	    }
            option_map["_cwm_id"] = opt_id;
	    option_map["_cwm_key"] = opt_key;
	    // add generic handlers if needed
	    option_map = updateOptionMap (option_map, (map<string, any>) (descr["fallback"]:$[]));
            symbol ret = singleOptionEditPopup (option_map);
            if (ret == `_tp_ok)
            {
                if (event_id == `_tp_add)
                {
                    TableInit (descr, key);
                }
                else if (event_id == `_tp_edit)
                {
		    integer column
			= descr["_cwm_attrib", "changed_column"]:false
			    ? 2
			    : 1;
		    UI::ChangeWidget (`id (`_tp_table),
			`Item (opt_id, column),
			tableEntryValue (opt_id, option_map));
		    // also redraw the key field as it can be changed
		    column = column - 1;
		    UI::ChangeWidget (`id (`_tp_table),
			`Item (opt_id, column),
			tableEntryKey (opt_id, option_map));
		    if (descr["_cwm_attrib", "changed_column"]:false)
		    {
			 UI::ChangeWidget (`id (`_tp_table),
			    `Item (opt_id, 0),
			    tableEntryChanged (opt_id, option_map) ? "*" : "");
		    }
                }
            }
        }
	else if (event_id == `_tp_delete)
	{
	    any opt_id = UI::QueryWidget (`id (`_tp_table), `CurrentItem);
	    if (deleteTableItem (opt_id, descr))
		TableInit (descr, key);
	}
	else if (event_id == `_tp_table)
	{
	    any opt_id = UI::QueryWidget (`id (`_tp_table), `CurrentItem);
	    any key = id2key (descr, opt_id);
	    if (key == "____sep")
	    {
		list id_list = getIdList (descr);
		integer previous_index = 0;
		if (previous_selected_item != nil)
		{
		    previous_index = -1;
		    find (any e, id_list, ``{
			previous_index = previous_index + 1;
			return e == previous_selected_item;
		    });
		}
		integer current_index = -1;
		find (any e, id_list, ``{
		    current_index = current_index + 1;
		    return e == opt_id;
		});
		integer step = 0;
		if (current_index == 0)
		    step = 1;
		else if (current_index + 1 == size (id_list))
		    step = -1;
		else if (current_index >= previous_index)
		    step = 1;
		else
		    step = -1;
		integer new_index = current_index + step;
		opt_id = id_list[new_index]:nil;
		key = id2key (descr, opt_id);
		UI::ChangeWidget (`id (`_tp_table), `CurrentItem, opt_id);
	    }
	    previous_selected_item = opt_id;
	    map<string,any> opt_descr
		= key2descr (descr, id2key (descr, opt_id));
	    updateButtons (descr, opt_descr);
	}
	else if (event_id == `_tp_up || event_id == `_tp_down)
	{
	    any opt_id = UI::QueryWidget (`id (`_tp_table), `CurrentItem);
	    opt_id = moveTableItem (opt_id, descr, event_id == `_tp_up
		? `up
		: `down);
	    if (nil != opt_id)
	    {
		TableRedraw (descr, false);
		UI::ChangeWidget (`id (`_tp_table), `CurrentItem, opt_id);
		map<string,any> opt_descr
		    = key2descr (descr, id2key (descr, opt_id));
		updateButtons (descr, opt_descr);
	    }
	}
	return nil;
    }

    /**
      * Wrapper for TableInit using CWM::GetProcessedWidget () for getting
      * widget description map
      * @param key any widget key
      */
    global define void TableInitWrapper (string key) ``{
	TableInit (CWM::GetProcessedWidget (), key);
    }

    /**
      * Wrapper for TableHandle using CWM::GetProcessedWidget () for getting
      * widget description map
      * @param key any widget key
      * @param event_descr map event description map
      * @return symbol return value for wizard sequencer or nil
      */
    global define symbol TableHandleWrapper (string key, map event_descr) ``{
	return TableHandle(CWM::GetProcessedWidget (), key, event_descr);
    }

    /**
      * Get the map with the table widget
      * @param attrib map table attributes
      * @param widget_descr map widget description map of the table, will be
      *  unioned with the generated map
      * @return map table widget
      */
    global define map<string,any> CreateTableDescr (map<string,any> attrib, map<string,any> widget_descr) ``{
	ValidateTableAttr (attrib);
	term add_button = attrib["add_delete_buttons"]:true
	    ? `PushButton (`id (`_tp_add), `opt (`key_F3), Label::AddButton ())
	    : `HSpacing (0);
	term edit_button
	    = `PushButton (`id (`_tp_edit), `opt (`key_F4), Label::EditButton ());
	term delete_button = attrib["add_delete_buttons"]:true
	    ? `PushButton (`id (`_tp_delete), `opt (`key_F5), Label::DeleteButton ())
	    : `HSpacing (0);
	term table_header = attrib["changed_column"]:false
	    ? `header (
		// table header, shortcut for changed, keep very short
		_("Ch."),
		// table header
		_("Option"),
		// table header
		_("Value"))
	    : `header (
		// table header
		_("Option"),
		// table header
		_("Value"));

	term replace_point = `ReplacePoint (`id (`_tp_table_repl), `HSpacing (0));
	// help 1/4
	string help = _("<p><b><big>Editing the Settings</big></b><br>
To edit the settings, choose the appropriate
entry of the table then click <b>Edit</b>.</p>");
	if (attrib["add_delete_buttons"]:true)
	    // help 2/4, optional
	    help = help + _("<p>To add a new option, click <b>Add</b>. To remove
an option, select it and click <b>Delete</b>.</p>");

	if (attrib["changed_column"]:false)
	    // help 3/4, optional
	    help = help + _("<P>The<B> Ch.</B> column of the table is meant for
detecting whether the option was changed.</P>");

	if (attrib["up_down_buttons"]:false)
	    // help 4/4, optional
	    help = help + _("<p>To reorder the options, select some option
and use the <b>Up</b> or <b>Down</b> buttons to move it up or down
in the list.</p>");

	term up_down = attrib["up_down_buttons"]:false
	    ? `VBox (
		`VStretch (),
		// push button
		`PushButton (`id (`_tp_up), _("&Up")),
		// push button
		`PushButton (`id (`_tp_down), _("&Down")),
		`VStretch ()
	    )
	    : `HSpacing (0);

	map<string,any> ret = (map<string,any>)union ($[
            "custom_widget" : (`HBox (`HSpacing (2), `VBox (
		    `HBox (
	                `Table (`id (`_tp_table),
			    `opt (`immediate, `notify, `keepSorting),
			    table_header,
	                 []),
			up_down
		    ),
                    `HBox (
			add_button,
			edit_button,
			delete_button,
                        `HStretch (),
			replace_point
                    )
                ), `HSpacing (2))),
	    "_cwm_attrib" : attrib,
	    "widget" : `custom,
	    "help" : help,
	    "_cwm_do_validate" : ValidateTableDescr,
	], widget_descr);

	if (! haskey (ret, "init"))
	{
	    ret["init"] = TablePopup::TableInitWrapper;
	}
	if (! haskey (ret, "handle"))
	{
	    ret["handle"] = TablePopup::TableHandleWrapper;
	}

	return ret;
    }

}
