summaryrefslogtreecommitdiffstats
path: root/src/filemanager/hotlist.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/filemanager/hotlist.c')
-rw-r--r--src/filemanager/hotlist.c1733
1 files changed, 1733 insertions, 0 deletions
diff --git a/src/filemanager/hotlist.c b/src/filemanager/hotlist.c
new file mode 100644
index 0000000..fa04a3b
--- /dev/null
+++ b/src/filemanager/hotlist.c
@@ -0,0 +1,1733 @@
+/*
+ Directory hotlist -- for the Midnight Commander
+
+ Copyright (C) 1994-2023
+ 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;
+ GString *buff;
+
+ buff = g_string_new ("");
+
+ for (current = current_group->head; current != NULL; current = current->next)
+ switch (current->type)
+ {
+ case HL_TYPE_GROUP:
+ {
+ /* buff clean up */
+ g_string_truncate (buff, 0);
+ g_string_append (buff, "->");
+ g_string_append (buff, current->label);
+ listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, buff->str, 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;
+ }
+
+ 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 != 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 (l_hotlist, pos, 0, lbl, new, FALSE);
+ g_free (lbl);
+ }
+ 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, char **r1, char **r2)
+{
+ quick_widget_t quick_widgets[] = {
+ /* *INDENT-OFF* */
+ QUICK_LABELED_INPUT (text1, input_label_above, *r1, "input-lbl", r1, NULL,
+ FALSE, FALSE, INPUT_COMPLETE_NONE),
+ QUICK_SEPARATOR (FALSE),
+ QUICK_LABELED_INPUT (text2, input_label_above, *r2, "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* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, header, help,
+ quick_widgets, NULL, NULL
+ };
+
+ int ret;
+
+ ret = quick_dialog (&qdlg);
+
+ return (ret != B_CANCEL) ? ret : 0;
+}
+
+/* --------------------------------------------------------------------------------------------- */
+
+static void
+add_new_entry_cmd (WPanel * panel)
+{
+ char *title, *url, *to_free;
+ int ret;
+
+ /* Take current directory as default value for input fields */
+ to_free = title = url = 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]", &title, &url);
+ g_free (to_free);
+
+ if (ret == 0)
+ return;
+ if (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)
+{
+ 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* */
+ };
+
+ WRect r = { -1, -1, 0, 64 };
+
+ quick_dialog_t qdlg = {
+ r, header, "[Hotlist]",
+ quick_widgets, NULL, NULL
+ };
+
+ int ret;
+
+ 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;
+ 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 == 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;
+ struct stat 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;
+ }
+}
+
+/* --------------------------------------------------------------------------------------------- */