/* Directory hotlist -- for the Midnight Commander
   Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002,
   2003, 2004, 2005, 2006, 2007 Free Software Foundation, Inc.

   Written by:
    1994 Radek Doulik
    1995 Janne Kukonlehto
    1996 Andrej Borsenkow
    1997 Norbert Warmuth

   Janne did the original Hotlist code, Andrej made the groupable
   hotlist; the move hotlist and revamped the file format and made
   it stronger.
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  
 */

/** \file hotlist.c
 *  \brief Source: directory hotlist
 */

#include <config.h>

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#include "global.h"
#include "tty.h"		/* COLS */
#include "color.h"		/* dialog_colors */
#include "dialog.h"
#include "widget.h"
#include "setup.h"		/* For profile_bname */
#include "profile.h"		/* Load/save directories hotlist */
#include "wtools.h"		/* QuickDialog */
#include "panel.h"		/* current_panel */
#include "main.h"		/* repaint_screen */
#include "hotlist.h"
#include "key.h"		/* KEY_M_CTRL */
#include "command.h"		/* cmdline */
#include "glibcompat.h"		/* g_strlcpy for glib < 2.0 */
#include "history.h"
#include "strutil.h"

#define UX		5
#define UY		2

#define BX		UX
#define BY		(LINES - 6)

#define BUTTONS		(sizeof(hotlist_but)/sizeof(struct _hotlist_but))
#define LABELS          3
#define B_ADD_CURRENT	B_USER
#define B_REMOVE        (B_USER + 1)
#define B_NEW_GROUP	(B_USER + 2)
#define B_NEW_ENTRY	(B_USER + 3)
#define B_UP_GROUP	(B_USER + 4)
#define B_INSERT	(B_USER + 5)
#define B_APPEND	(B_USER + 6)
#define B_MOVE		(B_USER + 7)

#ifdef USE_VFS
#include "../vfs/gc.h"
#define B_FREE_ALL_VFS	(B_USER + 8)
#define B_REFRESH_VFS	(B_USER + 9)
#endif

int hotlist_has_dot_dot = 1;

static WListbox *l_hotlist;
static WListbox *l_movelist;

static Dlg_head *hotlist_dlg;
static Dlg_head *movelist_dlg;

static WLabel *pname, *pname_group, *movelist_group;

enum HotListType {
    HL_TYPE_GROUP,
    HL_TYPE_ENTRY,
    HL_TYPE_COMMENT,
    HL_TYPE_DOTDOT
};

static struct {
    /*
     * these parameters are intended to be user configurable
     */
    int		expanded;	/* expanded view of all groups at startup */

    /*
     * these reflect run time state
     */

    int		loaded;		/* hotlist is loaded */
    int		readonly;	/* hotlist readonly */
    int		file_error;	/* parse error while reading file */
    int		running;	/* we are running dlg (and have to
    				   update listbox */
    int		moving;		/* we are in moving hotlist currently */
    int		modified;	/* hotlist was modified */
    int		type;		/* LIST_HOTLIST || LIST_VFSLIST */
}   hotlist_state;

static struct _hotlist_but {
    int ret_cmd, flags, y, x;
    const char *text;
    int   type;
} hotlist_but[] = {
    { B_MOVE, NORMAL_BUTTON,         1,   42, N_("&Move"),       LIST_HOTLIST},
    { B_REMOVE, NORMAL_BUTTON,       1,   30, N_("&Remove"),     LIST_HOTLIST},
    { B_APPEND, NORMAL_BUTTON,       1,   15, N_("&Append"),     LIST_MOVELIST},
    { B_INSERT, NORMAL_BUTTON,       1,    0, N_("&Insert"),     LIST_MOVELIST},
    { B_NEW_ENTRY, NORMAL_BUTTON,    1,   15, N_("New &Entry"),  LIST_HOTLIST},
    { B_NEW_GROUP, NORMAL_BUTTON,    1,    0, N_("New &Group"),  LIST_HOTLIST},
    { B_CANCEL, NORMAL_BUTTON,       0,   53, N_("&Cancel"),     LIST_HOTLIST|LIST_VFSLIST|LIST_MOVELIST},
    { B_UP_GROUP, NORMAL_BUTTON,     0,   42, N_("&Up"),         LIST_HOTLIST|LIST_MOVELIST},
    { B_ADD_CURRENT, NORMAL_BUTTON,  0,   20, N_("&Add current"), LIST_HOTLIST},
#ifdef	USE_VFS
    { B_REFRESH_VFS, NORMAL_BUTTON,  0,   43, N_("&Refresh"),    LIST_VFSLIST},
    { B_FREE_ALL_VFS, NORMAL_BUTTON, 0,   20, N_("Fr&ee VFSs now"), LIST_VFSLIST},
#endif
    { B_ENTER, DEFPUSH_BUTTON,       0,    0, N_("Change &To"),  LIST_HOTLIST|LIST_VFSLIST|LIST_MOVELIST},
};

/* Directory hotlist */
static struct hotlist{
    enum  HotListType type;
    char *directory;
    char *label;
    struct hotlist *head;
    struct hotlist *up;
    struct hotlist *next;
} *hotlist = NULL;

static struct hotlist *current_group;

static void init_movelist (int, struct hotlist *);
static void add_new_group_cmd (void);
static void add_new_entry_cmd (void);
static void remove_from_hotlist (struct hotlist *entry);
static void load_hotlist (void);
static void add_dotdot_to_list (void);

#define new_hotlist() g_new0(struct hotlist, 1)

static void
hotlist_refresh (Dlg_head * dlg)
{
    common_dialog_repaint (dlg);
    attrset (COLOR_NORMAL);
    draw_box (dlg, 2, 5, dlg->lines - (hotlist_state.moving ? 6 : 10),
	      dlg->cols - (UX * 2));
    if (!hotlist_state.moving)
	draw_box (dlg, dlg->lines - 8, 5, 3, dlg->cols - (UX * 2));
}

