/*
   Directory hotlist -- for the Midnight Commander

   Copyright (C) 1994-2025
   Free Software Foundation, Inc.

   Written by:
   Radek Doulik, 1994
   Janne Kukonlehto, 1995
   Andrej Borsenkow, 1996
   Norbert Warmuth, 1997
   Andrew Borodin <aborodin@vmail.ru>, 2012-2022

   Janne did the original Hotlist code, Andrej made the groupable
   hotlist; the move hotlist and revamped the file format and made
   it stronger.

   This file is part of the Midnight Commander.

   The Midnight Commander 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 3 of the License,
   or (at your option) any later version.

   The Midnight Commander 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, see <http://www.gnu.org/licenses/>.
 */

/** \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 "lib/global.h"

#include "lib/tty/tty.h"        /* COLS */
#include "lib/tty/key.h"        /* KEY_M_CTRL */
#include "lib/skin.h"           /* colors */
#include "lib/mcconfig.h"       /* Load/save directories hotlist */
#include "lib/fileloc.h"
#include "lib/strutil.h"
#include "lib/vfs/vfs.h"
#include "lib/util.h"
#include "lib/widget.h"

#include "src/setup.h"          /* For profile_bname */
#include "src/history.h"

#include "command.h"            /* cmdline */

#include "hotlist.h"

/*** global variables ****************************************************************************/

/*** file scope macro definitions ****************************************************************/

#define UX 3
#define UY 2

#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_ENTER_GROUP   (B_USER + 4)
#define B_UP_GROUP      (B_USER + 5)
#define B_INSERT        (B_USER + 6)
#define B_APPEND        (B_USER + 7)
#define B_MOVE          (B_USER + 8)
#ifdef ENABLE_VFS
#define B_FREE_ALL_VFS  (B_USER + 9)
#define B_REFRESH_VFS   (B_USER + 10)
#endif

#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

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

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

/*** file scope type declarations ****************************************************************/

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

static struct
{
    /*
     * these reflect run time state
     */

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

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

/*** forward declarations (file scope functions) *************************************************/

/*** file scope variables ************************************************************************/

static WPanel *our_panel;

static gboolean hotlist_has_dot_dot = TRUE;

static WDialog *hotlist_dlg, *movelist_dlg;
static WGroupbox *hotlist_group, *movelist_group;
static WListbox *l_hotlist, *l_movelist;
static WLabel *pname;

static struct
{
    int ret_cmd, flags, y, x, len;
    const char *text;
    int type;
    widget_pos_flags_t pos_flags;
} hotlist_but[] = {
    /* *INDENT-OFF* */
    { B_ENTER, DEFPUSH_BUTTON, 0, 0, 0, N_("Change &to"),
            LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
#ifdef ENABLE_VFS
    { B_FREE_ALL_VFS, NORMAL_BUTTON, 0, 20, 0, N_("&Free VFSs now"),
            LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_REFRESH_VFS, NORMAL_BUTTON, 0, 43, 0, N_("&Refresh"),
            LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
#endif
    { B_ADD_CURRENT, NORMAL_BUTTON, 0, 20, 0, N_("&Add current"),
            LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_UP_GROUP, NORMAL_BUTTON, 0, 42, 0, N_("&Up"),
            LIST_HOTLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_CANCEL, NORMAL_BUTTON, 0, 53, 0, N_("&Cancel"),
            LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_RIGHT | WPOS_KEEP_BOTTOM },
    { B_NEW_GROUP, NORMAL_BUTTON, 1, 0, 0, N_("New &group"),
            LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_NEW_ENTRY, NORMAL_BUTTON, 1, 15, 0, N_("New &entry"),
            LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_INSERT, NORMAL_BUTTON, 1, 0, 0, N_("&Insert"),
            LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_APPEND, NORMAL_BUTTON, 1, 15, 0, N_("A&ppend"),
            LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_REMOVE, NORMAL_BUTTON, 1, 30, 0, N_("&Remove"),
            LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
    { B_MOVE, NORMAL_BUTTON, 1, 42, 0, N_("&Move"),
            LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }
    /* *INDENT-ON* */
};

static const size_t hotlist_but_num = G_N_ELEMENTS (hotlist_but);

static struct hotlist *hotlist = NULL;

static struct hotlist *current_group;

static GString *tkn_buf = NULL;

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

static int list_level = 0;

/* --------------------------------------------------------------------------------------------- */
/*** file scope functions ************************************************************************/
/* --------------------------------------------------------------------------------------------- */

static void init_movelist (struct hotlist *item);
static void add_new_group_cmd (void);
static void add_new_entry_cmd (WPanel * panel);
static void remove_from_hotlist (struct hotlist *entry);
static void load_hotlist (void);
static void add_dotdot_to_list (void);

/* --------------------------------------------------------------------------------------------- */
/** If current->data is 0, then we are dealing with a VFS pathname */

static void
update_path_name (void)
{
    const char *text = "";
    char *p;
    WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
    Widget *w = WIDGET (list);

    if (!listbox_is_empty (list))
    {
        char *ctext = NULL;
        void *cdata = NULL;

        listbox_get_current (list, &ctext, &cdata);
        if (cdata == NULL)
            text = ctext;
        else
        {
            struct hotlist *hlp = (struct hotlist *) cdata;

            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");
        }
    }

    p = g_strconcat (" ", current_group->label, " ", (char *) NULL);
    if (hotlist_state.moving)
        groupbox_set_title (movelist_group, str_trunc (p, w->rect.cols - 2));
    else
    {
        groupbox_set_title (hotlist_group, str_trunc (p, w->rect.cols - 2));
        label_set_text (pname, str_trunc (text, w->rect.cols));
    }
    g_free (p);
}

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

static void
fill_listbox (WListbox *list)
{
    struct hotlist *current;

    for (current = current_group->head; current != NULL; current = current->next)
        switch (current->type)
        {
        case HL_TYPE_GROUP:
            {
                char *lbl;

                lbl = g_strconcat ("->", current->label, (char *) NULL);
                listbox_add_item_take (list, LISTBOX_APPEND_AT_END, 0, lbl, current, FALSE);
            }
            break;
        case HL_TYPE_DOTDOT:
        case HL_TYPE_ENTRY:
            listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, current->label, current, FALSE);
            break;
        default:
            break;
        }
}

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

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

    if (current == entry)
        current_group->head = entry->next;
    else
    {
        while (current != NULL && current->next != entry)
            current = current->next;
        if (current != NULL)
            current->next = entry->next;
    }
    entry->next = entry->up = NULL;
}

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

#ifdef ENABLE_VFS
static void
add_name_to_list (const char *path)
{
    listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, path, NULL, FALSE);
}
#endif /* !ENABLE_VFS */

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

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

