#include <glib.h>
#include <stdlib.h>
#include <math.h>
#include "config.h"
#ifdef HAVE_API_WIN32_BASE
#include <windows.h>
#endif
#include "color.h"
#include "coord.h"
#include "point.h"
#include "callback.h"
#include "graphics.h"
#include "debug.h"
#include "navit.h"
#include "navit_nls.h"
#include "command.h"
#include "attr.h"
#include "xmlconfig.h"
#include "event.h"
#include "config_.h"
#include "map.h"
#include "mapset.h"
#include "transform.h"
#include "search.h"
#include "route.h"
#include "vehicle.h"
#include "vehicleprofile.h"
#include "layout.h"
#include "util.h"
#include "gui_internal.h"
#include "gui_internal_widget.h"
#include "gui_internal_priv.h"
#include "gui_internal_html.h"
#include "gui_internal_menu.h"
#include "gui_internal_keyboard.h"
#include "gui_internal_search.h"
#include "gui_internal_poi.h"
#include "gui_internal_command.h"
#if HAS_IFADDRS
#include <ifaddrs.h>
#include <arpa/inet.h>
#endif

static int gui_internal_cmd_escape(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct attr escaped;
    if (!in || !in[0]) {
        dbg(lvl_error,"first parameter missing or wrong type");
        return 0;
    }
    if (!out) {
        dbg(lvl_error,"output missing");
        return 0;
    }
    if (ATTR_IS_STRING(in[0]->type)) {
        escaped.type=in[0]->type;
        escaped.u.str=str_escape(escape_mode_string,in[0]->u.str);
    } else if (ATTR_IS_INT(in[0]->type)) {
        escaped.type=attr_type_string_begin;
        escaped.u.str=g_strdup_printf("%ld",in[0]->u.num);
    } else {
        dbg(lvl_error,"first parameter wrong type");
        return 0;
    }
    dbg(lvl_debug,"in %s result %s",in[0]->u.str,escaped.u.str);
    *out=attr_generic_add_attr(*out, attr_dup(&escaped));
    g_free(escaped.u.str);
    return 0;
}

static int gui_internal_cmd2_about(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct widget *menu,*wb,*w;
    char *text;

    graphics_draw_mode(this->gra, draw_mode_begin);
    menu=gui_internal_menu(this, _("About Navit"));
    menu->spx=this->spacing*10;
    wb=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand);
    gui_internal_widget_append(menu, wb);

    //Icon
    gui_internal_widget_append(wb, w=gui_internal_image_new(this, image_new_xs(this, "navit")));
    w->flags=gravity_top_center|orientation_horizontal|flags_fill;

    //app name
    text=g_strdup_printf("%s",PACKAGE_NAME);
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_top_center|orientation_horizontal|flags_expand;
    g_free(text);

    //Version
    text=g_strdup_printf("%s",NAVIT_VERSION);
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_top_center|orientation_horizontal|flags_expand;
    g_free(text);

    //Site
    text=g_strdup_printf("http://www.navit-project.org/");
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_top_center|orientation_horizontal|flags_expand;
    g_free(text);

    //Authors
    text=g_strdup_printf("%s:",_("By"));
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);
    text=g_strdup_printf("Martin Schaller");
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);
    text=g_strdup_printf("Michael Farmbauer");
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);
    text=g_strdup_printf("Alexander Atanasov");
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);
    text=g_strdup_printf("Pierre Grandin");
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);

    //Contributors
    text=g_strdup_printf("%s",_("And all the Navit Team"));
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);
    text=g_strdup_printf("%s",_("members and contributors."));
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
    g_free(text);

    gui_internal_menu_render(this);
    graphics_draw_mode(this->gra, draw_mode_end);
    return 0;
}

static int gui_internal_cmd2_waypoints(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    gui_internal_select_waypoint(this, _("Waypoints"), NULL, NULL, gui_internal_cmd_position, (void*)2);
    return 0;
}

static int gui_internal_cmd_enter_coord(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct widget *w, *wb, *wk, *wr, *we, *wnext, *row;
    wb=gui_internal_menu(this, _("Enter Coordinates"));
    w=gui_internal_box_new(this, gravity_center|orientation_vertical|flags_expand|flags_fill);
    gui_internal_widget_append(wb, w);
    wr=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    gui_internal_widget_append(w, wr);
    we=gui_internal_box_new(this, gravity_left_center|orientation_horizontal|flags_fill);
    gui_internal_widget_append(wr, we);

    /*
            w=gui_internal_box_new(this, gravity_left_top|orientation_vertical|flags_expand|flags_fill);
            gui_internal_widget_append(wb, w);

            we=gui_internal_box_new(this, gravity_left_center|orientation_horizontal|flags_fill);
            gui_internal_widget_append(w, we);*/
    gui_internal_widget_append(we, wk=gui_internal_label_new(this, _("Latitude Longitude")));
    wk->state |= STATE_EDIT|STATE_EDITABLE|STATE_CLEAR;
    wk->background=this->background;
    wk->flags |= flags_expand|flags_fill;
    wk->func = gui_internal_call_linked_on_finish;
    gui_internal_widget_append(we, wnext=gui_internal_image_new(this, image_new_xs(this, "gui_active")));
    wnext->state |= STATE_SENSITIVE;
    wnext->func = gui_internal_cmd_enter_coord_clicked;
    wnext->data=wk;
    wk->data=wnext;
    row=gui_internal_text_new(this, _("Enter coordinates, for example:"),
                              gravity_top_center|flags_fill|orientation_vertical);
    gui_internal_widget_append(wr,row);
    row=gui_internal_text_new(this, "52.5219N 19.4127E", gravity_top_center|flags_fill|orientation_vertical);
    gui_internal_widget_append(wr,row);
    row=gui_internal_text_new(this, "52°31.3167N 19°24.7667E", gravity_top_center|flags_fill|orientation_vertical);
    gui_internal_widget_append(wr,row);
    row=gui_internal_text_new(this, "52°31'19N 19°24'46E", gravity_top_center|flags_fill|orientation_vertical);
    gui_internal_widget_append(wr,row);

    if (this->keyboard)
        gui_internal_widget_append(w, gui_internal_keyboard(this, VKBD_DEGREE));
    else
        gui_internal_keyboard_show_native(this, w, VKBD_DEGREE, NULL);
    gui_internal_menu_render(this);
    return 0;
}