/* If current->data is 0, then we are dealing with a VFS pathname */
static inline void
update_path_name (void)
{
    const char *text = "";
    char *p;
    WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
    Dlg_head *dlg = list->widget.parent;

    if (list->current) {
	if (list->current->data != 0) {
	    struct hotlist *hlp = (struct hotlist *) list->current->data;

	    if (hlp->type == HL_TYPE_ENTRY ||
		hlp->type == HL_TYPE_DOTDOT)
		text = hlp->directory;
	    else if (hlp->type == HL_TYPE_GROUP)
		text = _("Subgroup - press ENTER to see list");
	} else {
	    text = list->current->text;
	}
    }
    if (!hotlist_state.moving)
	label_set_text (pname,
			str_trunc (text, dlg->cols - (UX * 2 + 4)));

    p = g_strconcat (" ", current_group->label, " ", (char *) NULL);
    if (!hotlist_state.moving)
	label_set_text (pname_group,
			str_trunc (p, dlg->cols - (UX * 2 + 4)));
    else
	label_set_text (movelist_group,
			str_trunc (p, dlg->cols - (UX * 2 + 4)));
    g_free (p);

    dlg_redraw (dlg);
}

#define CHECK_BUFFER  \
do { \
    int               i; \
\
    if ((i = strlen (current->label) + 3) > buflen) { \
      g_free (buf); \
      buf = g_malloc (buflen = 1024 * (i/1024 + 1)); \
    } \
    buf[0] = '\0'; \
} while (0)

static void fill_listbox (void)
{
    struct hotlist *current = current_group->head;
    GString *buff = g_string_new ("");

    while (current){
	switch (current->type) {
	case HL_TYPE_GROUP:
	    {
		g_string_append(buff,"->");
		g_string_append(buff,current->label);
		if (hotlist_state.moving)
		    listbox_add_item (l_movelist, 0, 0, buff->str, current);
		else
                    listbox_add_item (l_hotlist, 0, 0, buff->str, current);
	    }
	    break;
	case HL_TYPE_DOTDOT:
	case HL_TYPE_ENTRY:
		if (hotlist_state.moving)
		    listbox_add_item (l_movelist, 0, 0, current->label, current);
		else
		    listbox_add_item (l_hotlist, 0, 0, current->label, current);
	    break;
	default:
	    break;
	}
	current = current->next;
    }
    g_string_free (buff, TRUE);
}

static void
unlink_entry (struct hotlist *entry)
{
    struct hotlist *current = current_group->head;

    if (current == entry)
	current_group->head = entry->next;
    else {
	while (current && current->next != entry)
	    current = current->next;
	if (current)
	    current->next = entry->next;
    }
    entry->next =
	entry->up = 0;
}
    
#ifdef USE_VFS
static void add_name_to_list (const char *path)
{
    listbox_add_item (l_hotlist, 0, 0, path, 0);
}
#endif /* !USE_VFS */

static int
hotlist_button_callback (int action)
{
    switch (action) {
    case B_MOVE:
	{
	    struct hotlist *saved = current_group;
	    struct hotlist *item;
	    struct hotlist *moveto_item = 0;
	    struct hotlist *moveto_group = 0;
	    int ret;

	    if (!l_hotlist->current)
		return MSG_NOT_HANDLED;	/* empty group - nothing to do */
	    item = l_hotlist->current->data;
	    hotlist_state.moving = 1;
	    init_movelist (LIST_MOVELIST, item);
	    run_dlg (movelist_dlg);
	    ret = movelist_dlg->ret_value;
	    hotlist_state.moving = 0;
	    if (l_movelist->current)
		moveto_item = l_movelist->current->data;
	    moveto_group = current_group;
	    destroy_dlg (movelist_dlg);
	    current_group = saved;
	    if (ret == B_CANCEL)
		return MSG_NOT_HANDLED;
	    if (moveto_item == item)
		return MSG_NOT_HANDLED;	/* If we insert/append a before/after a
					   it hardly changes anything ;) */
	    unlink_entry (item);
	    listbox_remove_current (l_hotlist, 1);
	    item->up = moveto_group;
	    if (!moveto_group->head)
		moveto_group->head = item;
	    else if (!moveto_item) {	/* we have group with just comments */
		struct hotlist *p = moveto_group->head;

		/* skip comments */
		while (p->next)
		    p = p->next;
		p->next = item;
	    } else if (ret == B_ENTER || ret == B_APPEND)
		if (!moveto_item->next)
		    moveto_item->next = item;
		else {
		    item->next = moveto_item->next;
		    moveto_item->next = item;
	    } else if (moveto_group->head == moveto_item) {
		moveto_group->head = item;
		item->next = moveto_item;
	    } else {
		struct hotlist *p = moveto_group->head;

		while (p->next != moveto_item)
		    p = p->next;
		item->next = p->next;
		p->next = item;
	    }
	    listbox_remove_list (l_hotlist);
	    fill_listbox ();
	    repaint_screen ();
	    hotlist_state.modified = 1;
	    return MSG_NOT_HANDLED;
	    break;
	}
    case B_REMOVE:
	if (l_hotlist->current && l_hotlist->current->data)
	    remove_from_hotlist (l_hotlist->current->data);
	return MSG_NOT_HANDLED;
	break;

    case B_NEW_GROUP:
	add_new_group_cmd ();
	return MSG_NOT_HANDLED;
	break;

    case B_ADD_CURRENT:
	add2hotlist_cmd ();
	return MSG_NOT_HANDLED;
	break;

    case B_NEW_ENTRY:
	add_new_entry_cmd ();
	return MSG_NOT_HANDLED;
	break;

    case B_ENTER:
	{
	    WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
	    if (list->current) {
		if (list->current->data) {
		    struct hotlist *hlp =
			(struct hotlist *) list->current->data;
		    if (hlp->type == HL_TYPE_ENTRY)
			return MSG_HANDLED;
		    else if (hlp->type == HL_TYPE_DOTDOT) {
			/* Fall through - go up */
			;
		    }
		    else {
			listbox_remove_list (list);
			current_group = hlp;
			fill_listbox ();
			return MSG_NOT_HANDLED;
		    }
		} else
		    return MSG_HANDLED;
	    }
	}
	/* Fall through if list empty - just go up */

    case B_UP_GROUP:
	{
	    WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
	    listbox_remove_list (list);
	    current_group = current_group->up;
	    fill_listbox ();
	    return MSG_NOT_HANDLED;
	    break;
	}

#ifdef	USE_VFS
    case B_FREE_ALL_VFS:
	vfs_expire (1);
	/* fall through */

    case B_REFRESH_VFS:
	listbox_remove_list (l_hotlist);
	listbox_add_item (l_hotlist, 0, 0, home_dir, 0);
	vfs_fill_names (add_name_to_list);
	return MSG_NOT_HANDLED;
#endif				/* USE_VFS */

    default:
	return MSG_HANDLED;
	break;

    }
}

