root/src/filemanager/hotlist.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. update_path_name
  2. fill_listbox
  3. unlink_entry
  4. add_name_to_list
  5. hotlist_run_cmd
  6. hotlist_button_callback
  7. hotlist_handle_key
  8. hotlist_callback
  9. hotlist_listbox_callback
  10. init_i18n_stuff
  11. init_hotlist
  12. init_movelist
  13. hotlist_done
  14. find_group_section
  15. add2hotlist
  16. add_new_entry_input
  17. add_new_entry_cmd
  18. add_new_group_input
  19. add_new_group_cmd
  20. remove_group
  21. remove_from_hotlist
  22. load_group
  23. hot_skip_blanks
  24. hot_next_token
  25. hot_load_group
  26. hot_load_file
  27. clean_up_hotlist_groups
  28. load_hotlist
  29. hot_save_group
  30. add_dotdot_to_list
  31. add2hotlist_cmd
  32. hotlist_show
  33. save_hotlist
  34. done_hotlist

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

/* [previous][next][first][last][top][bottom][index][help]  */