            if (listbox_is_empty (l_hotlist))
                return 0;       /* empty group - nothing to do */

            listbox_get_current (l_hotlist, NULL, (void **) &item);
            init_movelist (item);
            hotlist_state.moving = TRUE;
            ret = dlg_run (movelist_dlg);
            hotlist_state.moving = FALSE;
            listbox_get_current (l_movelist, NULL, (void **) &moveto_item);
            moveto_group = current_group;
            widget_destroy (WIDGET (movelist_dlg));
            current_group = saved;
            if (ret == B_CANCEL)
                return 0;
            if (moveto_item == item)
                return 0;       /* If we insert/append a before/after a
                                   it hardly changes anything ;) */
            unlink_entry (item);
            listbox_remove_current (l_hotlist);
            item->up = moveto_group;
            if (moveto_group->head == NULL)
                moveto_group->head = item;
            else if (moveto_item == NULL)
            {                   /* we have group with just comments */
                struct hotlist *p = moveto_group->head;

                /* skip comments */
                while (p->next != NULL)
                    p = p->next;
                p->next = item;
            }
            else if (ret == B_ENTER || ret == B_APPEND)
            {
                if (moveto_item->next == NULL)
                    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 (l_hotlist);
            repaint_screen ();
            hotlist_state.modified = TRUE;
            return 0;
        }
    case B_REMOVE:
        {
            struct hotlist *entry = NULL;

            listbox_get_current (l_hotlist, NULL, (void **) &entry);
            remove_from_hotlist (entry);
        }
        return 0;

    case B_NEW_GROUP:
        add_new_group_cmd ();
        return 0;

    case B_ADD_CURRENT:
        add2hotlist_cmd (our_panel);
        return 0;

    case B_NEW_ENTRY:
        add_new_entry_cmd (our_panel);
        return 0;