static cb_ret_t
hotlist_callback (Dlg_head *h, dlg_msg_t msg, int parm)
{
    switch (msg) {
    case DLG_DRAW:
	hotlist_refresh (h);
	return MSG_HANDLED;

    case DLG_UNHANDLED_KEY:
	switch (parm) {
	case KEY_M_CTRL | '\n':
	    goto l1;
	case '\n':
	case KEY_ENTER:
	case KEY_RIGHT:
	    if (hotlist_button_callback (B_ENTER)) {
		h->ret_value = B_ENTER;
		dlg_stop (h);
	    };
	    return MSG_HANDLED;
	    break;
	case KEY_LEFT:
	    if (hotlist_state.type != LIST_VFSLIST)
		return !hotlist_button_callback (B_UP_GROUP);
	    else
		return MSG_NOT_HANDLED;
	    break;
	case KEY_DC:
	    if (!hotlist_state.moving) {
		hotlist_button_callback (B_REMOVE);
		return MSG_HANDLED;
	    }
	    break;
	  l1:
	case ALT ('\n'):
	case ALT ('\r'):
	    if (!hotlist_state.moving) {
		if (l_hotlist->current) {
		    if (l_hotlist->current->data) {
			struct hotlist *hlp =
			    (struct hotlist *) l_hotlist->current->data;
			if (hlp->type == HL_TYPE_ENTRY) {
			    char *tmp =
				g_strconcat ("cd ", hlp->directory, (char *) NULL);
			    stuff (cmdline, tmp, 0);
			    g_free (tmp);
			    dlg_stop (h);
			    h->ret_value = B_CANCEL;
			    return MSG_HANDLED;
			}
		    }
		}
	    }
	    return MSG_HANDLED;	/* ignore key */
	}
	return MSG_NOT_HANDLED;

    case DLG_POST_KEY:
	if (hotlist_state.moving)
	    dlg_select_widget (l_movelist);
	else
	    dlg_select_widget (l_hotlist);
	/* always stay on hotlist */
	/* fall through */

    case DLG_INIT:
	attrset (MENU_ENTRY_COLOR);
	update_path_name ();
	return MSG_HANDLED;

    default:
	return default_dlg_callback (h, msg, parm);
    }
}

static int l_call (WListbox *list)
{
    Dlg_head *dlg = list->widget.parent;

    if (list->current){
	if (list->current->data) {
	    struct hotlist *hlp = (struct hotlist*) list->current->data;
	    if (hlp->type == HL_TYPE_ENTRY) {
		dlg->ret_value = B_ENTER;
		dlg_stop (dlg);
		return LISTBOX_DONE;
	    } else {
		hotlist_button_callback (B_ENTER);
		hotlist_callback (dlg, DLG_POST_KEY, '\n');
		return LISTBOX_CONT;
	    }
	} else {
	    dlg->ret_value = B_ENTER;
	    dlg_stop (dlg);
	    return LISTBOX_DONE;
	}
    }

    hotlist_button_callback (B_UP_GROUP);
    hotlist_callback (dlg, DLG_POST_KEY, 'u');
    return LISTBOX_CONT;
}

/*
 * Expands all button names (once) and recalculates button positions.
 * returns number of columns in the dialog box, which is 10 chars longer
 * then buttonbar.
 *
 * If common width of the window (i.e. in xterm) is less than returned
 * width - sorry :)  (anyway this did not handled in previous version too)
 */
static int
init_i18n_stuff(int list_type, int cols)
{
	register int i;
	static const char* cancel_but = N_("&Cancel");

#ifdef ENABLE_NLS
	static int hotlist_i18n_flag = 0;

	if (!hotlist_i18n_flag)
	{
		i = sizeof (hotlist_but) / sizeof (hotlist_but [0]);
		while (i--)
			hotlist_but [i].text = _(hotlist_but [i].text);

		cancel_but = _(cancel_but);
		hotlist_i18n_flag = 1;
	}
#endif /* ENABLE_NLS */

	/* Dynamic resizing of buttonbars */
	{
		int len[2], count[2]; /* at most two lines of buttons */
		int cur_x[2], row;

		i = sizeof (hotlist_but) / sizeof (hotlist_but [0]);
		len[0] = len[1] = count[0] = count[1] = 0;

		/* Count len of buttonbars, assuming 2 extra space between buttons */
		while (i--)
		{
			if (! (hotlist_but[i].type & list_type))
				continue;

			row = hotlist_but [i].y;
			++count [row];
			len [row] += str_term_width1 (hotlist_but [i].text) + 5;
			if (hotlist_but [i].flags == DEFPUSH_BUTTON)
				len [row] += 2;
		}
		len[0] -= 2;
		len[1] -= 2;

		cols = max(cols, max(len[0], len[1]));

		/* arrange buttons */

		cur_x[0] = cur_x[1] = 0;
		i = sizeof (hotlist_but) / sizeof (hotlist_but [0]);
		while (i--)
		{
			if (! (hotlist_but[i].type & list_type))
				continue;

			row = hotlist_but [i].y;

			if (hotlist_but [i].x != 0) 
			{
				/* not first int the row */
				if (!strcmp (hotlist_but [i].text, cancel_but))
					hotlist_but [i].x = 
						cols - str_term_width1 (hotlist_but [i].text) - 13;
				else
					hotlist_but [i].x = cur_x [row];
			}

			cur_x [row] += str_term_width1 (hotlist_but [i].text) + 2
				+ (hotlist_but [i].flags == DEFPUSH_BUTTON ? 5 : 3);
		}
	}
	
	return cols;
}

