/**
 * File:
 *   ui.ycp
 *
 * Module:
 *   System Services (Runlevel) (formerly known as Runlevel Editor)
 *
 * Summary:
 *   Runlevel Editor user interface.
 *
 * Authors:
 *   Martin Vidner <mvidner@suse.cz>
 *   Petr Blahos <pblahos@suse.cz>
 *   Martin Lazar <mlazar@suse.cz>
 *
 * $Id: ui.ycp 22221 2005-03-03 14:15:43Z locilka $
 *
 * Runlevel editor user interface functions.
 */

{
    textdomain "runlevel";

    import "Service";
    import "RunlevelEd";
    import "Wizard";
    import "Label";
    import "Popup";

    define string StartedText (integer started);
    define string BstatusText (boolean disabled, integer started);
    define list mapkeys (map m);
    define string formatLine (list<string> l, integer len);
    define list startStopService (string service_name, string command);
    define boolean LongContinueCancelHeadlinePopup (string headline, term richtext, integer hdim, integer vdim);
    define void setServiceToDefault (string service_name);
    define map<string, boolean> tomap_true (list<string> l);
    define void updateServiceStatus (boolean use_func, string service_name);

    string current_service = "";
    boolean do_abort_now = false;

    // We read service status when dialog with services is shown.
    // We read status for services taken from list of services (service_list)
    // and then update map services.
    integer fetching_service_index = 0;
    // when fetching_service_status becomes false, we stop fetching services
    boolean fetching_service_status = true;

    // columns in the table where these items are located
    // -1 means it is not there
    integer c_dirty = -1;
    integer c_bstatus = -1;
    integer c_rstatus = -1;
    integer c_runlevels = -1;
    integer c_descr = -1;
    // index into table column for each runlevel
    map runlevel2tableindex = $[];


    /**
     * Create term of checkboxes for runlevel selection.
     * @return HBox full of checkboxes.
     */
    define term getRlCheckBoxes () ``{
	term rls = `HBox (`opt (`hstretch));
	foreach (string i, RunlevelEd::runlevels, ``{
	    if (size (rls) > 1)
		rls = add (rls, `HStretch ());
	    rls = add (rls, `CheckBox (`id (i), `opt (`notify), "&" + i));
	});
	return rls;
    }
    /**
     * Changes value of a runlevel checkbox.
     * Prevents triggering userinput by disabling notify.
     * @param service @ref service
     * @param rl which runlevel
     */
    define void updateRlCheckBox (map service, string rl) ``{
	list start = service["start"]:[];
	boolean old_notify = (boolean) UI::QueryWidget (`id (rl), `Notify);
	UI::ChangeWidget (`id (rl), `Notify, false);
	UI::ChangeWidget (`id (rl), `Value, contains (start, rl));
	UI::ChangeWidget (`id (rl), `Notify, old_notify);
    }
    /**
     * Changes values of runlevel checkboxes.
     * @param service @ref service
     */
    define void updateRlCheckBoxes (map service) ``{
	foreach (string i, RunlevelEd::runlevels, ``{
	    updateRlCheckBox (service, i);
	});
    }
    /**
     * Update the long description text box
     * @param service @ref service
     */
    define void updateDescription (map service) ``{
	// For the box, use the long description.
	// The short one as a fallback. #20853
	string descr = service["description"]:"";
	if (descr == "")
	{
	    descr = service["shortdescription"]:"";
	}
	UI::ChangeWidget (`id (`description), `Value, descr);
    }
    /**
     * Sets runlevel columns in the table.
     * @param service_name which line
     * @param service @ref service
     * @param rls which columns to update, nil == all
     */
    define void updateRlColumns (string service_name, map service,
					list<string> rls) ``{
	if (c_runlevels == -1)
	{
	    return;
	}

	list start = service["start"]:[];
	if (rls == nil)
	{
	    rls = RunlevelEd::runlevels;
	}
	foreach (string rl, rls, ``{
	    UI::ChangeWidget (`id (`table),
			      `Item (service_name, runlevel2tableindex[rl]:-1),
			      contains (start, rl) ? rl : " ");
	});
    }

    /**
     * Update run-time status column.
     * @param service_name which line
     * @param started status or -1 (unknown yet)
     */
    define void updateStatusColumn (string service_name, integer started) ``{
	if (c_rstatus >= 0) {
	    UI::ChangeWidget(`id(`table), `Item(service_name, c_rstatus), StartedText(started));
	}
	if (c_bstatus >= 0) {
	    boolean disabled = RunlevelEd::isDisabled(RunlevelEd::services[service_name]:$[]);
	    UI::ChangeWidget(`id(`table), `Item(service_name, c_bstatus), BstatusText(disabled, started));
	}
    }
    /**
     * Helper function for fetching service status in run-time.
     * @param service_name which line
     * @param service @ref service
     * @param started status or -1 (unknown yet)
     */
    define void updateStatusInTable (string service_name, map service,
				     integer started) ``{
	// just translate the arguments. the callback is generic
	// because of the future simple UI, bug #13789
	updateStatusColumn (service_name, started);
    }

    /**
     * Changes values of runlevel checkboxes.
     * Get the status if not known yet.
     */
    define void changeService1 (map service) ``{
	if (service["started"]:-1 < 0)
	{
	    integer started = Service::RunInitScript (current_service,
						       "status");
	    service["started"] = started;
	    RunlevelEd::services[current_service] = service;
	    updateStatusColumn (current_service, started);
	}
	updateRlCheckBoxes (service);
	updateDescription (service);
    }

    /**
     * Reads data from checkboxes and updates service
     * and RunlevelEd::services maps.
     * @param service_name which service
     * @param service @ref service
     * @return @ref service
     */
    define map queryRlCheckBoxes (string service_name, map service) ``{
	list start_in = [];
	foreach (string i, RunlevelEd::runlevels, ``{
	    if ((boolean) UI::QueryWidget (`id (i), `Value))
	    {
		start_in[size(start_in)] = i;
	    }
	});
	if (service["start"]:[] != start_in)
	{
	    service = union (service, $[
				 "start": start_in,
				 "dirty": true
				 ]);
	    RunlevelEd::services[service_name] = service;
	}
	return service;
    }

    // mapping numbers to descriptions
    /**
     * Get help text for rcscript start|stop command exit value.
     * @param exit exit value
     * @return string help text
     */
    define string getActionReturnHelp (integer exit) ``{
	map <integer, string> descr =
	    $[
		// Init script non-status-command return codes
		// http://www.linuxbase.org/spec/gLSB/gLSB/iniscrptact.html
		// status code.
		// Changed "Foo bar." to "foo bar", #25082
		0: _("success"),
		/* 1: handled as default below */
		// status code.
		2: _("invalid or excess arguments"),
		// status code.
		3: _("unimplemented feature"),
		// status code.
		4: _("user had insufficient privileges"),
		// status code.
		5: _("program is not installed"),
		// status code.
		6: _("program is not configured"),
		// status code.
		7: _("program is not running"),
		];
	// status code.
	return descr[exit]:_("unspecified error");
    }
    /**
     * Get help text for rcscript status return value
     * according to LSB.
     * @param exit exit value
     * @return string help text
     */
    define string getStatusReturnHelp (integer exit) ``{
	map <integer, string> descr =
	    $[
		// Init script "status" command return codes
		// http://www.linuxbase.org/spec/gLSB/gLSB/iniscrptact.html
		// status code.
		// Changed "Foo bar." to "foo bar", #25082
		0: _("program is running"),
		// status code.
		1: _("program is dead and /var/run pid file exists"),
		// status code.
		2: _("program is dead and /var/lock lock file exists"),
		// status code.
		3: _("program is stopped"),
		// status code.
		4: _("program or service status is unknown"),
		];
	// status code.
	return descr[exit]:_("unspecified error");
    }

    /**
     * Returns items for default runlevel combo box.
     * (Excludes 0, 1, 6, S and B)
     * @param mode if `auto, adds Unchanged. if `proposal, only 2, 3 and 5
     * @return list List of items. Default is selected.
     */
    define list getDefaultPicker (symbol mode) ``{
	list items = [];
	list<string> rls = RunlevelEd::runlevels;
	if (mode == `auto)
	{
	    rls = prepend (rls, "");
	}
	else if (mode == `proposal)
	{
	    // We could read the list from SCR (#37071) but
	    // inittab in the inst-sys is pre-lsb so we have to override it
	    rls = ["2", "3", "5"];
	}

	foreach (string i, rls, ``{
	    // which ones to avoid: #36110
	    if ("0" != i && "1" != i && "6" != i && "S" != i && "B" != i)
	    {
		items[size(items)] = `item (`id (i),
					   RunlevelEd::getRunlevelDescr (i),
					   i == RunlevelEd::default);
	    }
	});
	return items;
    }

    /**
     * @param	rll	a list of runlevels or nil, meaning "all"
     * @return		"in [these] runlevels" (translated)
     */
    define string getInRunlevels (list<string> rll) ``{
	if (rll == nil)
	{
	    // translators: substituted into a message like
	    // "To enable/disable foo IN ALL RUNLEVELS, this must be done..."
	    // (do not include the trailing comma here)
	    return _("in all runlevels");
	}
	else
	{
	    string s = mergestring (rll, ", ");
	    // translators: substituted into a message like
	    // "To enable/disable foo IN RUNLEVELS 3, 5, this must be done..."
	    // (do not include the trailing comma here)
	    return size(rll)>1 ? sformat(_("in runlevels %1"), s) : sformat(_("in runlevel %1"), s);
	}
    }

    /**
     * @param started status or -1 (unknown yet)
     * @return "Yes", "No" or "???"
     */
    define string StartedText (integer started) ``{
	return (0 == started ?
		// is the service started?
		_("Yes") :
		(started > 0 ?
		 // is the service started?
		 _("No") :
		 // is the service started?
		 // ???: we do not know yet what is the service state
		 _("???")));
    }
    
    /**
     * @param disabled status
     * @param started status or -1 (unknown yet)
     * @return "Yes", "Yes*", "No", "No*" or "???"
     */
    define string BstatusText (boolean disabled, integer started) ``{
	string state = (started<0) ? _("???") : (!disabled ? _("Yes") : _("No"));
	if (started>=0 && (started>0 != disabled)) {
	    state = state + "*";
	}
	return state;
    }


    //start unsorted
    /**
     * Ask if really abort. Uses boolean changed_settings. Sets boolean do_abort_now.
     * @return boolean true if user really wants to abort
     */
    define boolean reallyAbort ()``{
	if (do_abort_now || !RunlevelEd::isDirty ())
	    {
		do_abort_now = true;
		return true;
	    }
	do_abort_now = Popup::ReallyAbort (true);
	return do_abort_now;
    }

    /**
     * Create table items from services.
     * For simple mode, also filter out critical services: boot ones.
     * For Expert mode:
     * Mixin: started, start, (short)description.
     * @param mix which items to mix in:<pre>
     *`simple:	id=name, name, bstatus,            (short)description
     *`complex:	id=name, name, rstatus, runlevels, (short)description
     *`auto:	id=name, name, dirty,   runlevels, ?(short)description
     *</pre>
     * @return list List of items for table.
     */
    define list servicesToTable (symbol mix) ``{
	boolean m_dirty = mix == `auto;
	boolean m_bstatus = mix == `simple;
	boolean m_rstatus = mix == `complex;
	boolean m_runlevels = mix != `simple;
	boolean m_descr = true;

	// assume it is not there until placed in
	c_dirty = -1;
	c_bstatus = -1;
	c_rstatus = -1;
	c_runlevels = -1;
	c_descr = -1;
	runlevel2tableindex = $[];

	// filter out services that are too important to be messed with
	// in the simple mode
	map<string,map> services = RunlevelEd::services;
	if (mix == `simple)
	{
	    services = (map<string, map>)filter (string s_name, map s, services, ``(
				   ! contains (s["defstart"]:[], "B")
				   ));
	}

	list items = [];
	boolean first = true;
	foreach (string k, map v, services, ``{
	    if (first)
	    {
		first = false;
		// preserve current service when switching modes
		if (!haskey (services, current_service))
		{
		    current_service = k;
		}
	    }
	    // id=name, name
	    term item = `item (`id (k), k);
	    // column where a item is added
	    integer col = 1;
	    if (m_dirty)
	    {
		// dirty
		c_dirty = col;
		item = add (item, v["dirty"]:false ? UI::Glyph(`CheckMark) : " ");
		col = col + 1;
	    }
	    if (m_bstatus)
	    {
		// boot status
		boolean disabled = RunlevelEd::isDisabled(v);
		integer started = v["started"]:-1;
		c_bstatus = col;
		item = add (item, BstatusText(disabled, started));
		col = col + 1;
	    }
	    if (m_rstatus)
	    {
		// runtime status
		integer started = v["started"]:-1;
		c_rstatus = col;
		item = add (item, StartedText (started));
		col = col + 1;
	    }
	    if (m_runlevels)
	    {
		// runlevels
		list rl = v["start"]:[];
		c_runlevels = col;
		foreach (string i, RunlevelEd::runlevels, ``{
		    runlevel2tableindex[i] = col;
		    item = add (item, (contains (rl, i)? i : " "));
		    col = col + 1;
		});
	    }
	    if (m_descr)
	    {
		// (short)description
		// For the table, use the short description.
		// The long one as a fallback. #20853
		string descr = v["shortdescription"]:"";
		if (descr == "")
		{
		    descr = v["description"]:"";
		}
		c_descr = col;
		item = add (item, descr);
		col = col + 1;
	    }
	    // next
	    items[size(items)] = item;
	});
	return items;
    }

    /**
     * For each service, determines its status and calls a supplied function.
     * @param func function to call
     * @see updateStatusInTable
     */
    define void serviceStatusIterator (boolean use_func) ``{
	if (!fetching_service_status)
	{
	    return;
	}
	if (fetching_service_index >= size (RunlevelEd::service_list))
	{
	    fetching_service_status = false;
	    return;
	}
	string service_name = RunlevelEd::service_list[fetching_service_index]:"";
	fetching_service_index = fetching_service_index + 1;

        updateServiceStatus(use_func, service_name);
    }

    /**
     * For specified service, determines its status and calls a supplied function.
     * @param func function to call
     * @see updateStatusInTable
     */
    define void updateServiceStatus (boolean use_func, string service_name) ``{
	if (RunlevelEd::services[service_name, "started"]:-1 < 0) {
            integer started = Service::RunInitScript (service_name, "status");

            RunlevelEd::services[service_name, "started"] = started;

            if (use_func)
            {
                updateStatusInTable(service_name, RunlevelEd::services[service_name]:$[], started);
            }
	}
    }

    /**
     * help text for progress
     * @return help text
     */
    define string getHelpProgress () ``{
	return
	    // help text
	    _("<P><BIG><B>System Service (Runlevel) Initialization</B></BIG><BR>
Please wait...</P>
")
	    +
	    // warning
	    _("<p><b>Note:</b> The system services (runlevel editor) is an expert tool. Only change settings if
 you know what you are doing.  Otherwise your system might not function properly afterwards.</p>
")
	    ;
    }

    /**
     * Enable or disable a service in some runlevels.
     * Set the variables and update the ui (rl columns).
     * @param service_name	a service
     * @param rls	which runlevels, nil == disable in all
     * @param enable	enabling or disabling?
     */
    define void SetService (string service_name,
				   list<string> rls, boolean enable) ``{
	map service = RunlevelEd::services[service_name]:$[];
	list<string> start_in = nil;
	if (rls == nil)
	{
	    start_in = [];
	}
	else
	{
	    map<string, boolean> start = tomap_true (service["start"]:[]);
	    foreach (string rl, rls, ``{
		start = (map<string, boolean>) add (start, rl, enable);
	    });
	    start = (map<string, boolean>) filter (string k, boolean v, start, ``( v == true));
	    start_in = (list<string>) mapkeys (start);
	}

	if (service["start"]:[] != start_in)
	{
	    service = union (service, $[
				 "start": start_in,
				 "dirty": true
				 ]);
	    RunlevelEd::services[service_name] = service;

	    updateRlColumns (service_name, service, rls);
	    updateStatusColumn (service_name, service["started"]:-1);
	}
    }

    /**
     * Check that all the services exist (in RunlevelEd::services).
     * If not, popup a list of the missing ones and ask whether
     * continue or not. Filter out the missing ones.
     * @param services a service list
     * @return [continue?, filtered list]
     */
    define list CheckMissingServices (list<string> services) ``{
	list<string> missing = [];
	boolean ok = true;
	services = filter (string s, services, ``{
	    if (haskey (RunlevelEd::services, s))
	    {
		return true;
	    }
	    else
	    {
		missing[size(missing)] = s;
		return false;
	    }
	});
	if (size (missing) > 0)
	{
	    // missing services only occur when enabling
	    ok = Popup::ContinueCancel (
		sformat (
		    // continue-cancel popup when enabling a service
		    // %1 is a list of unsatisfied dependencies
		    _("These required services are missing:\n%1."),
		    formatLine (missing, 40)));
	}
	return [ok, services];
    }


    /**
     * Generic function to handle enabling, disabling, starting and
     * stoping services and their dependencies, in various runlevels.
     * Piece of cake ;-) <br>

     * Either of init_time or run_time can be specified (for complex
     * mode) or both (for simple mode).

     * rls: ignored for -init +run

     * What it does: gets dependent services (in the correct order),
     * filters ones that are already in the desired state, if there
     * are dependencies left, pop up a confirmation dialog, check for
     * missing dependencies, perform the action (run-time, then init-time)
     * for the deps and the
     * service (in this order), displaying output after each error and
     * at the end.
     *

     * @param service_name	name of service
     * @param rls		in which run levels, nil == all
     * @param enable		on/off
     * @param init_time		do enable/disable
     * @param run_time		do start/stop
     * @return success (may have been canceled because of dependencies)
     */
    define boolean ModifyServiceDep (string service_name,
					    list<string> rls,
					    boolean enable,
					    boolean init_time,
					    boolean run_time) ``{
	y2debug (1, "Modify: %1 %2 %3 %4 %5",
		 service_name,
		 rls,
		 enable,
		 init_time? "+init": "-init",
		 run_time? "+run": "-run");
	boolean one = (rls != nil) ? (size (rls) == 1) : false;
	string command = enable? "start": "stop";

	// get dependent services
	list<string> dep_s = RunlevelEd::ServiceDependencies (service_name, enable);
	y2debug ("DEP: %1", dep_s);

        // ensure we have already determined the service status (#36171)
        foreach(string service_name, dep_s, {
		updateServiceStatus(false, service_name);
	});

	// filter ones that are ok already
	dep_s = RunlevelEd::FilterAlreadyDoneServices (dep_s, rls, enable,
						       init_time, run_time);
	y2debug ("DEP filtered: %1", dep_s);

	boolean doit = size (dep_s) == 0;
	// if there are dependencies left, pop up a confirmation dialog
	if (!doit)
	{
	    string key =
		(run_time? "+run": "-run") + "," +
		(init_time? "+init": "-init") + "," +
		(enable? "on": "off");
	    map texts = $[
		//"-run,-init,..." does not make sense

		// *disable*
		// continue-cancel popup
		// translators: %2 is "in runlevel(s) 3, 5"
		// or "in all runlevels"
		"-run,+init,off" : _("To disable service %1 %2,
these services must be additionally disabled,
because they depend on it:
%3."),
		// *enable*
		// continue-cancel popup
		// translators: %2 is "in runlevel(s) 3, 5"
		// or "in all runlevels"
		"-run,+init,on" : _("To enable service %1 %2,
these services must be additionally enabled,
because it depends on them:
%3."),
		// *stop*
		// continue-cancel popup
		"+run,-init,off" : _("To stop service %1,
these services must be additionally stopped,
because they depend on it:
%2."),
		// *start*
		// continue-cancel popup
		"+run,-init,on" : _("To start service %1,
these services must be additionally started,
because it depends on them:
%2."),
		// *stop and disable*
		// continue-cancel popup
		// translators: %2 is "in runlevel(s) 3, 5"
		// or "in all runlevels"
		"+run,+init,off" :_("To stop service %1 and disable it %2,
these services must be additionally stopped
and disabled, because they depend on it:
%3."),
		// *start and enable*
		// continue-cancel popup
		// translators: %2 is "in runlevel(s) 3, 5"
		// or "in all runlevels"
		"+run,+init,on" : _("To start service %1 and enable it %2,
these services must be additionally started
and enabled, because it depends on them:
%3."),
		];
	    string dep_formatted = formatLine (dep_s, 40);
	    doit = Popup::ContinueCancel (
		sformat (
		    texts[key]:"?",
		    service_name,
		    // non-init-time does not need the runlevels
		    // so we omit it not to confuser the translators
		    init_time? getInRunlevels (rls): dep_formatted,
		    dep_formatted
		    )
		);
	}

	// check for missing services
	if (doit)
	{
	    list r = CheckMissingServices (dep_s);
	    doit = r[0]:false;
	    dep_s = r[1]:[];
	}


	string rich_message = "";
	if (doit)
	{
	    // iterate dep_s, not including service_name
	    // because we will ask "continue?" on error
	    // Foreach with a break: find the failing one
	    find (string s, dep_s, ``{
		if (run_time)
		{
		    list ret = startStopService (s, command);

		    rich_message = rich_message + ret[1]:"";
		    integer exit = ret[0]:-1;
		    if (exit != 0)
		    {
			doit = LongContinueCancelHeadlinePopup (
			    // popup heading
			    _("An error has occurred."), `RichText (rich_message),
			    70, 10);
			// don't show what we've already seen
			rich_message = "";
			if (!doit)
			{
			    return true; // break(foreach)
			}
		    }
		}
		if (init_time)
		{
		    // set the variables and update the ui
		    SetService (s, rls, enable);
		}
		return false;
	    });
	}
	if (doit)
	{
	    // only for service_name
	    if (run_time)
	    {
		list ret = startStopService (service_name, command);

		rich_message = rich_message + ret[1]:"";
		Popup::LongText ("", `RichText(rich_message), 70, 5);

                // don't enable the service if it can't be started, #36176
                if (ret[0]:-1 != 0) return false;
	    }

            if (init_time)
	    {
		// set the variables and update the ui
		SetService (service_name, rls, enable);
	    }
	}
	return doit;
    }


    /**
     * Turns a service on or off in the simple mode, ie. resolving
     * dependencies and for each service doing start,enable or
     * stop,disable.
     * @param service_name	name of service
     * @param rls		in which run levels, nil == all
     * @return success (may have been canceled because of dependencies)
     */
    define boolean SimpleSetServiceDep (string service_name,
					       boolean enable) ``{
	list<string> rls = enable?
	    RunlevelEd::services[current_service, "defstart"]:[] :
	    nil;
	return ModifyServiceDep (service_name, rls, enable, true, true);
    }


    /**
     * Used for enabling/disabling a service and services depending on
     * it in a runlevel or a set of runlevels.
     * @param service_name	name of service
     * @param rls		in which run levels, nil == all
     * @param enable		enable/disable
     * @return success (may have been canceled because of dependencies)
     */
    define boolean EnableDisableServiceDep (string service_name,
						   list<string> rls,
						   boolean enable) ``{
	return ModifyServiceDep (service_name, rls, enable, true, false);
    }

    /**
     * Used for starting/stopping a service and services depending on it.
     * Displays result popups.
     * @param service_name	name of service
     * @param enable		start/stop
     * @return success (may have been canceled because of dependencies)
     */
    define boolean StartStopServiceDep (string service_name,
					       boolean enable) ``{
	return ModifyServiceDep (service_name, [], enable, false, true);
    }


    /**
     * Starts/stops/checks status of a service
     * @param service_name service to start/stop
     * @param command "start" or "stop" or "status"
     * @return [integer exit_status, string rich_message]
     */
    define list startStopService (string service_name,
					 string command) ``{
	UI::OpenDialog (`Label (service_name + " " + command));
	map out = Service::RunInitScriptOutput (service_name, command);
	UI::CloseDialog ();

	integer exit = out["exit"]:-1;
	string rich_message = sformat (
	    // %1 service, %2 command,
	    // %3 exit status, %4 status description, %5 stdout
	    // added a colon
	    _("<p><b>/etc/init.d/%1 %2</b> returned %3 (%4):<br>%5</p>"),
	    service_name, command, exit,
	    (command == "status" ?
	     getStatusReturnHelp (exit) :
	     getActionReturnHelp (exit)),
	    sformat ("<pre>%1</pre>", out["stdout"]:"")
	    );
	// succesful stop => status: program not running
	integer started = (exit == 0 && command == "stop")? 3: exit;
	// normally "started" has the exit code of "status",
	// and we may be adding output of a different command
	// but it is only tested against zero anyway
	map service = RunlevelEd::services[service_name]:$[];
	service["started"] = started;
	RunlevelEd::services[service_name] = service;
	// this won't work in simple mode
	// make a map "item"->column_number, depending on mode
	// then the functions can consult it and do nothing if not present
	updateStatusColumn (service_name, service["started"]:-1);
	return [exit, rich_message];
    }

    /**
     * Prints list items into a string, separating them by commas
     * and when line exceeds len characters, it does line break (\n).
     * It adds 5 spaces before each line.
     * Do not expect reasonable results if you set len < 0.
     * @param l list of strings
     * @param len minimal length of line
     * @return string formated string
     */
    define string formatLine (list<string> l, integer len) ``{
	string s = "";
	string line = "     ";
	string add_sep = "";
	string line_sep = "";
	foreach (string i, l, ``{
	    if (size (line) > len)
		{
		    s = s + line_sep + line;
		    line_sep = ",\n";
		    line = "     ";
		}
	    else
		{
		    line = line + add_sep;
		}
	    line = line + i;
	    add_sep = ", ";
	});
	if (size (line) > 0)
	    {
		s = s + line_sep + line;
	    }
	return s;
    }

    /**
     * Checks what services should run in this runlevel and do not run
     * or what services run but should not run.
     * @return string overview text
     */
    define string overviewText () ``{
	list<string> should_not_run = [];
	list<string> should_run = [];
	foreach (string k, map v, RunlevelEd::services, ``{
	    if (RunlevelEd::StartContainsImplicitly  (v["start"]:[],
						      RunlevelEd::current))
		{   // it should run
		    if (0 != v["started"]:-1)
			{
			    should_run[size(should_run)] = k;
			}
		}
	    else
		{
		    if (0 == v["started"]:-1)
			{
			    should_not_run[size(should_not_run)] = k;
			}
		}
	});
	string s = "";
	if (size (should_run) + size (should_not_run) > 0)
	    {
		// message label
		s = s + "\n\n"/* + _("Overview") + "\n\n"*/;
		if (size (should_not_run) > 0)
		    {
			// list of services will follow
			s = s + _("Following services run in current\nrunlevel although they should not:");
			s = s + "\n" + formatLine (should_not_run, 35) + "\n\n";
		    }

		if (size (should_run) > 0)
		    {
			// list of services will follow
			s = s + _("Following services do not run in current\nrunlevel although they should:");
			s = s + "\n" + formatLine (should_run, 35) + "\n\n";
		    }
	    }
	return s;
    }

    /**
     * Radio buttons (faking tabs) for switching modes
     * @param mode `simple or `complex, which one are we in
     * @return RadioButtonGroup term
     */
    define term ModeTabs (symbol mode) ``{
	term rbg =
	    // fake tabs using radio buttons
	    `RadioButtonGroup (
		`id (`rbg),
		`HBox (
		    `RadioButton (`id (`simple), `opt (`notify),
				  // radio button label
				  _("&Simple Mode"), mode == `simple),
		    `HSpacing (3),
		    `RadioButton (`id (`complex), `opt (`notify, `key_F7),
				  // radio button label
				  _("&Expert Mode"), mode == `complex)
		    )
		);
	return `Left (rbg);
    }


    /**
     * help text services dialog
     * @return help text
     */
    define string getHelpComplex () ``{
	// help text
	return
	    // help text
	    _("<p>Assign system services to runlevels by selecting the list entry of the respective service then
checking or unchecking the <b>check boxes B-S</b> for the runlevel.</p>
")
	    +
	    // help text
	    _("<p><b>Start/Stop/Refresh:</b> Use this to start or stop services individually.</p>")
	    +
	    // help text
	    _("<P><B>Set and Reset:</B>
Select runlevels in which to run the currently selected service.<ul>
<li><b>Enable the service:</b> Activates the service in the standard runlevels.</li>
<li><b>Disable the service:</b> Deactivates service.</li>
<li><b>Enable all services:</b> Activates all services in their standard runlevels.</li>
</ul></p>
")
	    +
	    // The change does not occur immediately. After a reboot the system boots into the specified runlevel.
	    _("<p>Changes to the <b>default runlevel</b> will take effect next time you boot your computer.</p>")
	    ;
    }

    /**
     * Main dialog for changing services.
     * @return symbol for wizard sequencer
     */
    define symbol ComplexDialog () ``{
	Wizard::SetScreenShotName ("runlevel-2-services");

	// currently selected service we are working with
	map service = $[];

	term header = `header (
	    // table header
	    _("Service"),
	    // table header. is a service running?
	    _("Running"));
	foreach (string i, RunlevelEd::runlevels, ``{
//	    header = add (header, `Center (" " + i + " "));
	    header = add (header, `Center (i));
	});
	// headers in table
	header = add (header, _("Description"));

	list args = WFM::Args ();
	// should we show debugging buttons?
	boolean show_debug = args[0]:"" == "debug";
	// should we show the Restore to default button?
	boolean show_restore = true;

	term contents = `VBox (
	    `VSpacing (0.4),
	    ModeTabs (`complex),
	    /*
	    `HBox (
		// preserve 2 spaces at the end.
		`Label (_("Current runlevel:  ")),
		`Label (`opt (`outputField, `hstretch), getRunlevelDescr (RunlevelEd::current))
		),
	    */
	    // combo box label
	    `ComboBox (`id (`default_rl), `opt (`hstretch), _("&Set default runlevel after booting to:"), getDefaultPicker (`complex)),

	    `VSpacing (0.4),
	    `Table (`id (`table), `opt (`notify, `immediate),
		    header,
		    servicesToTable (`complex)
		),
	    `VSquash (
		`HBox (
		    `VSpacing (3.4), // 3+borders in qt, 3 in curses
		    `RichText (`id (`description), `opt (`shrinkable), "")
		    )
		),
	    `VBox (
		// label above checkboxes
		`Label (`id (`service_label), `opt (`hstretch), _("Service will be started in following runlevels:")),
		getRlCheckBoxes ()
		),
	    `HBox (
		// menubutton label
		`MenuButton (_("S&tart/Stop/Refresh"), [
				 // menu item
		    `item (`id (`start), _("&Start now ...")),
				 // menu item
		    `item (`id (`stop), _("S&top now ...")),
				 // menu item
		    `item (`id (`status), _("&Refresh status ...")),
		    ]),
		`HStretch (),
		`ReplacePoint (
		    `id (`menubutton),
		    // menubutton label
		    `MenuButton (_("Set/&Reset"), [
				 // menu item
			`item (`id (`to_enable), /*`opt (`key_F3),*/ _("&Enable the service")),
				 // menu item
			`item (`id (`to_disable), /*`opt (`key_F5),*/ _("&Disable the service")),
			]
			+ (!show_restore ? [] :
			[
			    //TODO
			])
			+
			[
				 // menu item
			`item (`id (`to_all_enable), _("Enable &all services")),
			]
			+ (!show_debug ? [] :
			[
				// menu item
			`item (`id (`depviz), _("Save Dependency &Graph")),
			])
			)
		    )
		)
	    );
	// dialog caption.
	Wizard::SetContents (_("System Services (Runlevel): Details"), contents, getHelpComplex (), true, true);

	UI::ChangeWidget (`id (`table), `CurrentItem, current_service);
	service = RunlevelEd::services[current_service]:$[];
	changeService1 (service);

	any ret = nil;
	while (`next != ret && `back != ret && `abort != ret && `simple != ret)
	    {
		// Kludge, because a `Table still does not have a shortcut.
		// #16116
		UI::SetFocus (`id (`table));

		if (fetching_service_status)
		{
		    ret = UI::PollInput ();
		    UI::NormalCursor ();
		    if (nil == ret)
		    {
			serviceStatusIterator (true);
			continue;
		    }
		    UI::BusyCursor ();
		}
		else
		{
		    ret = UI::UserInput ();
		}
		if (ret == `cancel)
		{
		    ret = `abort;
		}

		current_service = (string) UI::QueryWidget (`id (`table), `CurrentItem);

		if (`abort == ret)
		{
		    if (!reallyAbort ())
		    {
			ret = nil;
			continue;
		    }
		}
		else if (`next == ret)
		{
		    // TODO: check dependencies of all services? (on demand?)
		    // string nfs_adj = RunlevelEd::CheckPortmap ();
		    // ...

		    if (RunlevelEd::isDirty ())
		    {
			// yes-no popup
			if (!Popup::YesNo (_("Now the changes to runlevels \nwill be saved.")))
			{
			    ret = nil;
			    continue;
			}
		    }
		    RunlevelEd::default = (string) UI::QueryWidget (`id (`default_rl), `Value);
		    break;
		}
		else if (`table == ret)
		{
		    service = RunlevelEd::services[current_service]:$[];
		    changeService1 (service);
		}
		else if (nil != ret && is (ret, string))
		{
		    // checkbox pressed
		    // - enable/disable current_service in one runlevel
		    boolean enable = (boolean) UI::QueryWidget (`id (ret), `Value);
		    list<string> rls = (list<string>) [ret];
		    if (!EnableDisableServiceDep (current_service, (list<string>) [ret], enable))
		    {
			// restore the check box
			updateRlCheckBox (service, (string) ret);
		    }
		}
		else if (`depviz == ret)
		{
		    string filename = UI::AskForSaveFileName(".", "*", "");
		    SCR::Write (.target.string, filename,
				RunlevelEd::DotRequires ());
		}
		else if (`to_enable == ret)
		{
		    list<string> default = RunlevelEd::services[current_service, "defstart"]:[];
		    EnableDisableServiceDep (current_service, default, true);
		    service = RunlevelEd::services[current_service]:$[];
		    changeService1 (service);
		}
		else if (`to_disable == ret)
		{
		    EnableDisableServiceDep (current_service, nil, false);
		    service = RunlevelEd::services[current_service]:$[];
		    changeService1 (service);
		}
		else if (`to_all_enable == ret)
		{
		    // yes-no popup
		    if (Popup::YesNo (_("Really enable all services?")))
		    {
			foreach (string k, map v, RunlevelEd::services, ``{
			    setServiceToDefault (k);
			});
			UI::ChangeWidget (`id (`table), `Items, servicesToTable (`complex));
			// message popup
			Popup::Message (_("Each service was enabled\nin the appropriate runlevels."));
		    }
		}
		else if (`start == ret || `stop == ret)
		{
		    boolean really = true;
		    if (`stop == ret && "xdm" == current_service)
		    {
			// yes-no popup. the user wants to stop xdm
			if (!Popup::YesNo ( _("This may kill your X session.\n\nProceed?")))
			{
			    really = false;
			}
		    }
		    if (really)
		    {
			StartStopServiceDep (current_service, ret == `start);
		    }
		}
		else if (`status == ret)
		{
		    // similar to startStopService but there will be changes
		    // when dependencies are checked

		    //TODO: find a place for it
		    //Popup::Message (overviewText ());
		    list r = startStopService (current_service, "status");
		    Popup::LongText ("", `RichText(r[1]:""), 70, 5);
		}
	    }

	Wizard::RestoreScreenShotName ();
	return (symbol) ret;
    }

    // simple mode
    /**
     * Main dialog for changing services.
     * @return symbol for wizard sequencer
     */
    define symbol SimpleDialog () ``{
	Wizard::SetScreenShotName ("runlevel-2-simple");
	map service = $[];

	string help_text =
	    // Simple mode dialog
	    // help text
	    _("<p>Here, specify which system services should be started.</p>")
	    +
	    // warning
	    _("<p><b>Warning:</b> The system services (runlevel editor) is an expert tool. Only change settings if you know
 what you are doing.  Otherwise your system might not function properly afterwards.</p>
")
	    +
	    // help text
	    // Activate is a button
	    _("<p><b>Activate</b> starts the selected service and services
that it depends on and enables them to start at system boot time.
Likewise, <b>Deactivate</b> stops services that depend on a given service
and the service itself and disables their start at system boot time.</p>")
	    +
	    // help text
	    _("<p>An asterisk (*) after a service status means that the service is enabled but not running or is disabled but running now.</p>")
	    +
	    // help text
	    _("<p>To change the behavior of runlevels and system services in detail, click <b>Expert Mode</b>.</p>\n")
	    ;

	term contents = `VBox (
	    `VSpacing (0.4),
	    ModeTabs (`simple),
	    `VSpacing (0.4),
	    `Table (`id (`table), `opt (`notify, `immediate),
		    `header (_("Service"), _("Enabled"), _("Description")),
		    servicesToTable (`simple)
		),
	    `VSquash (
		`HBox (
		    `VSpacing (3.4), // 3+borders in qt, 3 in curses
		    `RichText (`id (`description), `opt (`shrinkable), "")
		    )
		),
	    `HBox (
		// Button label
		`PushButton (`id (`enable), `opt (`key_F3),  _("Ena&ble")),
		// Button label
		`PushButton (`id (`disable), `opt (`key_F5), _("&Disable"))
		)
	    );
	// dialog caption.
	Wizard::SetContentsButtons (_("System Services (Runlevel): Services"), contents, help_text, Label::BackButton(), Label::FinishButton());

	UI::ChangeWidget (`id (`table), `CurrentItem, current_service);
	service = RunlevelEd::services[current_service]:$[];
	updateDescription (service);

	
	UI::SetFocus (`id (`table));
	any ret = nil;
	boolean focustable = false;
	while (`next != ret && `back != ret && `abort != ret && `complex != ret)
	{
	    if (focustable) {
		// Kludge, because a `Table still does not have a shortcut.
		// #16116
		UI::SetFocus (`id (`table));
	    }
	    
	    if (fetching_service_status)
	    {
		ret = UI::PollInput ();
		UI::BusyCursor ();
		if (nil == ret)
		{
		    serviceStatusIterator (true);
		    continue ;
		}
	    }
	    else
	    {
		ret = UI::UserInput ();
		focustable = true;
	    }
	    if (ret == `cancel)
	    {
		ret = `abort;
	    }

	    current_service = (string) UI::QueryWidget (`id (`table), `CurrentItem);

	    if (`abort == ret)
	    {
		if (!reallyAbort ())
		{
		    ret = nil;
		    continue;
		}
	    }
	    else if (`next == ret)
	    {
		// FIXME copied from ComplexDialog, make it a function
		// Misaligned for consistency with the original

		    // TODO: check dependencies of all services? (on demand?)
		    // string nfs_adj = RunlevelEd::CheckPortmap ();
		    // ...

		    if (RunlevelEd::isDirty ())
		    {
			// yes-no popup
			if (!Popup::YesNo (_("Now the changes to runlevels \nwill be saved.")))
			{
			    ret = nil;
			    continue;
			}
		    }
		    break;
	    }
	    else if (`table == ret)
	    {
		service = RunlevelEd::services[current_service]:$[];
		updateDescription (service);
	    }
	    else if (`disable == ret)
	    { //FIXME...
		y2milestone ("%1", ret);
		SimpleSetServiceDep (current_service, false);
	    }
	    else if (`enable == ret)
	    { //FIXME...
		y2milestone ("%1", ret);
		SimpleSetServiceDep (current_service, true);
	    }
	}
	Wizard::RestoreScreenShotName ();
	return (symbol) ret;
    }


    // Autoyast UI
    /**
     * Help text for auto-complex-screen
     * @return help text
     */
    define string getHelpAuto () ``{
	return
	    // help text
	    _("<p><b>Prepare data for autoinstallation.</b></p>")
	    +
	    // help text
	    _("<p>Change the services to requested state. Only services marked as changed will really be changed in the target system.</p>")
	    +
	    // help text
	    _("<p>If you made a mistake and want to undo the change, press <b>Clear</b> or <b>Clear all</b>.</p>")
	    ;
    }
    /**
     * Add service by hand.
     * @return new service name (already added to RunlevelEd::services) or ""
     */
    define string addService () ``{
	UI::OpenDialog (`VBox (
			    // dialog heading
		    `Heading (`opt (`hstretch), _("Add service")),
		    `VSpacing (1),
		    // text entry
		    `TextEntry (`id (`name), _("Service &name")),
		    // label
		    `Label (`opt (`hstretch), _("Starts in these runlevels by default:")),
		    getRlCheckBoxes (),
		    `VSpacing (1),
		    // text entry
		    `TextEntry (`id (`des), _("&Description (optional)"), ""),
		    `VSpacing (1),
		    `HBox (`PushButton (`id (`ok), `opt (`default, `key_F10),
					Label::OKButton ()),
			   `PushButton (`id (`cancel), `opt (`key_F9),
					Label::CancelButton ()))
		    ));
	UI::SetFocus (`id (`name));

	any ret = nil; // (symbol|string)
	string name = "";
	while (true)
	{
	    ret = UI::UserInput ();
	    if (`ok == ret)
	    {
		name = (string) UI::QueryWidget (`id (`name), `Value);
		if (nil == name || "" == name || haskey (RunlevelEd::services, name))
		{
		    // message popup
		    Popup::Message (_("Invalid service name. You did not specify service
name or the name specified is already in use."));
		    continue;
		}
		list def = [];
		foreach (string i, RunlevelEd::runlevels, ``{
		    if ((boolean) UI::QueryWidget (`id (i), `Value))
		    {
			def[size(def)] = i;
		    }
		});
		map m = $[
		    "dirty" : true,
		    "defstart" : def,
		    "start" : def,
		    "description" : UI::QueryWidget (`id (`des), `Value)
		    ];
		RunlevelEd::services[name] = m;
		break;
	    }
	    if (`cancel == ret)
	    {
		name = "";
		break;
	    }
	}
	UI::CloseDialog ();
	return name;
    }

    /**
     * Main dialog for changing services.
     * @return symbol for wizard sequencer
     */
    define symbol AutoDialog () ``{
	Wizard::SetScreenShotName ("runlevel-2-auto");
	// currently selected service we are working with
	map service = $[];

	/**
	 * Sets columns 0-S (runlevels) in table so they are synchronized with checkboxes.
	 */
	define void refreshTableLine2 () ``{
	    updateRlColumns (current_service, service, nil);
	    UI::ChangeWidget (`id (`table), `Item (current_service, c_dirty),
			      service["dirty"]:false ? UI::Glyph(`CheckMark) : " ");
	}

	// headers in table
	term header = `header (
	    // table header
	    _("Service"),
	    // table header. has the service state changed?
	    _("Changed"));
	foreach (string i, RunlevelEd::runlevels, ``{
//	    header = add (header, `Center (" " + i + " "));
	    header = add (header, `Center (i));
	});
	// headers in table
	header = add (header, _("Description"));
	term contents = `VBox (
	    `VSpacing (0.4),
	    // combo box label
	    `ComboBox (`id (`default_rl), `opt (`hstretch), _("&Set default runlevel after booting to:"), getDefaultPicker (`auto)),

	    `VSpacing (0.4),
	    `Table (`id (`table), `opt (`notify, `immediate),
		    header,
		    servicesToTable (`auto)
		),
	    `VBox (
		// label above checkboxed
		`Label (`id (`service_label), `opt (`hstretch), _("Service will be started in following runlevels:")),
		getRlCheckBoxes ()
		),
	    `HBox (
		// button label
		`PushButton (`id (`add), `opt (`key_F3), _("A&dd")),
		`HStretch (),
		// button label
		`PushButton (`id (`clear), _("&Clear")),
		// button label
		`PushButton (`id (`clear_all), _("Clea&r All")),
		// button label
		`PushButton (`id (`default), _("D&efault"))
		)
	    );
	// dialog caption.
	Wizard::SetContentsButtons (_("System Services (Runlevel): Details"), contents, getHelpAuto (), Label::BackButton(), Label::FinishButton());

	UI::ChangeWidget (`id (`table), `CurrentItem, current_service);
	service = RunlevelEd::services[current_service]:$[];
	updateRlCheckBoxes (service);
	any ret = nil;
	while (nil != UI::PollInput ()) {sleep(50);}
	while (`next != ret && `back != ret && `abort != ret)
	    {
		// Kludge, because a `Table still does not have a shortcut.
		// #16116
		UI::SetFocus (`id (`table));

		ret = UI::UserInput ();
		if (ret == `cancel)
		{
		    ret = `abort;
		}

		if (`abort == ret)
		{
		    if (!reallyAbort ())
		    {
			ret = nil;
			continue;
		    }
		}
		else if (`next == ret)
		{
		    //FIXME dependencies none or proper

		    string nfs_adj = RunlevelEd::CheckPortmap ();
		    if (nil != nfs_adj)
		    {
			UI::ChangeWidget (`id (`table), `CurrentItem, "portmap");
			current_service = "portmap";
			service = RunlevelEd::services["portmap"]:$[];
			updateRlCheckBoxes (service);
			while (nil != UI::PollInput ()) {sleep(50);}
			// yes-no popup
			if (!Popup::YesNo (sformat (_("Service portmap, which is required by
%1, is disabled. Enable
portmap if you want to run %1.

Leave portmap
disabled?\n"), nfs_adj)))
			{
			    ret = nil;
			    continue;
			}
		    }
		    RunlevelEd::default = (string) UI::QueryWidget (`id (`default_rl), `Value);
		    break;
		}
		else if (`add == ret)
		{
		    string name = addService ();
		    if ("" != name)
		    {
			UI::ChangeWidget (`id (`table), `Items, servicesToTable (`auto));
			UI::ChangeWidget (`id (`table), `CurrentItem, name);
			// qt and curses behave differently:
			// one of them sends notification after changewidget
			// and the other does not.
			// So eat it.
			while (nil != UI::PollInput ()) {sleep(50);}
			ret = `table;
		    }
		}
		else if (`default == ret)
		{
		    setServiceToDefault (current_service);
		    service = RunlevelEd::services[current_service]:$[];
		    refreshTableLine2 ();
		    ret = `table;
		}
		else if (`clear == ret)
		{
		    // re-read from SCR
		    service = Service::Info (current_service);
		    RunlevelEd::services[current_service] = service;
		    refreshTableLine2 ();
		    ret = `table;
		}
		else if (`clear_all == ret)
		{
		    RunlevelEd::ClearServices ();
		    UI::ChangeWidget (`id (`table), `Items, servicesToTable (`auto));
		    ret = `table;
		}
		else if (nil != ret && is (ret, string))
		{
		    // checkbox pressed
		    // checked or unchecked?
		    string checked = (((boolean) UI::QueryWidget (`id (ret), `Value)) ? (string) ret : " ");
		    service = queryRlCheckBoxes (current_service, service);
		    UI::ChangeWidget (`id (`table), `Item (current_service, runlevel2tableindex[ret]:-1), checked);
		    UI::ChangeWidget (`id (`table), `Item (current_service, c_dirty), UI::Glyph(`CheckMark));
		}

		// not a part of the else-if chain above!
		if (`table == ret)
		{
		    current_service = (string) UI::QueryWidget (`id (`table), `CurrentItem);
		    service = RunlevelEd::services[current_service]:$[];
		    updateRlCheckBoxes (service);
		    while (nil != UI::PollInput ()) {sleep(50);}
		}
	    }
	Wizard::RestoreScreenShotName ();
	return (symbol) ret;
    }

    // generic ui helpers
    /**
     * Like Popup::LongText
     * @param headline	a headline
     * @param richtext	`RichText(_("&lt;p>foo...&lt;/p>"))
     * @param hdim	popup width
     * @param vdim	popup height
     * @return continue?
     */
    define boolean LongContinueCancelHeadlinePopup (
	string headline, term richtext, integer hdim, integer vdim) ``{

	UI::OpenDialog (
	    `opt (`decorated),
	    `HBox (
		`VSpacing (vdim),
		`VBox (
		    `HSpacing (hdim),
		    `Left (`Heading (headline)),
		    `VSpacing (0.2),
		    richtext,	// scrolled text
		    `HBox (
			`PushButton (
			    `id (`continue),
			    `opt (`default, `key_F10),
			    Label::ContinueButton ()),
			`PushButton (
			    `id (`cancel),
			    `opt (`key_F9),
			    Label::CancelButton ())
			)
		    )
		)
	    );

	UI::SetFocus (`id (`continue));

	boolean ret = UI::UserInput() == `continue;
	UI::CloseDialog();
	return ret;
    }

    // move them to the module
    // (no ui interaction)
    /**
     * Disable the service. Changes global services.
     * @param service_name name of the service.
     */
    define void setServiceDisable (string service_name) ``{
	map service = RunlevelEd::services[service_name]:$[];
	RunlevelEd::services[service_name] = union (service,
			       $[
				   "start": [],
				   "dirty": true,
				   ]);
    }
    /**
     * DUH, in fact ENABLES  the service.
     * but the described function will be there sometime
     *
     * Set service to its default state upon installation.
     * Changes global services.
     * @param service_name Name of service to process.
     */
    define void setServiceToDefault (string service_name) ``{
	map service = RunlevelEd::services[service_name]:$[];
	RunlevelEd::services[service_name] = union (service,
			       $[
				   "start": service["defstart"]:[],
				   "dirty": true,
				   ]);
    }

    // favorite missing builtins
    /**
     * Converts a list to a map with values of true
     * @param l a list
     * @return a map
     */
    define map<string, boolean> tomap_true (list<string> l) ``{
	return (map<string, boolean>) listmap (string i, l, ``( $[i: true] ));
    }

    /**
     * @param m a map
     * @return keys of the map
     */
    define list mapkeys (map m) ``{
	return maplist (any k, any v, m, ``( k ));
    }


}