    case B_ENTER:
    case B_ENTER_GROUP:
        {
            WListbox *list;
            void *data;
            struct hotlist *hlp;

            list = hotlist_state.moving ? l_movelist : l_hotlist;
            listbox_get_current (list, NULL, &data);

            if (data == NULL)
                return 1;

            hlp = (struct hotlist *) data;

            if (hlp->type == HL_TYPE_ENTRY)
                return (action == B_ENTER ? 1 : 0);
            if (hlp->type != HL_TYPE_DOTDOT)
            {
                listbox_remove_list (list);
                current_group = hlp;
                fill_listbox (list);
                return 0;
            }
        }
        MC_FALLTHROUGH;         /* 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 (list);
            return 0;
        }

#ifdef ENABLE_VFS
    case B_FREE_ALL_VFS:
        vfs_expire (TRUE);
        MC_FALLTHROUGH;

    case B_REFRESH_VFS:
        listbox_remove_list (l_hotlist);
        listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
                          FALSE);
        vfs_fill_names (add_name_to_list);
        return 0;
#endif /* ENABLE_VFS */

    default:
        return 1;
    }
}

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

static int
hotlist_button_callback (WButton *button, int action)
{
    int ret;

    (void) button;
    ret = hotlist_run_cmd (action);
    update_path_name ();
    return ret;
}

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

