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

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