static int gui_internal_cmd2_town(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    if (this->sl)
        search_list_select(this->sl, attr_country_all, 0, 0);
    gui_internal_search(this,_("Town"),"Town",1);
    return 0;
}

static int gui_internal_cmd2_setting_vehicle(struct gui_priv *this, char *function, struct attr **in,
        struct attr ***out) {
    struct attr attr,attr2,vattr;
    struct widget *w,*wb,*wl;
    struct attr_iter *iter;
    struct attr active_vehicle;

    iter=navit_attr_iter_new(NULL);
    if (navit_get_attr(this->nav, attr_vehicle, &attr, iter) && !navit_get_attr(this->nav, attr_vehicle, &attr2, iter)) {
        vehicle_get_attr(attr.u.vehicle, attr_name, &vattr, NULL);
        navit_attr_iter_destroy(iter);
        gui_internal_menu_vehicle_settings(this, attr.u.vehicle, vattr.u.str);
        return 0;
    }
    navit_attr_iter_destroy(iter);

    wb=gui_internal_menu(this, _("Vehicle"));
    w=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    w->spy=this->spacing*3;
    gui_internal_widget_append(wb, w);
    if (!navit_get_attr(this->nav, attr_vehicle, &active_vehicle, NULL))
        active_vehicle.u.vehicle=NULL;
    iter=navit_attr_iter_new(NULL);
    while(navit_get_attr(this->nav, attr_vehicle, &attr, iter)) {
        vehicle_get_attr(attr.u.vehicle, attr_name, &vattr, NULL);
        wl=gui_internal_button_new_with_callback(this, vattr.u.str,
                image_new_xs(this, attr.u.vehicle == active_vehicle.u.vehicle ? "gui_active" : "gui_inactive"),
                gravity_left_center|orientation_horizontal|flags_fill,
                gui_internal_cmd_vehicle_settings, attr.u.vehicle);
        wl->text=g_strdup(vattr.u.str);
        gui_internal_widget_append(w, wl);
    }
    navit_attr_iter_destroy(iter);
    gui_internal_menu_render(this);
    return 0;
}

static int gui_internal_cmd2_setting_rules(struct gui_priv *this, char *function, struct attr **in,
        struct attr ***out) {
    struct widget *wb,*w;
    struct attr on,off;
    wb=gui_internal_menu(this, _("Rules"));
    w=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    w->spy=this->spacing*3;
    gui_internal_widget_append(wb, w);
    on.u.num=1;
    off.u.num=0;
    on.type=off.type=attr_tracking;
    gui_internal_widget_append(w,
                               gui_internal_button_navit_attr_new(this, _("Lock on road"), gravity_left_center|orientation_horizontal|flags_fill,
                                       &on, &off));
    on.u.num=0;
    off.u.num=-1;
    on.type=off.type=attr_orientation;
    gui_internal_widget_append(w,
                               gui_internal_button_navit_attr_new(this, _("Northing"), gravity_left_center|orientation_horizontal|flags_fill,
                                       &on, &off));
    on.u.num=1;
    off.u.num=0;
    on.type=off.type=attr_follow_cursor;
    gui_internal_widget_append(w,
                               gui_internal_button_navit_attr_new(this, _("Map follows Vehicle"),
                                       gravity_left_center|orientation_horizontal|flags_fill,
                                       &on, &off));
    on.u.num=1;
    off.u.num=0;
    on.type=off.type=attr_waypoints_flag;
    gui_internal_widget_append(w,
                               gui_internal_button_navit_attr_new(this, _("Plan with Waypoints"),
                                       gravity_left_center|orientation_horizontal|flags_fill,
                                       &on, &off));
    gui_internal_menu_render(this);
    return 0;
}

static int gui_internal_cmd2_setting_maps(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct attr attr, on, off, description, type, data, url, active;
    struct widget *w,*wb,*row,*wma;
    char *label;
    struct attr_iter *iter;

    wb=gui_internal_menu(this, _("Maps"));
    //w=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    //w->spy=this->spacing*3;
    w = gui_internal_widget_table_new(this,gravity_left_top | flags_fill | flags_expand |orientation_vertical,1);
    gui_internal_widget_append(wb, w);
    iter=navit_attr_iter_new(NULL);
    on.type=off.type=attr_active;
    on.u.num=1;
    off.u.num=0;
    while(navit_get_attr(this->nav, attr_map, &attr, iter)) {
        if (map_get_attr(attr.u.map, attr_description, &description, NULL)) {
            label=g_strdup(description.u.str);
        } else {
            if (!map_get_attr(attr.u.map, attr_type, &type, NULL))
                type.u.str="";
            if (!map_get_attr(attr.u.map, attr_data, &data, NULL))
                data.u.str="";
            label=g_strdup_printf("%s:%s", type.u.str, data.u.str);
        }
        if (map_get_attr(attr.u.map, attr_url, &url, NULL)) {
            if (!map_get_attr(attr.u.map, attr_active, &active, NULL))
                active.u.num=1;
            wma=gui_internal_button_new_with_callback(this, label, image_new_xs(this, active.u.num ? "gui_active" : "gui_inactive"),
                    gravity_left_center|orientation_horizontal|flags_fill,
                    gui_internal_cmd_map_download, attr.u.map);
        } else {
            wma=gui_internal_button_map_attr_new(this, label, gravity_left_center|orientation_horizontal|flags_fill,
                                                 attr.u.map, &on, &off, 1);
        }
        gui_internal_widget_append(w, row=gui_internal_widget_table_row_new(this,
                                          gravity_left|orientation_horizontal|flags_fill));
        gui_internal_widget_append(row, wma);
        g_free(label);
    }
    navit_attr_iter_destroy(iter);
    gui_internal_menu_render(this);
    return 0;
}