static inline cb_ret_t
hotlist_handle_key (WDialog *h, int key)
{
    switch (key)
    {
    case KEY_M_CTRL | '\n':
        goto l1;

    case '\n':
    case KEY_ENTER:
        if (hotlist_button_callback (NULL, B_ENTER) != 0)
        {
            h->ret_value = B_ENTER;
            dlg_close (h);
        }
        return MSG_HANDLED;

    case KEY_RIGHT:
        /* enter to the group */
        if (hotlist_state.type == LIST_VFSLIST)
            return MSG_NOT_HANDLED;
        return hotlist_button_callback (NULL, B_ENTER_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;

    case KEY_LEFT:
        /* leave the group */
        if (hotlist_state.type == LIST_VFSLIST)
            return MSG_NOT_HANDLED;
        return hotlist_button_callback (NULL, B_UP_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;

    case KEY_DC:
        if (hotlist_state.moving)
            return MSG_NOT_HANDLED;
        hotlist_button_callback (NULL, B_REMOVE);
        return MSG_HANDLED;

      l1:
    case ALT ('\n'):
    case ALT ('\r'):
        if (!hotlist_state.moving)
        {
            void *ldata = NULL;

            listbox_get_current (l_hotlist, NULL, &ldata);

            if (ldata != NULL)
            {
                struct hotlist *hlp = (struct hotlist *) ldata;

                if (hlp->type == HL_TYPE_ENTRY)
                {
                    char *tmp;

                    tmp = g_strconcat ("cd ", hlp->directory, (char *) NULL);
                    input_insert (cmdline, tmp, FALSE);
                    g_free (tmp);
                    h->ret_value = B_CANCEL;
                    dlg_close (h);
                }
            }
        }
        return MSG_HANDLED;     /* ignore key */

    default:
        return MSG_NOT_HANDLED;
    }
}

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

static cb_ret_t
hotlist_callback (Widget *w, Widget *sender, widget_msg_t msg, int parm, void *data)
{
    WDialog *h = DIALOG (w);

    switch (msg)
    {
    case MSG_INIT:
    case MSG_NOTIFY:           /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */
        update_path_name ();
        return MSG_HANDLED;

    case MSG_UNHANDLED_KEY:
        return hotlist_handle_key (h, parm);

    case MSG_POST_KEY:
        /*
         * The code here has two purposes:
         *
         * (1) Always stay on the hotlist.
         *
         * Activating a button using its hotkey (and even pressing ENTER, as
         * there's a "default button") moves the focus to the button. But we
         * want to stay on the hotlist, to be able to use the usual keys (up,
         * down, etc.). So we do `widget_select (lst)`.
         *
         * (2) Refresh the hotlist.
         *
         * We may have run a command that changed the contents of the list.
         * We therefore need to refresh it. So we do `widget_draw (lst)`.
         */
        {
            Widget *lst;

            lst = WIDGET (h == hotlist_dlg ? l_hotlist : l_movelist);

            /* widget_select() already redraws the widget, but since it's a
             * no-op if the widget is already selected ("focused"), we have
             * to call widget_draw() separately. */
            if (!widget_get_state (lst, WST_FOCUSED))
                widget_select (lst);
            else
                widget_draw (lst);
        }
        return MSG_HANDLED;

    case MSG_RESIZE:
        {
            WRect r = w->rect;

            r.lines = LINES - (h == hotlist_dlg ? 2 : 6);
            r.cols = COLS - 6;

            return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
        }

    default:
        return dlg_default_callback (w, sender, msg, parm, data);
    }
}

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

static lcback_ret_t
hotlist_listbox_callback (WListbox *list)
{
    WDialog *dlg = DIALOG (WIDGET (list)->owner);

    if (!listbox_is_empty (list))
    {
        void *data = NULL;

        listbox_get_current (list, NULL, &data);

        if (data != NULL)
        {
            struct hotlist *hlp = (struct hotlist *) data;

            if (hlp->type == HL_TYPE_ENTRY)
            {
                dlg->ret_value = B_ENTER;
                dlg_close (dlg);
                return LISTBOX_DONE;
            }
            else
            {
                hotlist_button_callback (NULL, B_ENTER);
                send_message (dlg, NULL, MSG_POST_KEY, '\n', NULL);
                return LISTBOX_CONT;
            }
        }
        else
        {
            dlg->ret_value = B_ENTER;
            dlg_close (dlg);
            return LISTBOX_DONE;
        }
    }

    hotlist_button_callback (NULL, B_UP_GROUP);
    send_message (dlg, NULL, MSG_POST_KEY, 'u', NULL);
    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)
{
    size_t i;

    static gboolean i18n_flag = FALSE;

    if (!i18n_flag)
    {
        for (i = 0; i < hotlist_but_num; i++)
        {
#ifdef ENABLE_NLS
            hotlist_but[i].text = _(hotlist_but[i].text);
#endif /* ENABLE_NLS */
            hotlist_but[i].len = str_term_width1 (hotlist_but[i].text) + 3;
            if (hotlist_but[i].flags == DEFPUSH_BUTTON)
                hotlist_but[i].len += 2;
        }

        i18n_flag = TRUE;
    }

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

        len[0] = len[1] = 0;
        count[0] = count[1] = 0;
        cur_x[0] = cur_x[1] = 0;

        /* Count len of buttonbars, assuming 1 extra space between buttons */
        for (i = 0; i < hotlist_but_num; i++)
            if ((hotlist_but[i].type & list_type) != 0)
            {
                int row;

                row = hotlist_but[i].y;
                ++count[row];
                len[row] += hotlist_but[i].len + 1;
            }

        (len[0])--;
        (len[1])--;

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

        /* arrange buttons */
        for (i = 0; i < hotlist_but_num; i++)
            if ((hotlist_but[i].type & list_type) != 0)
            {
                int row;

                row = hotlist_but[i].y;

                if (hotlist_but[i].x != 0)
                {
                    /* not first int the row */
                    if (hotlist_but[i].ret_cmd == B_CANCEL)
                        hotlist_but[i].x = cols - hotlist_but[i].len - 6;
                    else
                        hotlist_but[i].x = cur_x[row];
                }

                cur_x[row] += hotlist_but[i].len + 1;
            }
    }

    return cols;
}

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

static void
init_hotlist (hotlist_t list_type)
{
    size_t i;
    const char *title, *help_node;
    int lines, cols;
    int y;
    int dh = 0;
    WGroup *g;
    WGroupbox *path_box;
    Widget *hotlist_widget;

    do_refresh ();

    lines = LINES - 2;
    cols = init_i18n_stuff (list_type, COLS - 6);

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

    hotlist_dlg =
        dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
                    NULL, help_node, title);
    g = GROUP (hotlist_dlg);

    y = UY;
    hotlist_group = groupbox_new (y, UX, lines - 10 + dh, cols - 2 * UX, _("Top level group"));
    hotlist_widget = WIDGET (hotlist_group);
    group_add_widget_autopos (g, hotlist_widget, WPOS_KEEP_ALL, NULL);

    l_hotlist =
        listbox_new (y + 1, UX + 1, hotlist_widget->rect.lines - 2, hotlist_widget->rect.cols - 2,
                     FALSE, hotlist_listbox_callback);

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

    /* insert before groupbox to view scrollbar */
    group_add_widget_autopos (g, l_hotlist, WPOS_KEEP_ALL, NULL);

    y += hotlist_widget->rect.lines;

    path_box = groupbox_new (y, UX, 3, hotlist_widget->rect.cols, _("Directory path"));
    group_add_widget_autopos (g, path_box, WPOS_KEEP_BOTTOM | WPOS_KEEP_HORZ, NULL);

    pname = label_new (y + 1, UX + 2, NULL);
    group_add_widget_autopos (g, pname, WPOS_KEEP_BOTTOM | WPOS_KEEP_LEFT, NULL);
    y += WIDGET (path_box)->rect.lines;

    group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);

    for (i = 0; i < hotlist_but_num; i++)
        if ((hotlist_but[i].type & list_type) != 0)
            group_add_widget_autopos (g,
                                      button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
                                                  hotlist_but[i].ret_cmd, hotlist_but[i].flags,
                                                  hotlist_but[i].text, hotlist_button_callback),
                                      hotlist_but[i].pos_flags, NULL);

    widget_select (WIDGET (l_hotlist));
}

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