static void
init_hotlist (int list_type)
{
    size_t i;
    const char *title, *help_node;
    int hotlist_cols;

    hotlist_cols = init_i18n_stuff (list_type, COLS - 6);

    do_refresh ();

    hotlist_state.expanded =
	GetPrivateProfileInt ("HotlistConfig", "expanded_view_of_groups",
			      0, profile_name);

    if (list_type == LIST_VFSLIST) {
	title = _("Active VFS directories");
	help_node = "[vfshot]";	/* FIXME - no such node */
    } else {
	title = _("Directory hotlist");
	help_node = "[Hotlist]";
    }

    hotlist_dlg =
	create_dlg (0, 0, LINES - 2, hotlist_cols, dialog_colors,
		    hotlist_callback, help_node, title, DLG_CENTER | DLG_REVERSE);

    for (i = 0; i < BUTTONS; i++) {
	if (hotlist_but[i].type & list_type)
	    add_widget (hotlist_dlg,
			button_new (BY + hotlist_but[i].y,
				    BX + hotlist_but[i].x,
				    hotlist_but[i].ret_cmd,
				    hotlist_but[i].flags,
				    hotlist_but[i].text,
				    hotlist_button_callback));
    }

    /* We add the labels. 
     *    pname       will hold entry's pathname;
     *    pname_group will hold name of current group
     */
    pname = label_new (UY - 11 + LINES, UX + 2, "");
    add_widget (hotlist_dlg, pname);
    if (!hotlist_state.moving) {
	add_widget (hotlist_dlg,
		    label_new (UY - 12 + LINES, UX + 1,
			       _(" Directory path ")));

	/* This one holds the displayed pathname */
	pname_group = label_new (UY, UX + 1, _(" Directory label "));
	add_widget (hotlist_dlg, pname_group);
    }
    /* get new listbox */
    l_hotlist =
	listbox_new (UY + 1, UX + 1, LINES - 14, COLS - 2 * UX - 8,
		     l_call);

    /* Fill the hotlist with the active VFS or the hotlist */
#ifdef USE_VFS
    if (list_type == LIST_VFSLIST) {
	listbox_add_item (l_hotlist, 0, 0, home_dir, 0);
	vfs_fill_names (add_name_to_list);
    } else
#endif				/* !USE_VFS */
	fill_listbox ();

    add_widget (hotlist_dlg, l_hotlist);
    /* add listbox to the dialogs */
}

static void
init_movelist (int list_type, struct hotlist *item)
{
    size_t i;
    char *hdr = g_strdup_printf (_("Moving %s"), item->label);
    int movelist_cols = init_i18n_stuff (list_type, COLS - 6);

    do_refresh ();

    movelist_dlg =
	create_dlg (0, 0, LINES - 6, movelist_cols, dialog_colors,
		    hotlist_callback, "[Hotlist]", hdr, DLG_CENTER | DLG_REVERSE);
    g_free (hdr);

    for (i = 0; i < BUTTONS; i++) {
	if (hotlist_but[i].type & list_type)
	    add_widget (movelist_dlg,
			button_new (BY - 4 + hotlist_but[i].y,
				    BX + hotlist_but[i].x,
				    hotlist_but[i].ret_cmd,
				    hotlist_but[i].flags,
				    hotlist_but[i].text,
				    hotlist_button_callback));
    }

    /* We add the labels.  We are interested in the last one,
     * that one will hold the path name label
     */
    movelist_group = label_new (UY, UX + 1, _(" Directory label "));
    add_widget (movelist_dlg, movelist_group);
    /* get new listbox */
    l_movelist =
	listbox_new (UY + 1, UX + 1, movelist_dlg->lines - 8,
			movelist_dlg->cols - 2 * UX - 2, l_call);

    fill_listbox ();

    add_widget (movelist_dlg, l_movelist);
    /* add listbox to the dialogs */
}

/*
 * Destroy the list dialog.
 * Don't confuse with done_hotlist() for the list in memory.
 */
static void hotlist_done (void)
{
    destroy_dlg (hotlist_dlg);
    l_hotlist = NULL;
    if (0)
	update_panels (UP_OPTIMIZE, UP_KEEPSEL);
    repaint_screen ();
}

static inline char *
find_group_section (struct hotlist *grp)
{
    return g_strconcat (grp->directory, ".Group", (char *) NULL);
}


/* 1.11.96 bor: added pos parameter to control placement of new item.
   see widget.c, listbox_add_item()
                now hotlist is in unsorted mode
 */
enum {
    HL_BEFORE_CURRENT = 1
   ,HL_AFTER_CURRENT  = 2
};

static struct hotlist *
add2hotlist (char *label, char *directory, enum HotListType type, int pos)
{
    struct hotlist *new;
    struct hotlist *current = NULL;

    /*
     * Hotlist is neither loaded nor loading.
     * Must be called by "Ctrl-x a" before using hotlist.
     */
    if (!current_group)
	load_hotlist ();

    if (l_hotlist && l_hotlist->current) {
	current = l_hotlist->current->data;

	/* Make sure `..' stays at the top of the list. */
	if (current->type == HL_TYPE_DOTDOT)
	    pos = HL_AFTER_CURRENT;
    }

    new = new_hotlist ();

    new->type      = type;
    new->label     = label;
    new->directory = directory;
    new->up	   = current_group;

    if (type == HL_TYPE_GROUP) {
	current_group = new;
	add_dotdot_to_list ();
	current_group = new->up;
    }

    if (!current_group->head) { /* first element in group */
	current_group->head = new;
    } else if (pos == HL_AFTER_CURRENT) {
	new->next     = current->next;
	current->next = new;
    } else if (pos == HL_BEFORE_CURRENT &&
	       current == current_group->head) {
			   /* should be inserted before first item */
	new->next = current;
	current_group->head = new;
    } else if (pos == HL_BEFORE_CURRENT) {
	struct hotlist  *p = current_group->head;

	while (p->next != current)
	    p = p->next;

	new->next = current;
	p->next   = new;
    } else {		    /* append at the end */
	struct hotlist  *p = current_group->head;

	while (p->next)
	    p = p->next;

	p->next = new;
    }

    if (hotlist_state.running && type != HL_TYPE_COMMENT &&
	type != HL_TYPE_DOTDOT) {
	if (type == HL_TYPE_GROUP) {
	    char  *lbl = g_strconcat ("->", new->label, (char *) NULL);

	    listbox_add_item (l_hotlist, pos, 0, lbl, new);
	    g_free (lbl);
	} else
	    listbox_add_item (l_hotlist, pos, 0, new->label, new);
	listbox_select_entry (l_hotlist, l_hotlist->current);
    }
    return new;

}