static int gui_internal_cmd2_setting_layout(struct gui_priv *this, char *function, struct attr **in,
        struct attr ***out) {
    struct attr attr;
    struct widget *w,*wb,*wl,*row;
    struct attr_iter *iter;


    wb=gui_internal_menu(this, _("Layout"));
    w=gui_internal_widget_table_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill,1);
    gui_internal_widget_append(wb, w);
    iter=navit_attr_iter_new(NULL);
    while(navit_get_attr(this->nav, attr_layout, &attr, iter)) {
        gui_internal_widget_append(w, row=gui_internal_widget_table_row_new(this,
                                          gravity_left|orientation_horizontal|flags_fill));
        wl=gui_internal_button_navit_attr_new(this, attr.u.layout->name, gravity_left_center|orientation_horizontal|flags_fill,
                                              &attr, NULL);
        gui_internal_widget_append(row, wl);
    }
    navit_attr_iter_destroy(iter);
    gui_internal_menu_render(this);
    return 0;
}

/*
 * @brief Displays Route Height Profile
 *
 * displays a heightprofile if a route is active and
 * some heightinfo is provided by means of a map
 *
 * the name of the file providing the heightlines must
 * comply with *.heightlines.bin
 *
 */
static int gui_internal_cmd2_route_height_profile(struct gui_priv *this, char *function, struct attr **in,
        struct attr ***out) {
    struct widget * menu, *box;
    struct map * map=NULL;
    struct map_rect * mr=NULL;
    struct route * route;
    struct item * item =NULL;
    struct mapset *ms;
    struct mapset_handle *msh;
    int x,i,first=1,dist=0;
    int diagram_points_count = 0;
    struct coord c,last,res;
    struct coord_rect rbbox,dbbox;
    struct map_selection sel;
    struct heightline *heightline,*heightlines=NULL;
    struct diagram_point *min,*diagram_point,*diagram_points=NULL;
    struct point p[2];
    int min_ele=INT_MAX;
    int max_ele=INT_MIN;
    int distance=0;
    sel.next=NULL;
    sel.order=18;
    sel.range.min=type_height_line_1;
    sel.range.max=type_height_line_3;

    menu=gui_internal_menu(this,_("Height Profile"));
    box = gui_internal_box_new(this, gravity_left_top| orientation_vertical | flags_fill | flags_expand);
    gui_internal_widget_append(menu, box);
    route = navit_get_route(this->nav);
    if (route)
        map = route_get_map(route);
    if(map)
        mr = map_rect_new(map,NULL);
    if(mr) {
        while((item = map_rect_get_item(mr))) {
            while (item_coord_get(item, &c, 1)) {
                if (first) {
                    first=0;
                    sel.u.c_rect.lu=c;
                    sel.u.c_rect.rl=c;
                } else
                    coord_rect_extend(&sel.u.c_rect, &c);
            }
        }
        map_rect_destroy(mr);
        mr = NULL;
        ms=navit_get_mapset(this->nav);
        if (!first && ms) {
            int heightmap_installed = FALSE;
            msh=mapset_open(ms);
            while ((map=mapset_next(msh, 1))) {
                struct attr data_attr;
                if (map_get_attr(map, attr_data, &data_attr, NULL)) {
                    dbg(lvl_debug,"map name = %s",data_attr.u.str);
                    if (strstr(data_attr.u.str,".heightlines.bin")) {
                        dbg(lvl_info,"reading heightlines from map %s",data_attr.u.str);
                        mr=map_rect_new(map, &sel);
                        heightmap_installed = TRUE;
                    } else {
                        dbg(lvl_debug,"ignoring map %s",data_attr.u.str);
                    }
                }
                if (mr) {
                    while((item = map_rect_get_item(mr))) {
                        if (item->type >= sel.range.min && item->type <= sel.range.max) {
                            heightline=item_get_heightline(item);
                            if (heightline) {
                                heightline->next=heightlines;
                                heightlines=heightline;
                            }
                        }
                    }
                    map_rect_destroy(mr);
                    mr = NULL;
                }
            }
            mapset_close(msh);
            if (!heightmap_installed) {
                char *text;
                struct widget *w;
                text=g_strdup_printf("%s",_("please install a map *.heightlines.bin to provide elevationdata"));
                gui_internal_widget_append(box, w=gui_internal_label_new(this, text));
                w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
                g_free(text);
                gui_internal_menu_render(this);
                return 0;
            }
        }
    }
    map=NULL;
    mr=NULL;
    if (route)
        map = route_get_map(route);
    if(map)
        mr = map_rect_new(map,NULL);
    if(mr && heightlines) {
        while((item = map_rect_get_item(mr))) {
            first=1;
            while (item_coord_get(item, &c, 1)) {
                if (first)
                    first=0;
                else {
                    heightline=heightlines;
                    rbbox.lu=last;
                    rbbox.rl=last;
                    coord_rect_extend(&rbbox, &c);
                    while (heightline) {
                        if (coord_rect_overlap(&rbbox, &heightline->bbox)) {
                            for (i = 0 ; i < heightline->count - 1; i++) {
                                if (heightline->c[i].x != heightline->c[i+1].x || heightline->c[i].y != heightline->c[i+1].y) {
                                    if (line_intersection(heightline->c+i, heightline->c+i+1, &last, &c, &res)) {
                                        diagram_point=g_new(struct diagram_point, 1);
                                        diagram_point->c.x=dist+transform_distance(projection_mg, &last, &res);
                                        diagram_point->c.y=heightline->height;
                                        diagram_point->next=diagram_points;
                                        diagram_points=diagram_point;
                                        diagram_points_count ++;
                                        dbg(lvl_debug,"%d %d", diagram_point->c.x, diagram_point->c.y);
                                        max_ele=MAX(max_ele, diagram_point->c.y);
                                        min_ele=MIN(min_ele, diagram_point->c.y);
                                        distance=diagram_point->c.x;
                                    }
                                }
                            }
                        }
                        heightline=heightline->next;
                    }
                    dist+=transform_distance(projection_mg, &last, &c);
                }
                last=c;
            }
        }
    }
    while (heightlines) {
        heightline=heightlines;
        heightlines=heightlines->next;
        g_free(heightline);
    }
    if(mr)
        map_rect_destroy(mr);

    if(diagram_points_count < 2) {
        char *text;
        struct widget *w;
        text=g_strdup_printf("%s",_("The route must cross at least 2 heightlines"));
        gui_internal_widget_append(box, w=gui_internal_label_new(this, text));
        w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
        g_free(text);
        gui_internal_menu_render(this);
        if(diagram_points)
            g_free(diagram_points);
        return 0;
    }

    gui_internal_menu_render(this);
    first=1;
    diagram_point=diagram_points;
    while (diagram_point) {
        if (first) {
            dbbox.lu=diagram_point->c;
            dbbox.rl=diagram_point->c;
            first=0;
        } else
            coord_rect_extend(&dbbox, &diagram_point->c);
        diagram_point=diagram_point->next;
    }
    dbg(lvl_debug,"%d %d %d %d", dbbox.lu.x, dbbox.lu.y, dbbox.rl.x, dbbox.rl.y);
    if (dbbox.rl.x > dbbox.lu.x && dbbox.lu.x*100/(dbbox.rl.x-dbbox.lu.x) <= 25)
        dbbox.lu.x=0;
    if (dbbox.lu.y > dbbox.rl.y && dbbox.rl.y*100/(dbbox.lu.y-dbbox.rl.y) <= 25)
        dbbox.rl.y=0;
    dbg(lvl_debug,"%d,%d %dx%d", box->p.x, box->p.y, box->w, box->h);
    x=dbbox.lu.x;
    first=1;
    if (diagram_points_count > 1 && dbbox.rl.x != dbbox.lu.x && dbbox.lu.y != dbbox.rl.y) {
        for (;;) {
            min=NULL;
            diagram_point=diagram_points;
            while (diagram_point) {
                if (diagram_point->c.x >= x && (!min || min->c.x > diagram_point->c.x))
                    min=diagram_point;
                diagram_point=diagram_point->next;
            }
            if (! min)
                break;
            p[1].x=(min->c.x-dbbox.lu.x)*(box->w-10)/(dbbox.rl.x-dbbox.lu.x)+box->p.x+5;
            p[1].y=(box->h)-5-(min->c.y-dbbox.rl.y)*(box->h-10)/(dbbox.lu.y-dbbox.rl.y)+box->p.y;
            dbg(lvl_debug,"%d,%d=%d,%d",min->c.x, min->c.y, p[1].x,p[1].y);
            graphics_draw_circle(this->gra, this->foreground, &p[1], 2);
            if (first)
                first=0;
            else
                graphics_draw_lines(this->gra, this->foreground, p, 2);
            p[0]=p[1];
            x=min->c.x+1;
        }
    }

    struct point pTopLeft= {0, box->p.y + 10};
    struct point pBottomLeft= {0, box->h + box->p.y - 2};
    struct point pBottomRight= {box->w - 100, box->h + box->p.y - 2};
    char* minele_text=g_strdup_printf("%d m", min_ele);
    char* maxele_text=g_strdup_printf("%d m", max_ele);
    char* distance_text=g_strdup_printf("%.3f km", distance/1000.0);
    graphics_draw_text_std(this->gra, 10, maxele_text, &pTopLeft);
    graphics_draw_text_std(this->gra, 10, minele_text, &pBottomLeft);
    graphics_draw_text_std(this->gra, 10, distance_text, &pBottomRight);

    while (diagram_points) {
        diagram_point=diagram_points;
        diagram_points=diagram_points->next;
        g_free(diagram_point);
    }
    return 0;
}