static void
init_movelist (struct hotlist *item)
{
    size_t i;
    char *hdr;
    int lines, cols;
    int y;
    WGroup *g;
    Widget *movelist_widget;

    do_refresh ();

    lines = LINES - 6;
    cols = init_i18n_stuff (LIST_MOVELIST, COLS - 6);

    hdr = g_strdup_printf (_("Moving %s"), item->label);

    movelist_dlg =
        dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
                    NULL, "[Hotlist]", hdr);
    g = GROUP (movelist_dlg);

    g_free (hdr);

    y = UY;
    movelist_group = groupbox_new (y, UX, lines - 7, cols - 2 * UX, _("Directory label"));
    movelist_widget = WIDGET (movelist_group);
    group_add_widget_autopos (g, movelist_widget, WPOS_KEEP_ALL, NULL);

    l_movelist =
        listbox_new (y + 1, UX + 1, movelist_widget->rect.lines - 2, movelist_widget->rect.cols - 2,
                     FALSE, hotlist_listbox_callback);
    fill_listbox (l_movelist);
    /* insert before groupbox to view scrollbar */
    group_add_widget_autopos (g, l_movelist, WPOS_KEEP_ALL, NULL);

    y += movelist_widget->rect.lines;

    group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);

    for (i = 0; i < hotlist_but_num; i++)
        if ((hotlist_but[i].type & LIST_MOVELIST) != 0)
            group_add_widget_autopos (g,
                                      button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
                                                  hotlist_but[i].ret_cmd, hotlist_but[i].flags,
                                                  hotlist_but[i].text, hotlist_button_callback),
                                      hotlist_but[i].pos_flags, NULL);

    widget_select (WIDGET (l_movelist));
}

/* --------------------------------------------------------------------------------------------- */
/**
 * Destroy the list dialog.
 * Don't confuse with done_hotlist() for the list in memory.
 */

static void
hotlist_done (void)
{
    widget_destroy (WIDGET (hotlist_dlg));
    l_hotlist = NULL;
#if 0
    update_panels (UP_OPTIMIZE, UP_KEEPSEL);
#endif
    repaint_screen ();
}

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

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

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

static struct hotlist *
add2hotlist (char *label, char *directory, enum HotListType type, listbox_append_t 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 == NULL)
        load_hotlist ();

    listbox_get_current (l_hotlist, NULL, (void **) &current);

    /* Make sure '..' stays at the top of the list. */
    if ((current != NULL) && (current->type == HL_TYPE_DOTDOT))
        pos = LISTBOX_APPEND_AFTER;

    new = g_new0 (struct hotlist, 1);

    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 == NULL)
    {
        /* first element in group */
        current_group->head = new;
    }
    else if (pos == LISTBOX_APPEND_AFTER)
    {
        new->next = current->next;
        current->next = new;
    }
    else if (pos == LISTBOX_APPEND_BEFORE && current == current_group->head)
    {
        /* should be inserted before first item */
        new->next = current;
        current_group->head = new;
    }
    else if (pos == LISTBOX_APPEND_BEFORE)
    {
        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 != NULL)
            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;

            lbl = g_strconcat ("->", new->label, (char *) NULL);
            listbox_add_item_take (l_hotlist, pos, 0, lbl, new, FALSE);
        }
        else
            listbox_add_item (l_hotlist, pos, 0, new->label, new, FALSE);
        listbox_set_current (l_hotlist, l_hotlist->current);
    }

    return new;
}

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