#ifdef ENABLE_NLS
/*
 * Support routine for add_new_entry_input()/add_new_group_input()
 * Change positions of buttons (first three widgets).
 *
 * This is just a quick hack. Accurate procedure must take care of
 * internationalized label lengths and total buttonbar length...assume
 * 64 is longer anyway.
 */
static void add_widgets_i18n(QuickWidget* qw, int len)
{
	int i, l[3], space, cur_x;

	for (i = 0; i < 3; i++)
	{
		qw [i].text = _(qw [i].text);
		l[i] = str_term_width1 (qw [i].text) + 3;
	}
	space = (len - 4 - l[0] - l[1] - l[2]) / 4;

	for (cur_x = 2 + space, i = 3; i--; cur_x += l[i] + space)
	{
		qw [i].relative_x = cur_x;
		qw [i].x_divisions = len;
	}
}
#endif /* ENABLE_NLS */

static int
add_new_entry_input (const char *header, const char *text1, const char *text2,
		     const char *help, char **r1, char **r2)
{
#define RELATIVE_Y_BUTTONS	4
#define RELATIVE_Y_LABEL_PTH	3
#define RELATIVE_Y_INPUT_PTH	4

    QuickDialog Quick_input;
    static QuickWidget quick_widgets [] = {
    { quick_button, 55, 80, RELATIVE_Y_BUTTONS, 0, N_("&Cancel"), 0, B_CANCEL, 
	  0, 0, NULL , NULL, NULL},
    { quick_button, 30, 80, RELATIVE_Y_BUTTONS, 0, N_("&Insert"), 0, B_INSERT, 
	  0, 0, NULL , NULL, NULL},
    { quick_button, 10, 80, RELATIVE_Y_BUTTONS, 0, N_("&Append"), 0, B_APPEND, 
	  0, 0, NULL , NULL, NULL},
    { quick_input,  4, 80, RELATIVE_Y_INPUT_PTH, 0, "",58, 0, 
	  0, 0, "input-pth" , NULL, NULL},
    { quick_label, RELATIVE_Y_LABEL_PTH, 80, 3, 0, 0, 0, 0, 
	  0, 0, NULL , NULL, NULL},
    { quick_input,  4, 80, 3, 0, "", 58, 0, 
	  0, 0, "input-lbl" , NULL, NULL},
    { quick_label,  3, 80, 2, 0, 0, 0, 0, 
	  0, 0, NULL , NULL, NULL},
    NULL_QuickWidget };
    
    int len;
    int i;
    int lines1, lines2;
    int cols1, cols2;
    
#ifdef ENABLE_NLS
	static int i18n_flag = 0;
#endif /* ENABLE_NLS */

    msglen(text1, &lines1, &cols1);
    msglen(text2, &lines2, &cols2);
    len = max (str_term_width1 (header), cols1);
    len = max (len, cols2) + 4;
    len = max (len, 64);

#ifdef ENABLE_NLS
	if (!i18n_flag)
	{
		add_widgets_i18n(quick_widgets, len);
		i18n_flag = 1;
	}
#endif /* ENABLE_NLS */

    Quick_input.xlen  = len;
    Quick_input.xpos  = -1;
    Quick_input.title = header;
    Quick_input.help  = help;
    Quick_input.i18n  = 0;
    quick_widgets [6].text = text1;
    quick_widgets [4].text = text2;
    quick_widgets [5].text = *r1;
    quick_widgets [3].text = *r2;

    for (i = 0; i < 7; i++)
	quick_widgets [i].y_divisions = lines1+lines2+7;
    Quick_input.ylen  = lines1 + lines2 + 7;

    quick_widgets [0].relative_y = RELATIVE_Y_BUTTONS + (lines1 + lines2);
    quick_widgets [1].relative_y = RELATIVE_Y_BUTTONS + (lines1 + lines2);
    quick_widgets [2].relative_y = RELATIVE_Y_BUTTONS + (lines1 + lines2);
    quick_widgets [3].relative_y = RELATIVE_Y_INPUT_PTH + (lines1);
    quick_widgets [4].relative_y = RELATIVE_Y_LABEL_PTH + (lines1);

    quick_widgets [5].str_result = r1;
    quick_widgets [3].str_result = r2;
    
    Quick_input.widgets = quick_widgets;
    if ((i = quick_dialog (&Quick_input)) != B_CANCEL){
	 return i;
    } else
	 return 0;
}

static void add_new_entry_cmd (void)
{
    char *title, *url, *to_free;
    int ret;

    /* Take current directory as default value for input fields */
    to_free = title = url = strip_password (g_strdup (current_panel->cwd), 1);

    ret = add_new_entry_input (_("New hotlist entry"), _("Directory label"),
			       _("Directory path"), "[Hotlist]", &title, &url);
    g_free (to_free);

    if (!ret)
	return;
    if (!title || !*title || !url || !*url) {
	g_free (title);
	g_free (url);
	return;
    }

    if (ret == B_ENTER || ret == B_APPEND)
	add2hotlist (title, url, HL_TYPE_ENTRY, HL_AFTER_CURRENT);
    else
	add2hotlist (title, url, HL_TYPE_ENTRY, HL_BEFORE_CURRENT);

    hotlist_state.modified = 1;
}

