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

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