static int
add_new_entry_input (const char *header, const char *text1, const char *text2,
                     const char *help, const char *def_text, char **r1, char **r2)
{
#if defined(WIN32)  //WIN32, quick
    quick_widget_t quick_widgets[8] = {0},
        *qc = quick_widgets;
#else
    quick_widget_t quick_widgets[] = {
        /* *INDENT-OFF* */
        QUICK_LABELED_INPUT (text1, input_label_above, def_text, "input-lbl", r1, NULL,
                             FALSE, FALSE, INPUT_COMPLETE_NONE),
        QUICK_SEPARATOR (FALSE),
        QUICK_LABELED_INPUT (text2, input_label_above, def_text, "input-lbl", r2, NULL,
                             FALSE, FALSE, INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
        QUICK_START_BUTTONS (TRUE, TRUE),
            QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
            QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
            QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
        QUICK_END
        /* *INDENT-ON* */
    };
#endif  //WIN32

    WRect r = { -1, -1, 0, 64 };

    quick_dialog_t qdlg =  QUICK_DIALOG_INIT (
        &r, header, help,
        quick_widgets, NULL, NULL
    );

#if defined(WIN32)  //WIN32, quick
    qc = XQUICK_LABELED_INPUT (qc,
                                text1, input_label_above, def_text, "input-lbl", r1, NULL,
                                FALSE, FALSE, INPUT_COMPLETE_NONE);
    qc = XQUICK_SEPARATOR (qc, FALSE);
    qc = XQUICK_LABELED_INPUT (qc,
                                text2, input_label_above, def_text, "input-lbl", r2, NULL,
                                FALSE, FALSE, INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
    qc = XQUICK_START_BUTTONS (qc, TRUE, TRUE);
    qc =      XQUICK_BUTTON (qc, N_("&Append"), B_APPEND, NULL, NULL);
    qc =      XQUICK_BUTTON (qc, N_("&Insert"), B_INSERT, NULL, NULL);
    qc =      XQUICK_BUTTON (qc, N_("&Cancel"), B_CANCEL, NULL, NULL);
    qc = XQUICK_END (qc); 
    assert(qc == (quick_widgets + (sizeof(quick_widgets)/sizeof(quick_widgets[0]))));
#endif

    return quick_dialog (&qdlg);
}

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

static void
add_new_entry_cmd (WPanel *panel)
{
    char *def_text;
    char *title = NULL;
    char *url = NULL;
    int ret;

    /* Take current directory as default value for input fields */
    def_text = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
    ret = add_new_entry_input (_("New hotlist entry"), _("Directory label:"),
                               _("Directory path:"), "[Hotlist]", def_text, &title, &url);
    g_free (def_text);

    if (ret == B_CANCEL || title == NULL || *title == '\0' || url == NULL || *url == '\0')
    {
        g_free (title);
        g_free (url);
        return;
    }

    if (ret == B_ENTER || ret == B_APPEND)
        add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AFTER);
    else
        add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_BEFORE);

    hotlist_state.modified = TRUE;
}

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

static int
add_new_group_input (const char *header, const char *label, char **result)
{
#if defined(WIN32)  //WIN32, quick
    quick_widget_t quick_widgets[6] = {0},
        *qc = quick_widgets;
#else
    quick_widget_t quick_widgets[] = {
        /* *INDENT-OFF* */
        QUICK_LABELED_INPUT (label, input_label_above, "", "input", result, NULL,
                             FALSE, FALSE, INPUT_COMPLETE_NONE),
        QUICK_START_BUTTONS (TRUE, TRUE),
            QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
            QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
            QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
        QUICK_END
        /* *INDENT-ON* */
    };
#endif  //WIN32

    WRect r = { -1, -1, 0, 64 };

    quick_dialog_t qdlg =  QUICK_DIALOG_INIT (
        &r, header, "[Hotlist]",
        quick_widgets, NULL, NULL
    );

    int ret;

#if defined(WIN32)  //WIN32, quick
    qc = XQUICK_LABELED_INPUT (qc,
                                label, input_label_above, "", "input", result, NULL,
                                FALSE, FALSE, INPUT_COMPLETE_NONE);
    qc = XQUICK_START_BUTTONS (qc, TRUE, TRUE);
    qc =      XQUICK_BUTTON (qc, N_("&Append"), B_APPEND, NULL, NULL);
    qc =      XQUICK_BUTTON (qc, N_("&Insert"), B_INSERT, NULL, NULL);
    qc =      XQUICK_BUTTON (qc, N_("&Cancel"), B_CANCEL, NULL, NULL);
    qc = XQUICK_END (qc);
    assert(qc == (quick_widgets + (sizeof(quick_widgets)/sizeof(quick_widgets[0]))));
#endif

    ret = quick_dialog (&qdlg);

    return (ret != B_CANCEL) ? ret : 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 == 0 || label == NULL || *label == '\0')
        return;

    if (ret == B_ENTER || ret == B_APPEND)
        add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_AFTER);
    else
        add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_BEFORE);

    hotlist_state.modified = TRUE;
}

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

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

    while (current != NULL)
    {
        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 == NULL)
        return;

    if (entry->type == HL_TYPE_DOTDOT)
        return;

    if (confirm_directory_hotlist_delete)
    {
        char text[BUF_MEDIUM];
        int result;

        if (safe_delete)
            query_set_sel (1);

        g_snprintf (text, sizeof (text), _("Are you sure you want to remove entry \"%s\"?"),
                    str_trunc (entry->label, 30));
        result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
                               _("&Yes"), _("&No"));
        if (result != 0)
            return;
    }

    if (entry->type == HL_TYPE_GROUP)
    {
        struct hotlist *head = entry->head;

        if (head != NULL && (head->type != HL_TYPE_DOTDOT || head->next != NULL))
        {
            char text[BUF_MEDIUM];
            int result;

            g_snprintf (text, sizeof (text), _("Group \"%s\" is not empty.\nRemove it?"),
                        str_trunc (entry->label, 30));
            result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
                                   _("&Yes"), _("&No"));
            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);
    hotlist_state.modified = TRUE;
}

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