static int
add_new_group_input (const char *header, const char *label, char **result)
{
    int		ret;
    QuickDialog Quick_input;
    static QuickWidget quick_widgets [] = {
    { quick_button, 55, 80, 1, 0, N_("&Cancel"), 0, B_CANCEL, 0, 0,
	  NULL , NULL, NULL},
    { quick_button, 30, 80, 1, 0, N_("&Insert"), 0, B_INSERT, 0, 0,
	  NULL , NULL, NULL},
    { quick_button, 10, 80, 1, 0, N_("&Append"), 0, B_APPEND, 0, 0,
	  NULL , NULL, NULL},
    { quick_input,  4, 80,  0, 0, "", 58, 0, 0, 0, "input" , NULL, NULL},
    { quick_label,  3, 80,  2, 0,  0,  0, 0, 0, 0, NULL , NULL, NULL},
    NULL_QuickWidget };
    int relative_y[] = {1, 1, 1, 0, 2}; /* the relative_x component from the
                                           quick_widgets variable above */
    int len;
    int i;
    int lines, cols;

#ifdef ENABLE_NLS
	static int i18n_flag = 0;
#endif /* ENABLE_NLS */

    msglen (label, &lines, &cols);
    len = max (str_term_width1 (header), cols) + 4;
    len = max (len, 64);

#ifdef ENABLE_NLS
	if (!i18n_flag)
	{
		add_widgets_i18n(quick_widgets, len);
		i18n_flag = 1;
	}
#endif /* ENABLE_NLS */

    Quick_input.xlen  = len;
    Quick_input.xpos  = -1;
    Quick_input.title = header;
    Quick_input.help  = "[Hotlist]";
    Quick_input.i18n  = 0;
    quick_widgets [4].text = label;

    for (i = 0; i < 5; i++)
	quick_widgets [i].y_divisions = lines+6;
    Quick_input.ylen  = lines + 6;

    for (i = 0; i < 4; i++)
	quick_widgets [i].relative_y = relative_y[i] + 2 + lines;

    quick_widgets [3].str_result = result;
    quick_widgets [3].text       = "";
    
    Quick_input.widgets = quick_widgets;
    if ((ret = quick_dialog (&Quick_input)) != B_CANCEL){
	return ret;
    } else
	return 0;
}

static void add_new_group_cmd (void)
{
    char   *label;
    int		ret;

    ret = add_new_group_input (_(" New hotlist group "), _("Name of new group"), &label);
    if (!ret || !label || !*label)
	return;

    if (ret == B_ENTER || ret == B_APPEND)
	add2hotlist (label, 0, HL_TYPE_GROUP, HL_AFTER_CURRENT);
    else
	add2hotlist (label, 0, HL_TYPE_GROUP, HL_BEFORE_CURRENT);

    hotlist_state.modified = 1;
}

void add2hotlist_cmd (void)
{
    char *prompt, *label;
    const char *cp = _("Label for \"%s\":");
    int l = str_term_width1 (cp);
    char *label_string = g_strdup (current_panel->cwd);

    strip_password (label_string, 1);

    prompt = g_strdup_printf (cp, path_trunc (current_panel->cwd, COLS-2*UX-(l+8)));
    label = input_dialog (_(" Add to hotlist "), prompt, MC_HISTORY_HOTLIST_ADD, label_string);
    g_free (prompt);

    if (!label || !*label) {
	g_free (label_string);
	g_free (label);
	return;
    }
    add2hotlist (label, label_string, HL_TYPE_ENTRY, 0);
    hotlist_state.modified = 1;
}

static void remove_group (struct hotlist *grp)
{
    struct hotlist *current = grp->head;

    while (current) {
	struct hotlist *next = current->next;

	if (current->type == HL_TYPE_GROUP)
	    remove_group (current);

	g_free (current->label);
	g_free (current->directory);
	g_free (current);

	current = next;
    }

}
	
static void remove_from_hotlist (struct hotlist *entry)
{
    if (entry->type == HL_TYPE_DOTDOT)
	return;

    if (confirm_directory_hotlist_delete) {
	char *title;
	int result;

	title = g_strconcat (_(" Remove: "),
				   str_trunc (entry->label, 30),
				   " ",
				   NULL);

	if (safe_delete)
	    query_set_sel (1);
	result = query_dialog (title,
			_("\n Are you sure you want to remove this entry?"),
			D_ERROR, 2, _("&Yes"), _("&No"));

	g_free (title);

	if (result != 0)
	    return;
    }

    if (entry->type == HL_TYPE_GROUP) {
	if (entry->head) {
	    char *header;
	    int   result;

	    header = g_strconcat (_(" Remove: "),
				   str_trunc (entry->label, 30),
				   " ",
				   NULL);
	    result = query_dialog (header, _("\n Group not empty.\n Remove it?"),
				   D_ERROR, 2,
				   _("&Yes"), _("&No"));
	    g_free (header);

	    if (result != 0)
		return;
	}

	remove_group (entry);
    }

    unlink_entry (entry);

    g_free (entry->label);
    g_free (entry->directory);
    g_free (entry);
    /* now remove list entry from screen */
    listbox_remove_current (l_hotlist, 1);
    hotlist_state.modified = 1;
}

char *hotlist_cmd (int vfs_or_hotlist)
{
    char *target = NULL;

    hotlist_state.type = vfs_or_hotlist;
    load_hotlist ();

    init_hotlist (vfs_or_hotlist);

    /* display file info */
    attrset (SELECTED_COLOR);

    hotlist_state.running = 1;
    run_dlg (hotlist_dlg);
    hotlist_state.running = 0;
    save_hotlist ();

    switch (hotlist_dlg->ret_value) {
    case B_CANCEL:
	break;

    case B_ENTER:
	if (l_hotlist->current->data) {
	    struct hotlist *hlp = (struct hotlist*) l_hotlist->current->data;
	    target = g_strdup (hlp->directory);
	} else
	    target = g_strdup (l_hotlist->current->text);
	break;
    }

    hotlist_done ();
    return target;
}

static void
load_group (struct hotlist *grp)
{
    void *profile_keys;
    char *key, *value;
    char *group_section;
    struct hotlist *current = 0;
    
    group_section = find_group_section (grp);

    profile_keys = profile_init_iterator (group_section, profile_name);

    current_group = grp;

    while (profile_keys){
	profile_keys = profile_iterator_next (profile_keys, &key, &value);
	add2hotlist (g_strdup (value), g_strdup (key), HL_TYPE_GROUP, 0);
    }
    g_free (group_section);

    profile_keys = profile_init_iterator (grp->directory, profile_name);

    while (profile_keys){
	profile_keys = profile_iterator_next (profile_keys, &key, &value);
	add2hotlist (g_strdup (value),g_strdup (key), HL_TYPE_ENTRY, 0);
    }

    for (current = grp->head; current; current = current->next)
	load_group (current);
}