static int gui_internal_cmd2_route_description(struct gui_priv *this, char *function, struct attr **in,
        struct attr ***out) {


    struct widget * menu;
    struct widget * box;


    if(! this->vehicle_cb) {
        /**
         * Register the callback on vehicle updates.
         */
        this->vehicle_cb = callback_new_attr_1(callback_cast(gui_internal_route_update),
                                               attr_position_coord_geo,this);
        navit_add_callback(this->nav,this->vehicle_cb);
    }

    this->route_data.route_table = gui_internal_widget_table_new(this,
                                   gravity_left_top | flags_fill | flags_expand |orientation_vertical,1);

    menu=gui_internal_menu(this,_("Route Description"));

    menu->wfree=gui_internal_route_screen_free;
    this->route_data.route_showing=1;
    this->route_data.route_table->spx = this->spacing;


    box = gui_internal_box_new(this, gravity_left_top| orientation_vertical | flags_fill | flags_expand);

    gui_internal_widget_append(box,this->route_data.route_table);
    box->w=menu->w;
    box->spx = this->spacing;
    this->route_data.route_table->w=box->w;
    gui_internal_widget_append(menu,box);
    gui_internal_populate_route_table(this,this->nav);
    gui_internal_menu_render(this);
    return 0;
}

static int gui_internal_cmd2_pois(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct widget *w;
    struct poi_param *param;
    struct attr pro;
    struct coord c;

    dbg(lvl_debug,"enter");
    if (!in || !in[0])
        return 0;
    if (!ATTR_IS_COORD_GEO(in[0]->type))
        return 0;
    if (!navit_get_attr(this->nav, attr_projection, &pro, NULL))
        return 0;
    w=g_new0(struct widget,1);
    param=g_new0(struct poi_param,1);
    if (in[1] && ATTR_IS_STRING(in[1]->type)) {
        gui_internal_poi_param_set_filter(param, in[1]->u.str);
        if (in[2] && ATTR_IS_INT(in[2]->type))
            param->AddressFilterType=in[2]->u.num;
    }

    transform_from_geo(pro.u.projection,in[0]->u.coord_geo,&c);
    w->c.x=c.x;
    w->c.y=c.y;
    w->c.pro=pro.u.projection;
    gui_internal_cmd_pois(this, w, param);
    g_free(w);
    gui_internal_poi_param_free(param);
    return 0;
}

