1 /*
2    Directory hotlist -- for the Midnight Commander
3 
4    Copyright (C) 1994-2021
5    Free Software Foundation, Inc.
6 
7    Written by:
8    Radek Doulik, 1994
9    Janne Kukonlehto, 1995
10    Andrej Borsenkow, 1996
11    Norbert Warmuth, 1997
12    Andrew Borodin <aborodin@vmail.ru>, 2012, 2013
13 
14    Janne did the original Hotlist code, Andrej made the groupable
15    hotlist; the move hotlist and revamped the file format and made
16    it stronger.
17 
18    This file is part of the Midnight Commander.
19 
20    The Midnight Commander is free software: you can redistribute it
21    and/or modify it under the terms of the GNU General Public License as
22    published by the Free Software Foundation, either version 3 of the License,
23    or (at your option) any later version.
24 
25    The Midnight Commander is distributed in the hope that it will be useful,
26    but WITHOUT ANY WARRANTY; without even the implied warranty of
27    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28    GNU General Public License for more details.
29 
30    You should have received a copy of the GNU General Public License
31    along with this program.  If not, see <http://www.gnu.org/licenses/>.
32  */
33 
34 /** \file hotlist.c
35  *  \brief Source: directory hotlist
36  */
37 
38 #include <config.h>
39 
40 #include <ctype.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <unistd.h>
46 
47 #include "lib/global.h"
48 
49 #include "lib/tty/tty.h"        /* COLS */
50 #include "lib/tty/key.h"        /* KEY_M_CTRL */
51 #include "lib/skin.h"           /* colors */
52 #include "lib/mcconfig.h"       /* Load/save directories hotlist */
53 #include "lib/fileloc.h"
54 #include "lib/strutil.h"
55 #include "lib/vfs/vfs.h"
56 #include "lib/util.h"
57 #include "lib/widget.h"
58 
59 #include "src/setup.h"          /* For profile_bname */
60 #include "src/history.h"
61 
62 #include "command.h"            /* cmdline */
63 
64 #include "hotlist.h"
65 
66 /*** global variables ****************************************************************************/
67 
68 /*** file scope macro definitions ****************************************************************/
69 
70 #define UX 3
71 #define UY 2
72 
73 #define B_ADD_CURRENT   B_USER
74 #define B_REMOVE        (B_USER + 1)
75 #define B_NEW_GROUP     (B_USER + 2)
76 #define B_NEW_ENTRY     (B_USER + 3)
77 #define B_ENTER_GROUP   (B_USER + 4)
78 #define B_UP_GROUP      (B_USER + 5)
79 #define B_INSERT        (B_USER + 6)
80 #define B_APPEND        (B_USER + 7)
81 #define B_MOVE          (B_USER + 8)
82 #ifdef ENABLE_VFS
83 #define B_FREE_ALL_VFS  (B_USER + 9)
84 #define B_REFRESH_VFS   (B_USER + 10)
85 #endif
86 
87 #define TKN_GROUP   0
88 #define TKN_ENTRY   1
89 #define TKN_STRING  2
90 #define TKN_URL     3
91 #define TKN_ENDGROUP 4
92 #define TKN_COMMENT  5
93 #define TKN_EOL      125
94 #define TKN_EOF      126
95 #define TKN_UNKNOWN  127
96 
97 #define SKIP_TO_EOL \
98 { \
99     int _tkn; \
100     while ((_tkn = hot_next_token ()) != TKN_EOF && _tkn != TKN_EOL) ; \
101 }
102 
103 #define CHECK_TOKEN(_TKN_) \
104 tkn = hot_next_token (); \
105 if (tkn != _TKN_) \
106 { \
107     hotlist_state.readonly = TRUE; \
108     hotlist_state.file_error = TRUE; \
109     while (tkn != TKN_EOL && tkn != TKN_EOF) \
110         tkn = hot_next_token (); \
111     break; \
112 }
113 
114 /*** file scope type declarations ****************************************************************/
115 
116 enum HotListType
117 {
118     HL_TYPE_GROUP,
119     HL_TYPE_ENTRY,
120     HL_TYPE_COMMENT,
121     HL_TYPE_DOTDOT
122 };
123 
124 static struct
125 {
126     /*
127      * these reflect run time state
128      */
129 
130     gboolean loaded;            /* hotlist is loaded */
131     gboolean readonly;          /* hotlist readonly */
132     gboolean file_error;        /* parse error while reading file */
133     gboolean running;           /* we are running dlg (and have to
134                                    update listbox */
135     gboolean moving;            /* we are in moving hotlist currently */
136     gboolean modified;          /* hotlist was modified */
137     hotlist_t type;             /* LIST_HOTLIST || LIST_VFSLIST */
138 } hotlist_state;
139 
140 /* Directory hotlist */
141 struct hotlist
142 {
143     enum HotListType type;
144     char *directory;
145     char *label;
146     struct hotlist *head;
147     struct hotlist *up;
148     struct hotlist *next;
149 };
150 
151 /*** file scope variables ************************************************************************/
152 
153 static WPanel *our_panel;
154 
155 static gboolean hotlist_has_dot_dot = TRUE;
156 
157 static WDialog *hotlist_dlg, *movelist_dlg;
158 static WGroupbox *hotlist_group, *movelist_group;
159 static WListbox *l_hotlist, *l_movelist;
160 static WLabel *pname;
161 
162 static struct
163 {
164     int ret_cmd, flags, y, x, len;
165     const char *text;
166     int type;
167     widget_pos_flags_t pos_flags;
168 } hotlist_but[] =
169 {
170     /* *INDENT-OFF* */
171     { B_ENTER, DEFPUSH_BUTTON, 0, 0, 0, N_("Change &to"),
172             LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
173 #ifdef ENABLE_VFS
174     { B_FREE_ALL_VFS, NORMAL_BUTTON, 0, 20, 0, N_("&Free VFSs now"),
175             LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
176     { B_REFRESH_VFS, NORMAL_BUTTON, 0, 43, 0, N_("&Refresh"),
177             LIST_VFSLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
178 #endif
179     { B_ADD_CURRENT, NORMAL_BUTTON, 0, 20, 0, N_("&Add current"),
180             LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
181     { B_UP_GROUP, NORMAL_BUTTON, 0, 42, 0, N_("&Up"),
182             LIST_HOTLIST | LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
183     { B_CANCEL, NORMAL_BUTTON, 0, 53, 0, N_("&Cancel"),
184             LIST_HOTLIST | LIST_VFSLIST | LIST_MOVELIST, WPOS_KEEP_RIGHT | WPOS_KEEP_BOTTOM },
185     { B_NEW_GROUP, NORMAL_BUTTON, 1, 0, 0, N_("New &group"),
186             LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
187     { B_NEW_ENTRY, NORMAL_BUTTON, 1, 15, 0, N_("New &entry"),
188             LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
189     { B_INSERT, NORMAL_BUTTON, 1, 0, 0, N_("&Insert"),
190             LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
191     { B_APPEND, NORMAL_BUTTON, 1, 15, 0, N_("A&ppend"),
192             LIST_MOVELIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
193     { B_REMOVE, NORMAL_BUTTON, 1, 30, 0, N_("&Remove"),
194             LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM },
195     { B_MOVE, NORMAL_BUTTON, 1, 42, 0, N_("&Move"),
196             LIST_HOTLIST, WPOS_KEEP_LEFT | WPOS_KEEP_BOTTOM }
197     /* *INDENT-ON* */
198 };
199 
200 static const size_t hotlist_but_num = G_N_ELEMENTS (hotlist_but);
201 
202 static struct hotlist *hotlist = NULL;
203 
204 static struct hotlist *current_group;
205 
206 static GString *tkn_buf = NULL;
207 
208 static char *hotlist_file_name;
209 static FILE *hotlist_file;
210 static time_t hotlist_file_mtime;
211 
212 static int list_level = 0;
213 
214 /* --------------------------------------------------------------------------------------------- */
215 /*** file scope functions ************************************************************************/
216 /* --------------------------------------------------------------------------------------------- */
217 
218 static void init_movelist (struct hotlist *item);
219 static void add_new_group_cmd (void);
220 static void add_new_entry_cmd (WPanel * panel);
221 static void remove_from_hotlist (struct hotlist *entry);
222 static void load_hotlist (void);
223 static void add_dotdot_to_list (void);
224 
225 /* --------------------------------------------------------------------------------------------- */
226 /** If current->data is 0, then we are dealing with a VFS pathname */
227 
228 static void
update_path_name(void)229 update_path_name (void)
230 {
231     const char *text = "";
232     char *p;
233     WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
234     Widget *w = WIDGET (list);
235 
236     if (!listbox_is_empty (list))
237     {
238         char *ctext = NULL;
239         void *cdata = NULL;
240 
241         listbox_get_current (list, &ctext, &cdata);
242         if (cdata == NULL)
243             text = ctext;
244         else
245         {
246             struct hotlist *hlp = (struct hotlist *) cdata;
247 
248             if (hlp->type == HL_TYPE_ENTRY || hlp->type == HL_TYPE_DOTDOT)
249                 text = hlp->directory;
250             else if (hlp->type == HL_TYPE_GROUP)
251                 text = _("Subgroup - press ENTER to see list");
252         }
253     }
254 
255     p = g_strconcat (" ", current_group->label, " ", (char *) NULL);
256     if (hotlist_state.moving)
257         groupbox_set_title (movelist_group, str_trunc (p, w->cols - 2));
258     else
259     {
260         groupbox_set_title (hotlist_group, str_trunc (p, w->cols - 2));
261         label_set_text (pname, str_trunc (text, w->cols));
262     }
263     g_free (p);
264 }
265 
266 /* --------------------------------------------------------------------------------------------- */
267 
268 static void
fill_listbox(WListbox * list)269 fill_listbox (WListbox * list)
270 {
271     struct hotlist *current;
272     GString *buff;
273 
274     buff = g_string_new ("");
275 
276     for (current = current_group->head; current != NULL; current = current->next)
277         switch (current->type)
278         {
279         case HL_TYPE_GROUP:
280             {
281                 /* buff clean up */
282                 g_string_truncate (buff, 0);
283                 g_string_append (buff, "->");
284                 g_string_append (buff, current->label);
285                 listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, buff->str, current, FALSE);
286             }
287             break;
288         case HL_TYPE_DOTDOT:
289         case HL_TYPE_ENTRY:
290             listbox_add_item (list, LISTBOX_APPEND_AT_END, 0, current->label, current, FALSE);
291             break;
292         default:
293             break;
294         }
295 
296     g_string_free (buff, TRUE);
297 }
298 
299 /* --------------------------------------------------------------------------------------------- */
300 
301 static void
unlink_entry(struct hotlist * entry)302 unlink_entry (struct hotlist *entry)
303 {
304     struct hotlist *current = current_group->head;
305 
306     if (current == entry)
307         current_group->head = entry->next;
308     else
309     {
310         while (current != NULL && current->next != entry)
311             current = current->next;
312         if (current != NULL)
313             current->next = entry->next;
314     }
315     entry->next = entry->up = NULL;
316 }
317 
318 /* --------------------------------------------------------------------------------------------- */
319 
320 #ifdef ENABLE_VFS
321 static void
add_name_to_list(const char * path)322 add_name_to_list (const char *path)
323 {
324     listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, path, NULL, FALSE);
325 }
326 #endif /* !ENABLE_VFS */
327 
328 /* --------------------------------------------------------------------------------------------- */
329 
330 static int
hotlist_run_cmd(int action)331 hotlist_run_cmd (int action)
332 {
333     switch (action)
334     {
335     case B_MOVE:
336         {
337             struct hotlist *saved = current_group;
338             struct hotlist *item = NULL;
339             struct hotlist *moveto_item = NULL;
340             struct hotlist *moveto_group = NULL;
341             int ret;
342 
343             if (listbox_is_empty (l_hotlist))
344                 return 0;       /* empty group - nothing to do */
345 
346             listbox_get_current (l_hotlist, NULL, (void **) &item);
347             init_movelist (item);
348             hotlist_state.moving = TRUE;
349             ret = dlg_run (movelist_dlg);
350             hotlist_state.moving = FALSE;
351             listbox_get_current (l_movelist, NULL, (void **) &moveto_item);
352             moveto_group = current_group;
353             widget_destroy (WIDGET (movelist_dlg));
354             current_group = saved;
355             if (ret == B_CANCEL)
356                 return 0;
357             if (moveto_item == item)
358                 return 0;       /* If we insert/append a before/after a
359                                    it hardly changes anything ;) */
360             unlink_entry (item);
361             listbox_remove_current (l_hotlist);
362             item->up = moveto_group;
363             if (moveto_group->head == NULL)
364                 moveto_group->head = item;
365             else if (moveto_item == NULL)
366             {                   /* we have group with just comments */
367                 struct hotlist *p = moveto_group->head;
368 
369                 /* skip comments */
370                 while (p->next != NULL)
371                     p = p->next;
372                 p->next = item;
373             }
374             else if (ret == B_ENTER || ret == B_APPEND)
375             {
376                 if (moveto_item->next == NULL)
377                     moveto_item->next = item;
378                 else
379                 {
380                     item->next = moveto_item->next;
381                     moveto_item->next = item;
382                 }
383             }
384             else if (moveto_group->head == moveto_item)
385             {
386                 moveto_group->head = item;
387                 item->next = moveto_item;
388             }
389             else
390             {
391                 struct hotlist *p = moveto_group->head;
392 
393                 while (p->next != moveto_item)
394                     p = p->next;
395                 item->next = p->next;
396                 p->next = item;
397             }
398             listbox_remove_list (l_hotlist);
399             fill_listbox (l_hotlist);
400             repaint_screen ();
401             hotlist_state.modified = TRUE;
402             return 0;
403         }
404     case B_REMOVE:
405         {
406             struct hotlist *entry = NULL;
407 
408             listbox_get_current (l_hotlist, NULL, (void **) &entry);
409             remove_from_hotlist (entry);
410         }
411         return 0;
412 
413     case B_NEW_GROUP:
414         add_new_group_cmd ();
415         return 0;
416 
417     case B_ADD_CURRENT:
418         add2hotlist_cmd (our_panel);
419         return 0;
420 
421     case B_NEW_ENTRY:
422         add_new_entry_cmd (our_panel);
423         return 0;
424 
425     case B_ENTER:
426     case B_ENTER_GROUP:
427         {
428             WListbox *list;
429             void *data;
430             struct hotlist *hlp;
431 
432             list = hotlist_state.moving ? l_movelist : l_hotlist;
433             listbox_get_current (list, NULL, &data);
434 
435             if (data == NULL)
436                 return 1;
437 
438             hlp = (struct hotlist *) data;
439 
440             if (hlp->type == HL_TYPE_ENTRY)
441                 return (action == B_ENTER ? 1 : 0);
442             if (hlp->type != HL_TYPE_DOTDOT)
443             {
444                 listbox_remove_list (list);
445                 current_group = hlp;
446                 fill_listbox (list);
447                 return 0;
448             }
449         }
450         MC_FALLTHROUGH;         /* if list empty - just go up */
451 
452     case B_UP_GROUP:
453         {
454             WListbox *list = hotlist_state.moving ? l_movelist : l_hotlist;
455 
456             listbox_remove_list (list);
457             current_group = current_group->up;
458             fill_listbox (list);
459             return 0;
460         }
461 
462 #ifdef ENABLE_VFS
463     case B_FREE_ALL_VFS:
464         vfs_expire (TRUE);
465         MC_FALLTHROUGH;
466 
467     case B_REFRESH_VFS:
468         listbox_remove_list (l_hotlist);
469         listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
470                           FALSE);
471         vfs_fill_names (add_name_to_list);
472         return 0;
473 #endif /* ENABLE_VFS */
474 
475     default:
476         return 1;
477     }
478 }
479 
480 /* --------------------------------------------------------------------------------------------- */
481 
482 static int
hotlist_button_callback(WButton * button,int action)483 hotlist_button_callback (WButton * button, int action)
484 {
485     int ret;
486 
487     (void) button;
488     ret = hotlist_run_cmd (action);
489     update_path_name ();
490     return ret;
491 }
492 
493 /* --------------------------------------------------------------------------------------------- */
494 
495 static inline cb_ret_t
hotlist_handle_key(WDialog * h,int key)496 hotlist_handle_key (WDialog * h, int key)
497 {
498     switch (key)
499     {
500     case KEY_M_CTRL | '\n':
501         goto l1;
502 
503     case '\n':
504     case KEY_ENTER:
505         if (hotlist_button_callback (NULL, B_ENTER) != 0)
506         {
507             h->ret_value = B_ENTER;
508             dlg_stop (h);
509         }
510         return MSG_HANDLED;
511 
512     case KEY_RIGHT:
513         /* enter to the group */
514         if (hotlist_state.type == LIST_VFSLIST)
515             return MSG_NOT_HANDLED;
516         return hotlist_button_callback (NULL, B_ENTER_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;
517 
518     case KEY_LEFT:
519         /* leave the group */
520         if (hotlist_state.type == LIST_VFSLIST)
521             return MSG_NOT_HANDLED;
522         return hotlist_button_callback (NULL, B_UP_GROUP) == 0 ? MSG_HANDLED : MSG_NOT_HANDLED;
523 
524     case KEY_DC:
525         if (hotlist_state.moving)
526             return MSG_NOT_HANDLED;
527         hotlist_button_callback (NULL, B_REMOVE);
528         return MSG_HANDLED;
529 
530       l1:
531     case ALT ('\n'):
532     case ALT ('\r'):
533         if (!hotlist_state.moving)
534         {
535             void *ldata = NULL;
536 
537             listbox_get_current (l_hotlist, NULL, &ldata);
538 
539             if (ldata != NULL)
540             {
541                 struct hotlist *hlp = (struct hotlist *) ldata;
542 
543                 if (hlp->type == HL_TYPE_ENTRY)
544                 {
545                     char *tmp;
546 
547                     tmp = g_strconcat ("cd ", hlp->directory, (char *) NULL);
548                     input_insert (cmdline, tmp, FALSE);
549                     g_free (tmp);
550                     h->ret_value = B_CANCEL;
551                     dlg_stop (h);
552                 }
553             }
554         }
555         return MSG_HANDLED;     /* ignore key */
556 
557     default:
558         return MSG_NOT_HANDLED;
559     }
560 }
561 
562 /* --------------------------------------------------------------------------------------------- */
563 
564 static cb_ret_t
hotlist_callback(Widget * w,Widget * sender,widget_msg_t msg,int parm,void * data)565 hotlist_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
566 {
567     WDialog *h = DIALOG (w);
568 
569     switch (msg)
570     {
571     case MSG_INIT:
572     case MSG_NOTIFY:           /* MSG_NOTIFY is fired by the listbox to tell us the item has changed. */
573         update_path_name ();
574         return MSG_HANDLED;
575 
576     case MSG_UNHANDLED_KEY:
577         return hotlist_handle_key (h, parm);
578 
579     case MSG_POST_KEY:
580         /*
581          * The code here has two purposes:
582          *
583          * (1) Always stay on the hotlist.
584          *
585          * Activating a button using its hotkey (and even pressing ENTER, as
586          * there's a "default button") moves the focus to the button. But we
587          * want to stay on the hotlist, to be able to use the usual keys (up,
588          * down, etc.). So we do `widget_select (lst)`.
589          *
590          * (2) Refresh the hotlist.
591          *
592          * We may have run a command that changed the contents of the list.
593          * We therefore need to refresh it. So we do `widget_draw (lst)`.
594          */
595         {
596             Widget *lst;
597 
598             lst = WIDGET (h == hotlist_dlg ? l_hotlist : l_movelist);
599 
600             /* widget_select() already redraws the widget, but since it's a
601              * no-op if the widget is already selected ("focused"), we have
602              * to call widget_draw() separately. */
603             if (!widget_get_state (lst, WST_FOCUSED))
604                 widget_select (lst);
605             else
606                 widget_draw (lst);
607         }
608         return MSG_HANDLED;
609 
610     case MSG_RESIZE:
611         {
612             WRect r;
613 
614             rect_init (&r, w->y, w->x, LINES - (h == hotlist_dlg ? 2 : 6), COLS - 6);
615 
616             return dlg_default_callback (w, NULL, MSG_RESIZE, 0, &r);
617         }
618 
619     default:
620         return dlg_default_callback (w, sender, msg, parm, data);
621     }
622 }
623 
624 /* --------------------------------------------------------------------------------------------- */
625 
626 static lcback_ret_t
hotlist_listbox_callback(WListbox * list)627 hotlist_listbox_callback (WListbox * list)
628 {
629     WDialog *dlg = DIALOG (WIDGET (list)->owner);
630 
631     if (!listbox_is_empty (list))
632     {
633         void *data = NULL;
634 
635         listbox_get_current (list, NULL, &data);
636 
637         if (data != NULL)
638         {
639             struct hotlist *hlp = (struct hotlist *) data;
640 
641             if (hlp->type == HL_TYPE_ENTRY)
642             {
643                 dlg->ret_value = B_ENTER;
644                 dlg_stop (dlg);
645                 return LISTBOX_DONE;
646             }
647             else
648             {
649                 hotlist_button_callback (NULL, B_ENTER);
650                 send_message (dlg, NULL, MSG_POST_KEY, '\n', NULL);
651                 return LISTBOX_CONT;
652             }
653         }
654         else
655         {
656             dlg->ret_value = B_ENTER;
657             dlg_stop (dlg);
658             return LISTBOX_DONE;
659         }
660     }
661 
662     hotlist_button_callback (NULL, B_UP_GROUP);
663     send_message (dlg, NULL, MSG_POST_KEY, 'u', NULL);
664     return LISTBOX_CONT;
665 }
666 
667 /* --------------------------------------------------------------------------------------------- */
668 /**
669  * Expands all button names (once) and recalculates button positions.
670  * returns number of columns in the dialog box, which is 10 chars longer
671  * then buttonbar.
672  *
673  * If common width of the window (i.e. in xterm) is less than returned
674  * width - sorry :)  (anyway this did not handled in previous version too)
675  */
676 
677 static int
init_i18n_stuff(int list_type,int cols)678 init_i18n_stuff (int list_type, int cols)
679 {
680     size_t i;
681 
682     static gboolean i18n_flag = FALSE;
683 
684     if (!i18n_flag)
685     {
686         for (i = 0; i < hotlist_but_num; i++)
687         {
688 #ifdef ENABLE_NLS
689             hotlist_but[i].text = _(hotlist_but[i].text);
690 #endif /* ENABLE_NLS */
691             hotlist_but[i].len = str_term_width1 (hotlist_but[i].text) + 3;
692             if (hotlist_but[i].flags == DEFPUSH_BUTTON)
693                 hotlist_but[i].len += 2;
694         }
695 
696         i18n_flag = TRUE;
697     }
698 
699     /* Dynamic resizing of buttonbars */
700     {
701         int len[2], count[2];   /* at most two lines of buttons */
702         int cur_x[2];
703 
704         len[0] = len[1] = 0;
705         count[0] = count[1] = 0;
706         cur_x[0] = cur_x[1] = 0;
707 
708         /* Count len of buttonbars, assuming 1 extra space between buttons */
709         for (i = 0; i < hotlist_but_num; i++)
710             if ((hotlist_but[i].type & list_type) != 0)
711             {
712                 int row;
713 
714                 row = hotlist_but[i].y;
715                 ++count[row];
716                 len[row] += hotlist_but[i].len + 1;
717             }
718 
719         (len[0])--;
720         (len[1])--;
721 
722         cols = MAX (cols, MAX (len[0], len[1]));
723 
724         /* arrange buttons */
725         for (i = 0; i < hotlist_but_num; i++)
726             if ((hotlist_but[i].type & list_type) != 0)
727             {
728                 int row;
729 
730                 row = hotlist_but[i].y;
731 
732                 if (hotlist_but[i].x != 0)
733                 {
734                     /* not first int the row */
735                     if (hotlist_but[i].ret_cmd == B_CANCEL)
736                         hotlist_but[i].x = cols - hotlist_but[i].len - 6;
737                     else
738                         hotlist_but[i].x = cur_x[row];
739                 }
740 
741                 cur_x[row] += hotlist_but[i].len + 1;
742             }
743     }
744 
745     return cols;
746 }
747 
748 /* --------------------------------------------------------------------------------------------- */
749 
750 static void
init_hotlist(hotlist_t list_type)751 init_hotlist (hotlist_t list_type)
752 {
753     size_t i;
754     const char *title, *help_node;
755     int lines, cols;
756     int y;
757     int dh = 0;
758     WGroup *g;
759     WGroupbox *path_box;
760     Widget *hotlist_widget;
761 
762     do_refresh ();
763 
764     lines = LINES - 2;
765     cols = init_i18n_stuff (list_type, COLS - 6);
766 
767 #ifdef ENABLE_VFS
768     if (list_type == LIST_VFSLIST)
769     {
770         title = _("Active VFS directories");
771         help_node = "[vfshot]"; /* FIXME - no such node */
772         dh = 1;
773     }
774     else
775 #endif /* !ENABLE_VFS */
776     {
777         title = _("Directory hotlist");
778         help_node = "[Hotlist]";
779     }
780 
781     hotlist_dlg =
782         dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
783                     NULL, help_node, title);
784     g = GROUP (hotlist_dlg);
785 
786     y = UY;
787     hotlist_group = groupbox_new (y, UX, lines - 10 + dh, cols - 2 * UX, _("Top level group"));
788     hotlist_widget = WIDGET (hotlist_group);
789     group_add_widget_autopos (g, hotlist_widget, WPOS_KEEP_ALL, NULL);
790 
791     l_hotlist =
792         listbox_new (y + 1, UX + 1, hotlist_widget->lines - 2, hotlist_widget->cols - 2, FALSE,
793                      hotlist_listbox_callback);
794 
795     /* Fill the hotlist with the active VFS or the hotlist */
796 #ifdef ENABLE_VFS
797     if (list_type == LIST_VFSLIST)
798     {
799         listbox_add_item (l_hotlist, LISTBOX_APPEND_AT_END, 0, mc_config_get_home_dir (), NULL,
800                           FALSE);
801         vfs_fill_names (add_name_to_list);
802     }
803     else
804 #endif /* !ENABLE_VFS */
805         fill_listbox (l_hotlist);
806 
807     /* insert before groupbox to view scrollbar */
808     group_add_widget_autopos (g, l_hotlist, WPOS_KEEP_ALL, NULL);
809 
810     y += hotlist_widget->lines;
811 
812     path_box = groupbox_new (y, UX, 3, hotlist_widget->cols, _("Directory path"));
813     group_add_widget_autopos (g, path_box, WPOS_KEEP_BOTTOM | WPOS_KEEP_HORZ, NULL);
814 
815     pname = label_new (y + 1, UX + 2, "");
816     group_add_widget_autopos (g, pname, WPOS_KEEP_BOTTOM | WPOS_KEEP_LEFT, NULL);
817     y += WIDGET (path_box)->lines;
818 
819     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
820 
821     for (i = 0; i < hotlist_but_num; i++)
822         if ((hotlist_but[i].type & list_type) != 0)
823             group_add_widget_autopos (g,
824                                       button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
825                                                   hotlist_but[i].ret_cmd, hotlist_but[i].flags,
826                                                   hotlist_but[i].text, hotlist_button_callback),
827                                       hotlist_but[i].pos_flags, NULL);
828 
829     widget_select (WIDGET (l_hotlist));
830 }
831 
832 /* --------------------------------------------------------------------------------------------- */
833 
834 static void
init_movelist(struct hotlist * item)835 init_movelist (struct hotlist *item)
836 {
837     size_t i;
838     char *hdr;
839     int lines, cols;
840     int y;
841     WGroup *g;
842     Widget *movelist_widget;
843 
844     do_refresh ();
845 
846     lines = LINES - 6;
847     cols = init_i18n_stuff (LIST_MOVELIST, COLS - 6);
848 
849     hdr = g_strdup_printf (_("Moving %s"), item->label);
850 
851     movelist_dlg =
852         dlg_create (TRUE, 0, 0, lines, cols, WPOS_CENTER, FALSE, dialog_colors, hotlist_callback,
853                     NULL, "[Hotlist]", hdr);
854     g = GROUP (movelist_dlg);
855 
856     g_free (hdr);
857 
858     y = UY;
859     movelist_group = groupbox_new (y, UX, lines - 7, cols - 2 * UX, _("Directory label"));
860     movelist_widget = WIDGET (movelist_group);
861     group_add_widget_autopos (g, movelist_widget, WPOS_KEEP_ALL, NULL);
862 
863     l_movelist =
864         listbox_new (y + 1, UX + 1, movelist_widget->lines - 2, movelist_widget->cols - 2, FALSE,
865                      hotlist_listbox_callback);
866     fill_listbox (l_movelist);
867     /* insert before groupbox to view scrollbar */
868     group_add_widget_autopos (g, l_movelist, WPOS_KEEP_ALL, NULL);
869 
870     y += movelist_widget->lines;
871 
872     group_add_widget_autopos (g, hline_new (y++, -1, -1), WPOS_KEEP_BOTTOM, NULL);
873 
874     for (i = 0; i < hotlist_but_num; i++)
875         if ((hotlist_but[i].type & LIST_MOVELIST) != 0)
876             group_add_widget_autopos (g,
877                                       button_new (y + hotlist_but[i].y, UX + hotlist_but[i].x,
878                                                   hotlist_but[i].ret_cmd, hotlist_but[i].flags,
879                                                   hotlist_but[i].text, hotlist_button_callback),
880                                       hotlist_but[i].pos_flags, NULL);
881 
882     widget_select (WIDGET (l_movelist));
883 }
884 
885 /* --------------------------------------------------------------------------------------------- */
886 /**
887  * Destroy the list dialog.
888  * Don't confuse with done_hotlist() for the list in memory.
889  */
890 
891 static void
hotlist_done(void)892 hotlist_done (void)
893 {
894     widget_destroy (WIDGET (hotlist_dlg));
895     l_hotlist = NULL;
896 #if 0
897     update_panels (UP_OPTIMIZE, UP_KEEPSEL);
898 #endif
899     repaint_screen ();
900 }
901 
902 /* --------------------------------------------------------------------------------------------- */
903 
904 static inline char *
find_group_section(struct hotlist * grp)905 find_group_section (struct hotlist *grp)
906 {
907     return g_strconcat (grp->directory, ".Group", (char *) NULL);
908 }
909 
910 /* --------------------------------------------------------------------------------------------- */
911 
912 static struct hotlist *
add2hotlist(char * label,char * directory,enum HotListType type,listbox_append_t pos)913 add2hotlist (char *label, char *directory, enum HotListType type, listbox_append_t pos)
914 {
915     struct hotlist *new;
916     struct hotlist *current = NULL;
917 
918     /*
919      * Hotlist is neither loaded nor loading.
920      * Must be called by "Ctrl-x a" before using hotlist.
921      */
922     if (current_group == NULL)
923         load_hotlist ();
924 
925     listbox_get_current (l_hotlist, NULL, (void **) &current);
926 
927     /* Make sure '..' stays at the top of the list. */
928     if ((current != NULL) && (current->type == HL_TYPE_DOTDOT))
929         pos = LISTBOX_APPEND_AFTER;
930 
931     new = g_new0 (struct hotlist, 1);
932 
933     new->type = type;
934     new->label = label;
935     new->directory = directory;
936     new->up = current_group;
937 
938     if (type == HL_TYPE_GROUP)
939     {
940         current_group = new;
941         add_dotdot_to_list ();
942         current_group = new->up;
943     }
944 
945     if (current_group->head == NULL)
946     {
947         /* first element in group */
948         current_group->head = new;
949     }
950     else if (pos == LISTBOX_APPEND_AFTER)
951     {
952         new->next = current->next;
953         current->next = new;
954     }
955     else if (pos == LISTBOX_APPEND_BEFORE && current == current_group->head)
956     {
957         /* should be inserted before first item */
958         new->next = current;
959         current_group->head = new;
960     }
961     else if (pos == LISTBOX_APPEND_BEFORE)
962     {
963         struct hotlist *p = current_group->head;
964 
965         while (p->next != current)
966             p = p->next;
967 
968         new->next = current;
969         p->next = new;
970     }
971     else
972     {                           /* append at the end */
973         struct hotlist *p = current_group->head;
974 
975         while (p->next != NULL)
976             p = p->next;
977 
978         p->next = new;
979     }
980 
981     if (hotlist_state.running && type != HL_TYPE_COMMENT && type != HL_TYPE_DOTDOT)
982     {
983         if (type == HL_TYPE_GROUP)
984         {
985             char *lbl;
986 
987             lbl = g_strconcat ("->", new->label, (char *) NULL);
988             listbox_add_item (l_hotlist, pos, 0, lbl, new, FALSE);
989             g_free (lbl);
990         }
991         else
992             listbox_add_item (l_hotlist, pos, 0, new->label, new, FALSE);
993         listbox_select_entry (l_hotlist, l_hotlist->pos);
994     }
995 
996     return new;
997 }
998 
999 /* --------------------------------------------------------------------------------------------- */
1000 
1001 static int
add_new_entry_input(const char * header,const char * text1,const char * text2,const char * help,char ** r1,char ** r2)1002 add_new_entry_input (const char *header, const char *text1, const char *text2,
1003                      const char *help, char **r1, char **r2)
1004 {
1005     quick_widget_t quick_widgets[] = {
1006         /* *INDENT-OFF* */
1007         QUICK_LABELED_INPUT (text1, input_label_above, *r1, "input-lbl", r1, NULL,
1008                              FALSE, FALSE, INPUT_COMPLETE_NONE),
1009         QUICK_SEPARATOR (FALSE),
1010         QUICK_LABELED_INPUT (text2, input_label_above, *r2, "input-lbl", r2, NULL,
1011                              FALSE, FALSE, INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD),
1012         QUICK_START_BUTTONS (TRUE, TRUE),
1013             QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
1014             QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
1015             QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1016         QUICK_END
1017         /* *INDENT-ON* */
1018     };
1019 
1020     quick_dialog_t qdlg = {
1021         -1, -1, 64,
1022         header, help,
1023         quick_widgets, NULL, NULL
1024     };
1025 
1026     int ret;
1027 
1028     ret = quick_dialog (&qdlg);
1029 
1030     return (ret != B_CANCEL) ? ret : 0;
1031 }
1032 
1033 /* --------------------------------------------------------------------------------------------- */
1034 
1035 static void
add_new_entry_cmd(WPanel * panel)1036 add_new_entry_cmd (WPanel * panel)
1037 {
1038     char *title, *url, *to_free;
1039     int ret;
1040 
1041     /* Take current directory as default value for input fields */
1042     to_free = title = url = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
1043 
1044     ret = add_new_entry_input (_("New hotlist entry"), _("Directory label:"),
1045                                _("Directory path:"), "[Hotlist]", &title, &url);
1046     g_free (to_free);
1047 
1048     if (ret == 0)
1049         return;
1050     if (title == NULL || *title == '\0' || url == NULL || *url == '\0')
1051     {
1052         g_free (title);
1053         g_free (url);
1054         return;
1055     }
1056 
1057     if (ret == B_ENTER || ret == B_APPEND)
1058         add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AFTER);
1059     else
1060         add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_BEFORE);
1061 
1062     hotlist_state.modified = TRUE;
1063 }
1064 
1065 /* --------------------------------------------------------------------------------------------- */
1066 
1067 static int
add_new_group_input(const char * header,const char * label,char ** result)1068 add_new_group_input (const char *header, const char *label, char **result)
1069 {
1070     quick_widget_t quick_widgets[] = {
1071         /* *INDENT-OFF* */
1072         QUICK_LABELED_INPUT (label, input_label_above, "", "input", result, NULL,
1073                              FALSE, FALSE, INPUT_COMPLETE_NONE),
1074         QUICK_START_BUTTONS (TRUE, TRUE),
1075             QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
1076             QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
1077             QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1078         QUICK_END
1079         /* *INDENT-ON* */
1080     };
1081 
1082     quick_dialog_t qdlg = {
1083         -1, -1, 64,
1084         header, "[Hotlist]",
1085         quick_widgets, NULL, NULL
1086     };
1087 
1088     int ret;
1089 
1090     ret = quick_dialog (&qdlg);
1091 
1092     return (ret != B_CANCEL) ? ret : 0;
1093 }
1094 
1095 /* --------------------------------------------------------------------------------------------- */
1096 
1097 static void
add_new_group_cmd(void)1098 add_new_group_cmd (void)
1099 {
1100     char *label;
1101     int ret;
1102 
1103     ret = add_new_group_input (_("New hotlist group"), _("Name of new group:"), &label);
1104     if (ret == 0 || label == NULL || *label == '\0')
1105         return;
1106 
1107     if (ret == B_ENTER || ret == B_APPEND)
1108         add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_AFTER);
1109     else
1110         add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_BEFORE);
1111 
1112     hotlist_state.modified = TRUE;
1113 }
1114 
1115 /* --------------------------------------------------------------------------------------------- */
1116 
1117 static void
remove_group(struct hotlist * grp)1118 remove_group (struct hotlist *grp)
1119 {
1120     struct hotlist *current = grp->head;
1121 
1122     while (current != NULL)
1123     {
1124         struct hotlist *next = current->next;
1125 
1126         if (current->type == HL_TYPE_GROUP)
1127             remove_group (current);
1128 
1129         g_free (current->label);
1130         g_free (current->directory);
1131         g_free (current);
1132 
1133         current = next;
1134     }
1135 }
1136 
1137 /* --------------------------------------------------------------------------------------------- */
1138 
1139 static void
remove_from_hotlist(struct hotlist * entry)1140 remove_from_hotlist (struct hotlist *entry)
1141 {
1142     if (entry == NULL)
1143         return;
1144 
1145     if (entry->type == HL_TYPE_DOTDOT)
1146         return;
1147 
1148     if (confirm_directory_hotlist_delete)
1149     {
1150         char text[BUF_MEDIUM];
1151         int result;
1152 
1153         if (safe_delete)
1154             query_set_sel (1);
1155 
1156         g_snprintf (text, sizeof (text), _("Are you sure you want to remove entry \"%s\"?"),
1157                     str_trunc (entry->label, 30));
1158         result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
1159                                _("&Yes"), _("&No"));
1160         if (result != 0)
1161             return;
1162     }
1163 
1164     if (entry->type == HL_TYPE_GROUP)
1165     {
1166         struct hotlist *head = entry->head;
1167 
1168         if (head != NULL && (head->type != HL_TYPE_DOTDOT || head->next != NULL))
1169         {
1170             char text[BUF_MEDIUM];
1171             int result;
1172 
1173             g_snprintf (text, sizeof (text), _("Group \"%s\" is not empty.\nRemove it?"),
1174                         str_trunc (entry->label, 30));
1175             result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
1176                                    _("&Yes"), _("&No"));
1177             if (result != 0)
1178                 return;
1179         }
1180 
1181         remove_group (entry);
1182     }
1183 
1184     unlink_entry (entry);
1185 
1186     g_free (entry->label);
1187     g_free (entry->directory);
1188     g_free (entry);
1189     /* now remove list entry from screen */
1190     listbox_remove_current (l_hotlist);
1191     hotlist_state.modified = TRUE;
1192 }
1193 
1194 /* --------------------------------------------------------------------------------------------- */
1195 
1196 static void
load_group(struct hotlist * grp)1197 load_group (struct hotlist *grp)
1198 {
1199     gchar **profile_keys, **keys;
1200     char *group_section;
1201     struct hotlist *current = 0;
1202 
1203     group_section = find_group_section (grp);
1204 
1205     keys = mc_config_get_keys (mc_global.main_config, group_section, NULL);
1206 
1207     current_group = grp;
1208 
1209     for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
1210         add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
1211                      g_strdup (*profile_keys), HL_TYPE_GROUP, LISTBOX_APPEND_AT_END);
1212 
1213     g_strfreev (keys);
1214 
1215     keys = mc_config_get_keys (mc_global.main_config, grp->directory, NULL);
1216 
1217     for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
1218         add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
1219                      g_strdup (*profile_keys), HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1220 
1221     g_free (group_section);
1222     g_strfreev (keys);
1223 
1224     for (current = grp->head; current; current = current->next)
1225         load_group (current);
1226 }
1227 
1228 /* --------------------------------------------------------------------------------------------- */
1229 
1230 static int
hot_skip_blanks(void)1231 hot_skip_blanks (void)
1232 {
1233     int c;
1234 
1235     while ((c = getc (hotlist_file)) != EOF && c != '\n' && g_ascii_isspace (c))
1236         ;
1237     return c;
1238 }
1239 
1240 /* --------------------------------------------------------------------------------------------- */
1241 
1242 static int
hot_next_token(void)1243 hot_next_token (void)
1244 {
1245     int c, ret = 0;
1246     size_t l;
1247 
1248     if (tkn_buf == NULL)
1249         tkn_buf = g_string_new ("");
1250     g_string_set_size (tkn_buf, 0);
1251 
1252   again:
1253     c = hot_skip_blanks ();
1254     switch (c)
1255     {
1256     case EOF:
1257         ret = TKN_EOF;
1258         break;
1259     case '\n':
1260         ret = TKN_EOL;
1261         break;
1262     case '#':
1263         while ((c = getc (hotlist_file)) != EOF && c != '\n')
1264             g_string_append_c (tkn_buf, c);
1265         ret = TKN_COMMENT;
1266         break;
1267     case '"':
1268         while ((c = getc (hotlist_file)) != EOF && c != '"')
1269         {
1270             if (c == '\\')
1271             {
1272                 c = getc (hotlist_file);
1273                 if (c == EOF)
1274                 {
1275                     g_string_free (tkn_buf, TRUE);
1276                     return TKN_EOF;
1277                 }
1278             }
1279             g_string_append_c (tkn_buf, c == '\n' ? ' ' : c);
1280         }
1281         ret = (c == EOF) ? TKN_EOF : TKN_STRING;
1282         break;
1283     case '\\':
1284         c = getc (hotlist_file);
1285         if (c == EOF)
1286         {
1287             g_string_free (tkn_buf, TRUE);
1288             return TKN_EOF;
1289         }
1290         if (c == '\n')
1291             goto again;
1292 
1293         MC_FALLTHROUGH;         /* it is taken as normal character */
1294 
1295     default:
1296         do
1297         {
1298             g_string_append_c (tkn_buf, g_ascii_toupper (c));
1299         }
1300         while ((c = fgetc (hotlist_file)) != EOF && (g_ascii_isalnum (c) || !isascii (c)));
1301         if (c != EOF)
1302             ungetc (c, hotlist_file);
1303         l = tkn_buf->len;
1304         if (strncmp (tkn_buf->str, "GROUP", l) == 0)
1305             ret = TKN_GROUP;
1306         else if (strncmp (tkn_buf->str, "ENTRY", l) == 0)
1307             ret = TKN_ENTRY;
1308         else if (strncmp (tkn_buf->str, "ENDGROUP", l) == 0)
1309             ret = TKN_ENDGROUP;
1310         else if (strncmp (tkn_buf->str, "URL", l) == 0)
1311             ret = TKN_URL;
1312         else
1313             ret = TKN_UNKNOWN;
1314         break;
1315     }
1316     return ret;
1317 }
1318 
1319 /* --------------------------------------------------------------------------------------------- */
1320 
1321 static void
hot_load_group(struct hotlist * grp)1322 hot_load_group (struct hotlist *grp)
1323 {
1324     int tkn;
1325     struct hotlist *new_grp;
1326     char *label, *url;
1327 
1328     current_group = grp;
1329 
1330     while ((tkn = hot_next_token ()) != TKN_ENDGROUP)
1331         switch (tkn)
1332         {
1333         case TKN_GROUP:
1334             CHECK_TOKEN (TKN_STRING);
1335             new_grp =
1336                 add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
1337                              LISTBOX_APPEND_AT_END);
1338             SKIP_TO_EOL;
1339             hot_load_group (new_grp);
1340             current_group = grp;
1341             break;
1342         case TKN_ENTRY:
1343             {
1344                 CHECK_TOKEN (TKN_STRING);
1345                 label = g_strndup (tkn_buf->str, tkn_buf->len);
1346                 CHECK_TOKEN (TKN_URL);
1347                 CHECK_TOKEN (TKN_STRING);
1348                 url = tilde_expand (tkn_buf->str);
1349                 add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1350                 SKIP_TO_EOL;
1351             }
1352             break;
1353         case TKN_COMMENT:
1354             label = g_strndup (tkn_buf->str, tkn_buf->len);
1355             add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
1356             break;
1357         case TKN_EOF:
1358             hotlist_state.readonly = TRUE;
1359             hotlist_state.file_error = TRUE;
1360             return;
1361         case TKN_EOL:
1362             /* skip empty lines */
1363             break;
1364         default:
1365             hotlist_state.readonly = TRUE;
1366             hotlist_state.file_error = TRUE;
1367             SKIP_TO_EOL;
1368             break;
1369         }
1370     SKIP_TO_EOL;
1371 }
1372 
1373 /* --------------------------------------------------------------------------------------------- */
1374 
1375 static void
hot_load_file(struct hotlist * grp)1376 hot_load_file (struct hotlist *grp)
1377 {
1378     int tkn;
1379     struct hotlist *new_grp;
1380     char *label, *url;
1381 
1382     current_group = grp;
1383 
1384     while ((tkn = hot_next_token ()) != TKN_EOF)
1385         switch (tkn)
1386         {
1387         case TKN_GROUP:
1388             CHECK_TOKEN (TKN_STRING);
1389             new_grp =
1390                 add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
1391                              LISTBOX_APPEND_AT_END);
1392             SKIP_TO_EOL;
1393             hot_load_group (new_grp);
1394             current_group = grp;
1395             break;
1396         case TKN_ENTRY:
1397             {
1398                 CHECK_TOKEN (TKN_STRING);
1399                 label = g_strndup (tkn_buf->str, tkn_buf->len);
1400                 CHECK_TOKEN (TKN_URL);
1401                 CHECK_TOKEN (TKN_STRING);
1402                 url = tilde_expand (tkn_buf->str);
1403                 add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1404                 SKIP_TO_EOL;
1405             }
1406             break;
1407         case TKN_COMMENT:
1408             label = g_strndup (tkn_buf->str, tkn_buf->len);
1409             add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
1410             break;
1411         case TKN_EOL:
1412             /* skip empty lines */
1413             break;
1414         default:
1415             hotlist_state.readonly = TRUE;
1416             hotlist_state.file_error = TRUE;
1417             SKIP_TO_EOL;
1418             break;
1419         }
1420 }
1421 
1422 /* --------------------------------------------------------------------------------------------- */
1423 
1424 static void
clean_up_hotlist_groups(const char * section)1425 clean_up_hotlist_groups (const char *section)
1426 {
1427     char *grp_section;
1428 
1429     grp_section = g_strconcat (section, ".Group", (char *) NULL);
1430     if (mc_config_has_group (mc_global.main_config, section))
1431         mc_config_del_group (mc_global.main_config, section);
1432 
1433     if (mc_config_has_group (mc_global.main_config, grp_section))
1434     {
1435         char **profile_keys, **keys;
1436 
1437         keys = mc_config_get_keys (mc_global.main_config, grp_section, NULL);
1438 
1439         for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
1440             clean_up_hotlist_groups (*profile_keys);
1441 
1442         g_strfreev (keys);
1443         mc_config_del_group (mc_global.main_config, grp_section);
1444     }
1445     g_free (grp_section);
1446 }
1447 
1448 /* --------------------------------------------------------------------------------------------- */
1449 
1450 static void
load_hotlist(void)1451 load_hotlist (void)
1452 {
1453     gboolean remove_old_list = FALSE;
1454     struct stat stat_buf;
1455 
1456     if (hotlist_state.loaded)
1457     {
1458         stat (hotlist_file_name, &stat_buf);
1459         if (hotlist_file_mtime < stat_buf.st_mtime)
1460             done_hotlist ();
1461         else
1462             return;
1463     }
1464 
1465     if (hotlist_file_name == NULL)
1466         hotlist_file_name = mc_config_get_full_path (MC_HOTLIST_FILE);
1467 
1468     hotlist = g_new0 (struct hotlist, 1);
1469     hotlist->type = HL_TYPE_GROUP;
1470     hotlist->label = g_strdup (_("Top level group"));
1471     hotlist->up = hotlist;
1472     /*
1473      * compatibility :-(
1474      */
1475     hotlist->directory = g_strdup ("Hotlist");
1476 
1477     hotlist_file = fopen (hotlist_file_name, "r");
1478     if (hotlist_file == NULL)
1479     {
1480         int result;
1481 
1482         load_group (hotlist);
1483         hotlist_state.loaded = TRUE;
1484         /*
1485          * just to be sure we got copy
1486          */
1487         hotlist_state.modified = TRUE;
1488         result = save_hotlist ();
1489         hotlist_state.modified = FALSE;
1490         if (result != 0)
1491             remove_old_list = TRUE;
1492         else
1493             message (D_ERROR, _("Hotlist Load"),
1494                      _
1495                      ("MC was unable to write %s file,\nyour old hotlist entries were not deleted"),
1496                      MC_USERCONF_DIR PATH_SEP_STR MC_HOTLIST_FILE);
1497     }
1498     else
1499     {
1500         hot_load_file (hotlist);
1501         fclose (hotlist_file);
1502         hotlist_state.loaded = TRUE;
1503     }
1504 
1505     if (remove_old_list)
1506     {
1507         GError *mcerror = NULL;
1508 
1509         clean_up_hotlist_groups ("Hotlist");
1510         if (!mc_config_save_file (mc_global.main_config, &mcerror))
1511             setup_save_config_show_error (mc_global.main_config->ini_path, &mcerror);
1512 
1513         mc_error_message (&mcerror, NULL);
1514     }
1515 
1516     stat (hotlist_file_name, &stat_buf);
1517     hotlist_file_mtime = stat_buf.st_mtime;
1518     current_group = hotlist;
1519 }
1520 
1521 /* --------------------------------------------------------------------------------------------- */
1522 
1523 static void
hot_save_group(struct hotlist * grp)1524 hot_save_group (struct hotlist *grp)
1525 {
1526     struct hotlist *current;
1527     int i;
1528     char *s;
1529 
1530 #define INDENT(n) \
1531 do { \
1532     for (i = 0; i < n; i++) \
1533         putc (' ', hotlist_file); \
1534 } while (0)
1535 
1536     for (current = grp->head; current != NULL; current = current->next)
1537         switch (current->type)
1538         {
1539         case HL_TYPE_GROUP:
1540             INDENT (list_level);
1541             fputs ("GROUP \"", hotlist_file);
1542             for (s = current->label; *s != '\0'; s++)
1543             {
1544                 if (*s == '"' || *s == '\\')
1545                     putc ('\\', hotlist_file);
1546                 putc (*s, hotlist_file);
1547             }
1548             fputs ("\"\n", hotlist_file);
1549             list_level += 2;
1550             hot_save_group (current);
1551             list_level -= 2;
1552             INDENT (list_level);
1553             fputs ("ENDGROUP\n", hotlist_file);
1554             break;
1555         case HL_TYPE_ENTRY:
1556             INDENT (list_level);
1557             fputs ("ENTRY \"", hotlist_file);
1558             for (s = current->label; *s != '\0'; s++)
1559             {
1560                 if (*s == '"' || *s == '\\')
1561                     putc ('\\', hotlist_file);
1562                 putc (*s, hotlist_file);
1563             }
1564             fputs ("\" URL \"", hotlist_file);
1565             for (s = current->directory; *s != '\0'; s++)
1566             {
1567                 if (*s == '"' || *s == '\\')
1568                     putc ('\\', hotlist_file);
1569                 putc (*s, hotlist_file);
1570             }
1571             fputs ("\"\n", hotlist_file);
1572             break;
1573         case HL_TYPE_COMMENT:
1574             fprintf (hotlist_file, "#%s\n", current->label);
1575             break;
1576         case HL_TYPE_DOTDOT:
1577             /* do nothing */
1578             break;
1579         default:
1580             break;
1581         }
1582 }
1583 
1584 /* --------------------------------------------------------------------------------------------- */
1585 
1586 static void
add_dotdot_to_list(void)1587 add_dotdot_to_list (void)
1588 {
1589     if (current_group != hotlist && hotlist_has_dot_dot)
1590         add2hotlist (g_strdup (".."), g_strdup (".."), HL_TYPE_DOTDOT, LISTBOX_APPEND_AT_END);
1591 }
1592 
1593 /* --------------------------------------------------------------------------------------------- */
1594 /*** public functions ****************************************************************************/
1595 /* --------------------------------------------------------------------------------------------- */
1596 
1597 void
add2hotlist_cmd(WPanel * panel)1598 add2hotlist_cmd (WPanel * panel)
1599 {
1600     char *lc_prompt;
1601     const char *cp = N_("Label for \"%s\":");
1602     int l;
1603     char *label_string, *label;
1604 
1605 #ifdef ENABLE_NLS
1606     cp = _(cp);
1607 #endif
1608 
1609     /* extra variable to use it in the button callback */
1610     our_panel = panel;
1611 
1612     l = str_term_width1 (cp);
1613     label_string = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
1614     lc_prompt = g_strdup_printf (cp, str_trunc (label_string, COLS - 2 * UX - (l + 8)));
1615     label =
1616         input_dialog (_("Add to hotlist"), lc_prompt, MC_HISTORY_HOTLIST_ADD, label_string,
1617                       INPUT_COMPLETE_NONE);
1618     g_free (lc_prompt);
1619 
1620     if (label == NULL || *label == '\0')
1621     {
1622         g_free (label_string);
1623         g_free (label);
1624     }
1625     else
1626     {
1627         add2hotlist (label, label_string, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1628         hotlist_state.modified = TRUE;
1629     }
1630 }
1631 
1632 /* --------------------------------------------------------------------------------------------- */
1633 
1634 char *
hotlist_show(hotlist_t list_type,WPanel * panel)1635 hotlist_show (hotlist_t list_type, WPanel * panel)
1636 {
1637     char *target = NULL;
1638     int res;
1639 
1640     /* extra variable to use it in the button callback */
1641     our_panel = panel;
1642 
1643     hotlist_state.type = list_type;
1644     load_hotlist ();
1645 
1646     init_hotlist (list_type);
1647 
1648     /* display file info */
1649     tty_setcolor (SELECTED_COLOR);
1650 
1651     hotlist_state.running = TRUE;
1652     res = dlg_run (hotlist_dlg);
1653     hotlist_state.running = FALSE;
1654     save_hotlist ();
1655 
1656     if (res == B_ENTER)
1657     {
1658         char *text = NULL;
1659         struct hotlist *hlp = NULL;
1660 
1661         listbox_get_current (l_hotlist, &text, (void **) &hlp);
1662         target = g_strdup (hlp != NULL ? hlp->directory : text);
1663     }
1664 
1665     hotlist_done ();
1666     return target;
1667 }
1668 
1669 /* --------------------------------------------------------------------------------------------- */
1670 
1671 gboolean
save_hotlist(void)1672 save_hotlist (void)
1673 {
1674     gboolean saved = FALSE;
1675     struct stat stat_buf;
1676 
1677     if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name != NULL)
1678     {
1679         mc_util_make_backup_if_possible (hotlist_file_name, ".bak");
1680 
1681         hotlist_file = fopen (hotlist_file_name, "w");
1682         if (hotlist_file == NULL)
1683             mc_util_restore_from_backup_if_possible (hotlist_file_name, ".bak");
1684         else
1685         {
1686             hot_save_group (hotlist);
1687             fclose (hotlist_file);
1688             stat (hotlist_file_name, &stat_buf);
1689             hotlist_file_mtime = stat_buf.st_mtime;
1690             hotlist_state.modified = FALSE;
1691             saved = TRUE;
1692         }
1693     }
1694 
1695     return saved;
1696 }
1697 
1698 /* --------------------------------------------------------------------------------------------- */
1699 /**
1700  * Unload list from memory.
1701  * Don't confuse with hotlist_done() for GUI.
1702  */
1703 
1704 void
done_hotlist(void)1705 done_hotlist (void)
1706 {
1707     if (hotlist != NULL)
1708     {
1709         remove_group (hotlist);
1710         g_free (hotlist->label);
1711         g_free (hotlist->directory);
1712         MC_PTR_FREE (hotlist);
1713     }
1714 
1715     hotlist_state.loaded = FALSE;
1716 
1717     MC_PTR_FREE (hotlist_file_name);
1718     l_hotlist = NULL;
1719     current_group = NULL;
1720 
1721     if (tkn_buf != NULL)
1722     {
1723         g_string_free (tkn_buf, TRUE);
1724         tkn_buf = NULL;
1725     }
1726 }
1727 
1728 /* --------------------------------------------------------------------------------------------- */
1729