#define TKN_GROUP	0
#define TKN_ENTRY	1
#define TKN_STRING	2
#define TKN_URL		3
#define TKN_ENDGROUP	4
#define TKN_COMMENT	5
#define TKN_EOL		125
#define TKN_EOF		126
#define TKN_UNKNOWN	127

static GString *tkn_buf = NULL;

static char *hotlist_file_name;
static FILE *hotlist_file;
static time_t hotlist_file_mtime;

static int hot_skip_blanks (void)
{
    int c;

    while ((c = getc (hotlist_file)) != EOF && c != '\n' && g_ascii_isspace (c))
	;
    return c;
    
}

static int hot_next_token (void)
{
    int	c, ret=0;
    size_t l;


    if (tkn_buf == NULL) tkn_buf = g_string_new ("");
    g_string_set_size(tkn_buf,0);

again:
    c = hot_skip_blanks ();
    switch (c) {
    case EOF:
	ret = TKN_EOF;
	break;
    case '\n':
	ret = TKN_EOL;
	break;
    case '#':
	while ((c = getc (hotlist_file)) != EOF && c != '\n') {
	    g_string_append_c (tkn_buf, c);
	}
	ret = TKN_COMMENT;
	break;
    case '"':
	while ((c = getc (hotlist_file)) != EOF && c != '"') {
	    if (c == '\\')
		if ((c = getc (hotlist_file)) == EOF){
		    g_string_free (tkn_buf, TRUE);
		    return TKN_EOF;
		}
	    g_string_append_c (tkn_buf, c == '\n' ? ' ' : c);
	}
	if (c == EOF)
	    ret = TKN_EOF;
	else
	    ret = TKN_STRING;
	break;
    case '\\':
	if ((c = getc (hotlist_file)) == EOF){
	    g_string_free (tkn_buf, TRUE);
	    return TKN_EOF;
	}
	if (c == '\n')
	    goto again;

	/* fall through; it is taken as normal character */

    default:
	do {
	    g_string_append_c (tkn_buf, g_ascii_toupper (c));
	} while ((c = fgetc (hotlist_file)) != EOF && 
                         (g_ascii_isalnum (c) || !isascii (c)));
	if (c != EOF)
	    ungetc (c, hotlist_file);
        l = tkn_buf->len;
	if (strncmp (tkn_buf->str, "GROUP", l) == 0)
	    ret = TKN_GROUP;
	else if (strncmp (tkn_buf->str, "ENTRY", l) == 0)
	    ret = TKN_ENTRY;
	else if (strncmp (tkn_buf->str, "ENDGROUP", l) == 0)
	    ret = TKN_ENDGROUP;
	else if (strncmp (tkn_buf->str, "URL", l) == 0)
	    ret = TKN_URL;
	else
	    ret = TKN_UNKNOWN;
	break;
    }
    return ret;
}

#define SKIP_TO_EOL	{ \
int _tkn; \
while ((_tkn = hot_next_token ()) != TKN_EOF && _tkn != TKN_EOL) ; \
}

#define CHECK_TOKEN(_TKN_) \
if ((tkn = hot_next_token ()) != _TKN_) { \
    hotlist_state.readonly = 1; \
    hotlist_state.file_error = 1; \
    while (tkn != TKN_EOL && tkn != TKN_EOF) \
	tkn = hot_next_token (); \
break; \
}

static void
hot_load_group (struct hotlist * grp)
{
    int		tkn;
    struct hotlist *new_grp;
    char	*label, *url;

    current_group = grp;

    while ((tkn = hot_next_token()) != TKN_ENDGROUP)
	switch (tkn) {
	case TKN_GROUP:
	    CHECK_TOKEN(TKN_STRING);
	    new_grp = add2hotlist (g_strdup (tkn_buf->str), 0, HL_TYPE_GROUP, 0);
	    SKIP_TO_EOL;
	    hot_load_group (new_grp);
	    current_group = grp;
	    break;
	case TKN_ENTRY:
	    CHECK_TOKEN(TKN_STRING);
            label = g_strdup (tkn_buf->str);
	    CHECK_TOKEN(TKN_URL);
	    CHECK_TOKEN(TKN_STRING);
	    url = g_strdup (tkn_buf->str);
	    add2hotlist (label, url, HL_TYPE_ENTRY, 0);
	    SKIP_TO_EOL;
	    break;
	case TKN_COMMENT:
            label = g_strdup (tkn_buf->str);
	    add2hotlist (label, 0, HL_TYPE_COMMENT, 0);
	    break;
	case TKN_EOF:
	    hotlist_state.readonly = 1;
	    hotlist_state.file_error = 1;
	    return;
	    break;
	case TKN_EOL:
	    /* skip empty lines */
	    break;
	default:
	    hotlist_state.readonly = 1;
	    hotlist_state.file_error = 1;
	    SKIP_TO_EOL;
	    break;
	}
    SKIP_TO_EOL;
}

static void
hot_load_file (struct hotlist * grp)
{
    int		tkn;
    struct hotlist *new_grp;
    char	*label, *url;

    current_group = grp;

    while ((tkn = hot_next_token())!= TKN_EOF)
	switch (tkn) {
	case TKN_GROUP:
	    CHECK_TOKEN(TKN_STRING);
            new_grp = add2hotlist (g_strdup (tkn_buf->str), 0, HL_TYPE_GROUP, 0);
	    SKIP_TO_EOL;
	    hot_load_group (new_grp);
	    current_group = grp;
	    break;
	case TKN_ENTRY:
	    CHECK_TOKEN(TKN_STRING);
            label = g_strdup (tkn_buf->str);
	    CHECK_TOKEN(TKN_URL);
	    CHECK_TOKEN(TKN_STRING);
            url = g_strdup (tkn_buf->str);
	    add2hotlist (label, url, HL_TYPE_ENTRY, 0);
	    SKIP_TO_EOL;
	    break;
	case TKN_COMMENT:
            label = g_strdup (tkn_buf->str);
	    add2hotlist (label, 0, HL_TYPE_COMMENT, 0);
	    break;
	case TKN_EOL:
	    /* skip empty lines */
	    break;
	default:
	    hotlist_state.readonly = 1;
	    hotlist_state.file_error = 1;
	    SKIP_TO_EOL;
	    break;
	}
}