static int gui_internal_cmd2_locale(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct widget *menu,*wb,*w;
    char *text;

    graphics_draw_mode(this->gra, draw_mode_begin);
    menu=gui_internal_menu(this, _("Show Locale"));
    menu->spx=this->spacing*10;
    wb=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    gui_internal_widget_append(menu, wb);
    text=g_strdup_printf("LANG=%1$s (1=%3$s 2=%2$s)",getenv("LANG"),"2","1");
    gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
    w->flags=gravity_left_center|orientation_horizontal|flags_fill;
    g_free(text);
#ifdef HAVE_API_WIN32_BASE
    {
        char country[32],lang[32];
#ifdef HAVE_API_WIN32_CE
        wchar_t wcountry[32],wlang[32];

        GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, wlang, sizeof(wlang));
        WideCharToMultiByte(CP_ACP,0,wlang,-1,lang,sizeof(lang),NULL,NULL);
#else
        GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME, lang, sizeof(lang));
#endif
        text=g_strdup_printf("LOCALE_SABBREVLANGNAME=%s",lang);
        gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
        w->flags=gravity_left_center|orientation_horizontal|flags_fill;
        g_free(text);
#ifdef HAVE_API_WIN32_CE
        GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVCTRYNAME, wcountry, sizeof(wcountry));
        WideCharToMultiByte(CP_ACP,0,wcountry,-1,country,sizeof(country),NULL,NULL);
#else
        GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVCTRYNAME, country, sizeof(country));
#endif
        text=g_strdup_printf("LOCALE_SABBREVCTRYNAME=%s",country);
        gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
        w->flags=gravity_left_center|orientation_horizontal|flags_fill;
        g_free(text);
    }
#endif

    gui_internal_menu_render(this);
    graphics_draw_mode(this->gra, draw_mode_end);
    return 0;
}

/**
 * @brief display basic networking information
 *
 * @return nothing
 *
 * This function displays basic networking information, currently
 * only the interface name and the associated IP address(es).
 * Currently only works on non Windows systems.
 *
 */
static int gui_internal_cmd2_network_info(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
#if HAS_IFADDRS
    struct widget *menu,*wb,*w;
    char *text;

    graphics_draw_mode(this->gra, draw_mode_begin);
    menu=gui_internal_menu(this, _("Network info"));
    menu->spx=this->spacing*10;
    wb=gui_internal_box_new(this, gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    gui_internal_widget_append(menu, wb);

    struct ifaddrs *addrs, *tmp;
    getifaddrs(&addrs);
    tmp = addrs;

    while (tmp) {
        if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_INET) {
            struct sockaddr_in *pAddr = (struct sockaddr_in *)tmp->ifa_addr;
            if(g_ascii_strncasecmp(tmp->ifa_name,"lo",2 ) ) {
                text=g_strdup_printf("%s: %s", tmp->ifa_name, inet_ntoa(pAddr->sin_addr));
                gui_internal_widget_append(wb, w=gui_internal_label_new(this, text));
                w->flags=gravity_bottom_center|orientation_horizontal|flags_fill;
                g_free(text);
            }
        }
        tmp = tmp->ifa_next;
    }
    freeifaddrs(addrs);

    gui_internal_menu_render(this);
    graphics_draw_mode(this->gra, draw_mode_end);
#else
    dbg(lvl_error, "Cannot show network info: ifaddr.h not found");
#endif
    return 0;
}

static int gui_internal_cmd_formerdests(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct widget *wb,*w,*wbm,*tbl=NULL;
    struct map *formerdests;
    struct map_rect *mr_formerdests;
    struct item *item;
    struct attr attr;
    char *label_full;
    enum projection projection;

    if(!navit_get_attr(this->nav, attr_former_destination_map, &attr, NULL))
        return 0;

    formerdests=attr.u.map;
    if(!formerdests)
        return 0;

    mr_formerdests=map_rect_new(formerdests, NULL);
    if(!mr_formerdests)
        return 0;

    projection = map_projection(formerdests);

    gui_internal_prune_menu_count(this, 1, 0);
    wb=gui_internal_menu(this, _("Former Destinations"));
    wb->background=this->background;

    w=gui_internal_box_new(this,
                           gravity_top_center|orientation_vertical|flags_expand|flags_fill);
    w->spy=this->spacing*2;
    gui_internal_widget_append(wb, w);
    while ((item=map_rect_get_item(mr_formerdests))) {
        struct coord c;
        struct widget *row;
        if (item->type!=type_former_destination) continue;
        if (!item_attr_get(item, attr_label, &attr)) continue;
        if(!tbl) {
            tbl=gui_internal_widget_table_new(this,gravity_left_top | flags_fill | flags_expand | orientation_vertical,1);
            gui_internal_widget_append(w,tbl);
        }
        row=gui_internal_widget_table_row_new(this,gravity_left| flags_fill| orientation_vertical);
        gui_internal_widget_prepend(tbl, row);
        label_full=attr.u.str;
        wbm=gui_internal_button_new_with_callback(this, label_full,
                image_new_xs(this, "gui_active"),
                gravity_left_center|orientation_horizontal|flags_fill,
                gui_internal_cmd_position, NULL);
        gui_internal_widget_append(row,wbm);
        if (item_coord_get(item, &c, 1)) {
            wbm->c.x=c.x;
            wbm->c.y=c.y;
            wbm->c.pro=projection;
            wbm->name=g_strdup(label_full);
            wbm->text=g_strdup(label_full);
            wbm->data=(void*)8; //Mark us as a former destination
            wbm->prefix=g_strdup(label_full);
        }
    }
    if (!tbl) {
        wbm=gui_internal_text_new(this, _("- No former destinations available -"),
                                  gravity_left_center|orientation_horizontal|flags_fill);
        gui_internal_widget_append(w, wbm);
    }
    gui_internal_menu_render(this);
    map_rect_destroy(mr_formerdests);
    return 0;
}