static void
load_group (struct hotlist *grp)
{
    gchar **profile_keys, **keys;
    char *group_section;
    struct hotlist *current = 0;

    group_section = find_group_section (grp);

    keys = mc_config_get_keys (mc_global.main_config, group_section, NULL);

    current_group = grp;

    for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
        add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
                     g_strdup (*profile_keys), HL_TYPE_GROUP, LISTBOX_APPEND_AT_END);

    g_strfreev (keys);

    keys = mc_config_get_keys (mc_global.main_config, grp->directory, NULL);

    for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
        add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
                     g_strdup (*profile_keys), HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);

    g_free (group_section);
    g_strfreev (keys);

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

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

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 == '\\')
            {
                c = getc (hotlist_file);
                if (c == EOF)
                {
                    g_string_free (tkn_buf, TRUE);
                    return TKN_EOF;
                }
            }
            g_string_append_c (tkn_buf, c == '\n' ? ' ' : c);
        }
        ret = (c == EOF) ? TKN_EOF : TKN_STRING;
        break;
    case '\\':
        c = getc (hotlist_file);
        if (c == EOF)
        {
            g_string_free (tkn_buf, TRUE);
            return TKN_EOF;
        }
        if (c == '\n')
            goto again;

        MC_FALLTHROUGH;         /* 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;
}

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

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_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
                             LISTBOX_APPEND_AT_END);
            SKIP_TO_EOL;
            hot_load_group (new_grp);
            current_group = grp;
            break;
        case TKN_ENTRY:
            {
                CHECK_TOKEN (TKN_STRING);
                label = g_strndup (tkn_buf->str, tkn_buf->len);
                CHECK_TOKEN (TKN_URL);
                CHECK_TOKEN (TKN_STRING);
                url = tilde_expand (tkn_buf->str);
                add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
                SKIP_TO_EOL;
            }
            break;
        case TKN_COMMENT:
            label = g_strndup (tkn_buf->str, tkn_buf->len);
            add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
            break;
        case TKN_EOF:
            hotlist_state.readonly = TRUE;
            hotlist_state.file_error = TRUE;
            return;
        case TKN_EOL:
            /* skip empty lines */
            break;
        default:
            hotlist_state.readonly = TRUE;
            hotlist_state.file_error = TRUE;
            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_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
                             LISTBOX_APPEND_AT_END);
            SKIP_TO_EOL;
            hot_load_group (new_grp);
            current_group = grp;
            break;
        case TKN_ENTRY:
            {
                CHECK_TOKEN (TKN_STRING);
                label = g_strndup (tkn_buf->str, tkn_buf->len);
                CHECK_TOKEN (TKN_URL);
                CHECK_TOKEN (TKN_STRING);
                url = tilde_expand (tkn_buf->str);
                add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
                SKIP_TO_EOL;
            }
            break;
        case TKN_COMMENT:
            label = g_strndup (tkn_buf->str, tkn_buf->len);
            add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
            break;
        case TKN_EOL:
            /* skip empty lines */
            break;
        default:
            hotlist_state.readonly = TRUE;
            hotlist_state.file_error = TRUE;
            SKIP_TO_EOL;
            break;
        }
}

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

static void
clean_up_hotlist_groups (const char *section)
{
    char *grp_section;

    grp_section = g_strconcat (section, ".Group", (char *) NULL);
    if (mc_config_has_group (mc_global.main_config, section))
        mc_config_del_group (mc_global.main_config, section);

    if (mc_config_has_group (mc_global.main_config, grp_section))
    {
        char **profile_keys, **keys;

        keys = mc_config_get_keys (mc_global.main_config, grp_section, NULL);

        for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
            clean_up_hotlist_groups (*profile_keys);

        g_strfreev (keys);
        mc_config_del_group (mc_global.main_config, grp_section);
    }
    g_free (grp_section);
}

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