static void
clean_up_hotlist_groups (const char *section)
{
    char	*grp_section;
    void	*profile_keys;
    char	*key, *value;

    grp_section = g_strconcat (section, ".Group", (char *) NULL);
    if (profile_has_section (section, profile_name))
	profile_clean_section (section, profile_name);
    if (profile_has_section (grp_section, profile_name)) {
	profile_keys = profile_init_iterator (grp_section, profile_name);

	while (profile_keys) {
	    profile_keys = profile_iterator_next (profile_keys, &key, &value);
	    clean_up_hotlist_groups (key);
	}
	profile_clean_section (grp_section, profile_name);
    }
    g_free (grp_section);
}



static void
load_hotlist (void)
{
    int		remove_old_list = 0;
    struct stat stat_buf;

    if (hotlist_state.loaded) {
	stat (hotlist_file_name, &stat_buf);
	if (hotlist_file_mtime < stat_buf.st_mtime) 
	    done_hotlist ();
	else
	    return;
    }

    if (!hotlist_file_name)
	hotlist_file_name = concat_dir_and_file (home_dir, HOTLIST_FILENAME);
    
    hotlist	       = new_hotlist ();
    hotlist->type      = HL_TYPE_GROUP;
    hotlist->label     = g_strdup (_(" Top level group "));
    hotlist->up        = hotlist;
    /*
     * compatibility :-(
     */
    hotlist->directory = g_strdup ("Hotlist");

    if ((hotlist_file = fopen (hotlist_file_name, "r")) == 0) {
	int	result;

	load_group (hotlist);
	hotlist_state.loaded   = 1;
	/*
	 * just to be sure we got copy
	 */
	hotlist_state.modified = 1;
	result = save_hotlist ();
	hotlist_state.modified = 0;
	if (result) {
	    remove_old_list = 1;
	} else {
	    message (D_ERROR, _(" Hotlist Load "),
		     _("MC was unable to write ~/%s file, your old hotlist entries were not deleted"),
		     HOTLIST_FILENAME);
	}
    } else {
	hot_load_file (hotlist);
	fclose (hotlist_file);
	hotlist_state.loaded = 1;
    }

    if (remove_old_list) {
	clean_up_hotlist_groups ("Hotlist");
	sync_profiles ();
    }

    stat (hotlist_file_name, &stat_buf);
    hotlist_file_mtime = stat_buf.st_mtime;
    current_group = hotlist;
}


static int  list_level = 0;

static void
hot_save_group (struct hotlist *grp)
{
    struct hotlist *current = grp->head;
    int		    i;
    char	   *s;

#define INDENT(n) \
do { \
    for (i = 0; i < n; i++) \
	putc (' ', hotlist_file); \
} while (0)

    for (;current; current = current->next)
        switch (current->type) {
        case HL_TYPE_GROUP:
	    INDENT (list_level);
	    fputs ("GROUP \"", hotlist_file);
	    for (s = current->label; *s; s++) {
		if (*s == '"' || *s == '\\')
		    putc ('\\', hotlist_file);
		putc (*s, hotlist_file);
	    }
	    fputs ("\"\n", hotlist_file);
	    list_level += 2;
	    hot_save_group (current);
	    list_level -= 2;
	    INDENT (list_level);
	    fputs ("ENDGROUP\n", hotlist_file);
	    break;
        case HL_TYPE_ENTRY:
	    INDENT(list_level);
	    fputs ("ENTRY \"", hotlist_file);
	    for (s = current->label; *s; s++) {
		if (*s == '"' || *s == '\\')
		    putc ('\\', hotlist_file);
		putc (*s, hotlist_file);
	    }
	    fputs ("\" URL \"", hotlist_file);
	    for (s = current->directory; *s; s++) {
		if (*s == '"' || *s == '\\')
		    putc ('\\', hotlist_file);
		putc (*s, hotlist_file);
	    }
	    fputs ("\"\n", hotlist_file);
	    break;
	case HL_TYPE_COMMENT:
	    fprintf (hotlist_file, "#%s\n", current->label);
	    break;
	case HL_TYPE_DOTDOT:
	    /* do nothing */
	    break;
	}
}

int save_hotlist (void)
{
    int		saved = 0;
    struct      stat stat_buf;
    
    if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name) {
	char	*fbak = g_strconcat (hotlist_file_name, ".bak", (char *) NULL);

	rename (hotlist_file_name, fbak);
	if ((hotlist_file = fopen (hotlist_file_name, "w")) != 0) {
	    if (stat (fbak, &stat_buf) == 0)
		chmod (hotlist_file_name, stat_buf.st_mode);
	    else
		chmod (hotlist_file_name, S_IRUSR | S_IWUSR);
	    hot_save_group (hotlist);
	    fclose (hotlist_file);
	    stat (hotlist_file_name, &stat_buf);
	    hotlist_file_mtime = stat_buf.st_mtime;
	    saved = 1;
	    hotlist_state.modified = 0;
	} else
	    rename (fbak, hotlist_file_name);
	g_free (fbak);
    }

    return saved;
}

/*
 * Unload list from memory.
 * Don't confuse with hotlist_done() for GUI.
 */
void done_hotlist (void)
{
    if (hotlist){
	remove_group (hotlist);
	g_free (hotlist->label);
	g_free (hotlist->directory);
	g_free (hotlist);
	hotlist = 0;
    }

    hotlist_state.loaded = 0;

    g_free (hotlist_file_name);
    hotlist_file_name = 0;
    l_hotlist = 0;
    current_group = 0;

    if (tkn_buf){
	g_string_free (tkn_buf, TRUE);
	tkn_buf = NULL;
    }
}

static void
add_dotdot_to_list (void)
{
    if (current_group != hotlist) {
	if (hotlist_has_dot_dot != 0)
	    add2hotlist (g_strdup (".."), g_strdup (".."), HL_TYPE_DOTDOT, 0);
    }
}