static int gui_internal_cmd2_bookmarks(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    char *str=NULL;
    if (in && in[0] && ATTR_IS_STRING(in[0]->type)) {
        str=in[0]->u.str;
    }

    gui_internal_cmd_bookmarks(this, NULL, str);
    return 0;
}

static int gui_internal_cmd2_abort_navigation(struct gui_priv *this, char *function, struct attr **in,
        struct attr ***out) {
    navit_set_destination(this->nav, NULL, NULL, 0);
    return 0;
}

static int gui_internal_cmd2_back(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    graphics_draw_mode(this->gra, draw_mode_begin);
    gui_internal_back(this, NULL, NULL);
    graphics_draw_mode(this->gra, draw_mode_end);
    gui_internal_check_exit(this);
    return 0;
}

static int gui_internal_cmd2_back_to_map(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    gui_internal_prune_menu(this, NULL);
    gui_internal_check_exit(this);
    return 0;
}


static int gui_internal_get_data(struct gui_priv *priv, char *command, struct attr **in, struct attr ***out) {
    struct attr private_data = { attr_private_data, {(void *)&priv->data}};
    if (out)
        *out=attr_generic_add_attr(*out, &private_data);
    return 0;
}

static int gui_internal_cmd_log(struct gui_priv *this, char *command, struct attr **in, struct attr ***out) {
    struct widget *w,*wb,*wk,*wl,*we,*wnext;
    gui_internal_enter(this, 1);
    gui_internal_set_click_coord(this, NULL);
    gui_internal_enter_setup(this);
    wb=gui_internal_menu(this, "Log Message");
    w=gui_internal_box_new(this, gravity_left_top|orientation_vertical|flags_expand|flags_fill);
    gui_internal_widget_append(wb, w);
    we=gui_internal_box_new(this, gravity_left_center|orientation_horizontal|flags_fill);
    gui_internal_widget_append(w, we);
    gui_internal_widget_append(we, wk=gui_internal_label_new(this, _("Message")));
    wk->state |= STATE_EDIT|STATE_EDITABLE|STATE_CLEAR;
    wk->background=this->background;
    wk->flags |= flags_expand|flags_fill;
    wk->func = gui_internal_call_linked_on_finish;
    gui_internal_widget_append(we, wnext=gui_internal_image_new(this, image_new_xs(this, "gui_active")));
    wnext->state |= STATE_SENSITIVE;
    wnext->func = gui_internal_cmd_log_clicked;
    wnext->data=wk;
    wk->data=wnext;
    wl=gui_internal_box_new(this, gravity_left_top|orientation_vertical|flags_expand|flags_fill);
    gui_internal_widget_append(w, wl);
    if (this->keyboard)
        gui_internal_widget_append(w, gui_internal_keyboard(this,
                                   VKBD_FLAG_2 | gui_internal_keyboard_init_mode(getenv("LANG"))));
    else
        gui_internal_keyboard_show_native(this, w, VKBD_FLAG_2 | gui_internal_keyboard_init_mode(getenv("LANG")),
                                          getenv("LANG"));
    gui_internal_menu_render(this);
    gui_internal_leave(this);
    return 0;
}

static int gui_internal_cmd_menu2(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    char *href=NULL;
    int i=0, ignore=0, replace=0;

    if (in && in[i] && ATTR_IS_INT(in[i]->type))
        ignore=in[i++]->u.num;

    if (in && in[i] && ATTR_IS_STRING(in[i]->type)) {
        href=in[i++]->u.str;
        if (in[i] && ATTR_IS_INT(in[i]->type))
            replace=in[i++]->u.num;
    }

    if (this->root.children) {
        if (!href)
            return 0;
        gui_internal_html_load_href(this, href, replace);
        return 0;
    }
    gui_internal_cmd_menu(this, ignore, href);
    return 0;
}

static int gui_internal_cmd2_position(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    const char *name=_("Position");
    int flags=-1;

    dbg(lvl_debug,"enter");
    if (!in || !in[0])
        return 0;
    if (!ATTR_IS_COORD_GEO(in[0]->type))
        return 0;
    if (in[1] && ATTR_IS_STRING(in[1]->type)) {
        name=in[1]->u.str;
        if (in[2] && ATTR_IS_INT(in[2]->type))
            flags=in[2]->u.num;
    }
    dbg(lvl_debug,"flags=0x%x",flags);
    gui_internal_cmd_position_do(this, NULL, in[0]->u.coord_geo, NULL, name, flags);
    return 0;
}

static int gui_internal_cmd_redraw_map(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    this->redraw=1;
    return 0;
}

static int gui_internal_cmd2_refresh(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    char *href=g_strdup(this->href);
    gui_internal_html_load_href(this, href, 1);
    g_free(href);
    return 0;
}

static int gui_internal_cmd2_set(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    char *pattern,*command=NULL;
    if (!in || !in[0] || !ATTR_IS_STRING(in[0]->type)) {
        dbg(lvl_error,"first parameter missing or wrong type");
        return 0;
    }
    pattern=in[0]->u.str;
    dbg(lvl_debug,"pattern %s",pattern);
    if (in[1]) {
        command=gui_internal_cmd_match_expand(pattern, in+1);
        dbg(lvl_debug,"expand %s",command);
        gui_internal_set(pattern, command);
        command_evaluate(&this->self, command);
        g_free(command);
    } else {
        gui_internal_set(pattern, NULL);
    }
    return 0;
}