static void
load_hotlist (void)
{
    gboolean remove_old_list = FALSE;
    mc_stat_t 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 == NULL)
        hotlist_file_name = mc_config_get_full_path (MC_HOTLIST_FILE);

    hotlist = g_new0 (struct hotlist, 1);
    hotlist->type = HL_TYPE_GROUP;
    hotlist->label = g_strdup (_("Top level group"));
    hotlist->up = hotlist;
    /*
     * compatibility :-(
     */
    hotlist->directory = g_strdup ("Hotlist");

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

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

    if (remove_old_list)
    {
        GError *mcerror = NULL;

        clean_up_hotlist_groups ("Hotlist");
        if (!mc_config_save_file (mc_global.main_config, &mcerror))
            setup_save_config_show_error (mc_global.main_config->ini_path, &mcerror);

        mc_error_message (&mcerror, NULL);
    }

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

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

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

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

    for (current = grp->head; current != NULL; current = current->next)
        switch (current->type)
        {
        case HL_TYPE_GROUP:
            INDENT (list_level);
            fputs ("GROUP \"", hotlist_file);
            for (s = current->label; *s != '\0'; 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 != '\0'; s++)
            {
                if (*s == '"' || *s == '\\')
                    putc ('\\', hotlist_file);
                putc (*s, hotlist_file);
            }
            fputs ("\" URL \"", hotlist_file);
            for (s = current->directory; *s != '\0'; 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;
        default:
            break;
        }
}

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

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

/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */

void
add2hotlist_cmd (WPanel *panel)
{
    char *lc_prompt;
    const char *cp = N_("Label for \"%s\":");
    int l;
    char *label_string, *label;

#ifdef ENABLE_NLS
    cp = _(cp);
#endif

    /* extra variable to use it in the button callback */
    our_panel = panel;

    l = str_term_width1 (cp);
    label_string = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
    lc_prompt = g_strdup_printf (cp, str_trunc (label_string, COLS - 2 * UX - (l + 8)));
    label =
        input_dialog (_("Add to hotlist"), lc_prompt, MC_HISTORY_HOTLIST_ADD, label_string,
                      INPUT_COMPLETE_NONE);
    g_free (lc_prompt);

    if (label == NULL || *label == '\0')
    {
        g_free (label_string);
        g_free (label);
    }
    else
    {
        add2hotlist (label, label_string, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
        hotlist_state.modified = TRUE;
    }
}

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

char *
hotlist_show (hotlist_t list_type, WPanel *panel)
{
    char *target = NULL;
    int res;

    /* extra variable to use it in the button callback */
    our_panel = panel;

    hotlist_state.type = list_type;
    load_hotlist ();

    init_hotlist (list_type);

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

    hotlist_state.running = TRUE;
    res = dlg_run (hotlist_dlg);
    hotlist_state.running = FALSE;
    save_hotlist ();

    if (res == B_ENTER)
    {
        char *text = NULL;
        struct hotlist *hlp = NULL;

        listbox_get_current (l_hotlist, &text, (void **) &hlp);
        target = g_strdup (hlp != NULL ? hlp->directory : text);
    }

    hotlist_done ();
    return target;
}

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

gboolean
save_hotlist (void)
{
    gboolean saved = FALSE;
    mc_stat_t stat_buf;

    if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name != NULL)
    {
        mc_util_make_backup_if_possible (hotlist_file_name, ".bak");

        hotlist_file = fopen (hotlist_file_name, "w");
        if (hotlist_file == NULL)
            mc_util_restore_from_backup_if_possible (hotlist_file_name, ".bak");
        else
        {
            hot_save_group (hotlist);
            fclose (hotlist_file);
            stat (hotlist_file_name, &stat_buf);
            hotlist_file_mtime = stat_buf.st_mtime;
            hotlist_state.modified = FALSE;
            saved = TRUE;
        }
    }

    return saved;
}

/* --------------------------------------------------------------------------------------------- */
/**
 * Unload list from memory.
 * Don't confuse with hotlist_done() for GUI.
 */

void
done_hotlist (void)
{
    if (hotlist != NULL)
    {
        remove_group (hotlist);
        g_free (hotlist->label);
        g_free (hotlist->directory);
        MC_PTR_FREE (hotlist);
    }

    hotlist_state.loaded = FALSE;

    MC_PTR_FREE (hotlist_file_name);
    l_hotlist = NULL;
    current_group = NULL;

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

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