int gui_internal_cmd2_quit(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    struct attr navit;
    gui_internal_prune_menu(this, NULL);
    navit.type=attr_navit;
    navit.u.navit=this->nav;
    config_remove_attr(config, &navit);
    event_main_loop_quit();
    return 0;
}

static char *gui_internal_append_attr(char *str, enum escape_mode mode, char *pre, struct attr *attr, char *post) {
    char *astr=NULL;
    if (ATTR_IS_STRING(attr->type))
        astr=str_escape(mode, attr->u.str);
    else if (ATTR_IS_COORD_GEO(attr->type)) {
        char coord_str[32];
        coord_geo_format_short(attr->u.coord_geo, coord_str, sizeof(coord_str), "\n");
        astr=str_escape(mode, coord_str);
    } else if (ATTR_IS_INT(attr->type))
        astr=g_strdup_printf("%ld",attr->u.num);
    else
        astr=g_strdup_printf("Unsupported type %s",attr_to_name(attr->type));
    str=g_strconcat_printf(str,"%s%s%s",pre,astr,post);
    g_free(astr);
    return str;
}

static int gui_internal_cmd_write(struct gui_priv * this, char *function, struct attr **in, struct attr ***out) {
    char *str=NULL;
    dbg(lvl_debug,"enter %s %p %p",function,in,out);
    if (!in)
        return 0;
    while (*in) {
        str=gui_internal_append_attr(str, escape_mode_none, "", *in, "");
        in++;
    }
    if (str) {
        str=g_strdup_printf("<html>%s</html>\n",str);
#if 0
        dbg(lvl_debug,"%s",str);
#endif
        gui_internal_html_parse_text(this, str);
    }
    g_free(str);
    return 0;
}

static void gui_internal_onclick(struct attr ***in, char **onclick, char *set) {
    struct attr **i=*in;
    char *c,*str=NULL,*args=NULL,*sep="";

    if (!*i || !ATTR_IS_STRING((*i)->type) || !(*i)->u.str)
        goto error;
    str=g_strdup((*i)->u.str);
    i++;
    c=str;
    while (*c) {
        if (c[0] == '%' && c[1] == '{') {
            char format[4],*end=strchr(c+2,'}'),*replacement=NULL,*new_str;
            int is_arg;
            if (!end) {
                dbg(lvl_error,"Missing closing brace in format string %s",c);
                goto error;
            }
            if (end-c > sizeof(format)) {
                dbg(lvl_error,"Invalid format string %s",c);
                goto error;
            }
            strncpy(format, c+2, end-c-2);
            format[end-c-2]='\0';
            is_arg=end[1] == '*';
            c[0]='\0';
            if (!strcmp(format,"d")) {
                replacement=gui_internal_append_attr(NULL, escape_mode_string, "", *i++, "");
                if (is_arg) {
                    args=g_strconcat_printf(args, "%s%s", args ? "," : "", replacement);
                    g_free(replacement);
                    replacement=g_strdup("");
                }

            }
            if (!strcmp(format,"se")) {
                replacement=gui_internal_append_attr(NULL, escape_mode_string, "", *i++, "");
                if (is_arg) {
                    char *arg=str_escape(escape_mode_string, replacement);
                    args=g_strconcat_printf(args, "%s%s", args ? "," : "", arg);
                    g_free(replacement);
                    g_free(arg);
                    replacement=g_strdup("");
                }
            }
            if (!replacement) {
                dbg(lvl_error,"Unsupported format string %s",format);
                goto error;
            }
            new_str=g_strconcat(str, replacement, end+1, NULL);
            c=new_str+strlen(str)+strlen(replacement);
            g_free(str);
            g_free(replacement);
            str=new_str;
        }
        c++;
    }
    *in=i;
    if (*onclick && strlen(*onclick))
        sep=";";
    if (str && strlen(str)) {
        char *old=*onclick;
        if (set) {
            char *setstr=str_escape(escape_mode_string,str);
            char *argssep="";
            if (args && strlen(args))
                argssep=",";
            *onclick=g_strconcat(old,sep,set,"(",setstr,argssep,args,")",NULL);
        } else {
            *onclick=g_strconcat(old,sep,str,NULL);
        }
        g_free(old);
    }
error:
    g_free(str);
    g_free(args);
    return;
}

static int gui_internal_cmd_img(struct gui_priv * this, char *function, struct attr **in, struct attr ***out) {
    char *str=g_strdup("<img"),*suffix=NULL,*onclick=g_strdup(""),*html;

    if (ATTR_IS_STRING((*in)->type)) {
        if ((*in)->u.str && strlen((*in)->u.str))
            str=gui_internal_append_attr(str, escape_mode_string|escape_mode_html, " class=", *in, "");
        in++;
    } else {
        dbg(lvl_error,"argument error: class argument not string");
        goto error;
    }
    if (ATTR_IS_STRING((*in)->type) && (*in)->u.str) {
        if ((*in)->u.str && strlen((*in)->u.str)) {
            str=gui_internal_append_attr(str, escape_mode_string|escape_mode_html, " src=", *in, "");
        }
        in++;
    } else {
        dbg(lvl_error,"argument error: image argument not string");
        goto error;
    }
    if (ATTR_IS_STRING((*in)->type) && (*in)->u.str) {
        if ((*in)->u.str && strlen((*in)->u.str)) {
            suffix=gui_internal_append_attr(NULL, escape_mode_html, ">", *in, "</img>");
        } else {
            suffix=g_strdup("/>");
        }
        in++;
    } else {
        dbg(lvl_error,"argument error: text argument not string");
        goto error;
    }
    gui_internal_onclick(&in,&onclick,NULL);
    gui_internal_onclick(&in,&onclick,"set");
    gui_internal_onclick(&in,&onclick,NULL);
    if (strlen(onclick)) {
        char *tmp=str_escape(escape_mode_html_apos, onclick);
        str=g_strconcat_printf(str," onclick='%s'",tmp);
        g_free(tmp);
    }
    g_free(onclick);
    html=g_strdup_printf("<html>%s%s</html>\n",str,suffix);
    dbg(lvl_debug,"return %s",html);
    gui_internal_html_parse_text(this, html);
    g_free(html);
error:
    g_free(suffix);
    g_free(str);
    return 0;
}

static int gui_internal_cmd_debug(struct gui_priv * this, char *function, struct attr **in, struct attr ***out) {
    char *str;
    dbg(lvl_debug,"begin");
    if (in) {
        while (*in) {
            str=attr_to_text(*in, NULL, 0);
            dbg(lvl_debug,"%s:%s",attr_to_name((*in)->type),str);
            in++;
            g_free(str);
        }
    }
    dbg(lvl_debug,"done");
    return 0;
}

static int gui_internal_cmd2(struct gui_priv *this, char *function, struct attr **in, struct attr ***out) {
    int entering=0;
    int ignore=1;
    if (in && in[0] && ATTR_IS_INT(in[0]->type)) {
        ignore=in[0]->u.num;
        in++;
    }

    if(!this->root.children) {
        entering=1;
        gui_internal_apply_config(this);
        gui_internal_enter(this, ignore);
        gui_internal_enter_setup(this);
    }

    if(!strcmp(function, "bookmarks"))
        gui_internal_cmd2_bookmarks(this, function, in, out);
    else if(!strcmp(function, "formerdests"))
        gui_internal_cmd_formerdests(this, function, in, out);
    else if(!strcmp(function, "locale"))
        gui_internal_cmd2_locale(this, function, in, out);
    else if(!strcmp(function, "network_info"))
        gui_internal_cmd2_network_info(this, function, in, out);
    else if(!strcmp(function, "position"))
        gui_internal_cmd2_position(this, function, in, out);
    else if(!strcmp(function, "pois"))
        gui_internal_cmd2_pois(this, function, in, out);
    else if(!strcmp(function, "route_description"))
        gui_internal_cmd2_route_description(this, function, in, out);
    else if(!strcmp(function, "route_height_profile"))
        gui_internal_cmd2_route_height_profile(this, function, in, out);
    else if(!strcmp(function, "setting_layout"))
        gui_internal_cmd2_setting_layout(this, function, in, out);
    else if(!strcmp(function, "setting_maps"))
        gui_internal_cmd2_setting_maps(this, function, in, out);
    else if(!strcmp(function, "setting_rules"))
        gui_internal_cmd2_setting_rules(this, function, in, out);
    else if(!strcmp(function, "setting_vehicle"))
        gui_internal_cmd2_setting_vehicle(this, function, in, out);
    else if(!strcmp(function, "town"))
        gui_internal_cmd2_town(this, function, in, out);
    else if(!strcmp(function, "enter_coord"))
        gui_internal_cmd_enter_coord(this, function, in, out);
    else if(!strcmp(function, "waypoints"))
        gui_internal_cmd2_waypoints(this, function, in, out);
    else if(!strcmp(function, "about"))
        gui_internal_cmd2_about(this, function, in, out);

    if(entering)
        graphics_draw_mode(this->gra, draw_mode_end);
    return 0;
}

static struct command_table commands[] = {
    {"E",command_cast(gui_internal_cmd_escape)},
    {"abort_navigation",command_cast(gui_internal_cmd2_abort_navigation)},
    {"back",command_cast(gui_internal_cmd2_back)},
    {"back_to_map",command_cast(gui_internal_cmd2_back_to_map)},
    {"bookmarks",command_cast(gui_internal_cmd2)},
    {"debug",command_cast(gui_internal_cmd_debug)},
    {"formerdests",command_cast(gui_internal_cmd2)},
    {"get_data",command_cast(gui_internal_get_data)},
    {"img",command_cast(gui_internal_cmd_img)},
    {"locale",command_cast(gui_internal_cmd2)},
    {"log",command_cast(gui_internal_cmd_log)},
    {"menu",command_cast(gui_internal_cmd_menu2)},
    {"position",command_cast(gui_internal_cmd2_position)},
    {"pois",command_cast(gui_internal_cmd2)},
    {"redraw_map",command_cast(gui_internal_cmd_redraw_map)},
    {"refresh",command_cast(gui_internal_cmd2_refresh)},
    {"route_description",command_cast(gui_internal_cmd2)},
    {"route_height_profile",command_cast(gui_internal_cmd2)},
    {"set",command_cast(gui_internal_cmd2_set)},
    {"setting_layout",command_cast(gui_internal_cmd2)},
    {"setting_maps",command_cast(gui_internal_cmd2)},
    {"setting_rules",command_cast(gui_internal_cmd2)},
    {"setting_vehicle",command_cast(gui_internal_cmd2)},
    {"town",command_cast(gui_internal_cmd2)},
    {"enter_coord",command_cast(gui_internal_cmd2)},
    {"quit",command_cast(gui_internal_cmd2_quit)},
    {"waypoints",command_cast(gui_internal_cmd2)},
    {"write",command_cast(gui_internal_cmd_write)},
    {"about",command_cast(gui_internal_cmd2)},
#if HAS_IFADDRS
    {"network_info",command_cast(gui_internal_cmd2)},
#endif
};

void gui_internal_command_init(struct gui_priv *this, struct attr **attrs) {
    struct attr *attr;
    if ((attr=attr_search(attrs, attr_callback_list))) {
        command_add_table(attr->u.callback_list, commands, sizeof(commands)/sizeof(struct command_table), this);
    }
}


