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-2024
   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, char **r1, char **r2)
 999 {
1000     quick_widget_t quick_widgets[] = {
1001         /* *INDENT-OFF* */
1002         QUICK_LABELED_INPUT (text1, input_label_above, *r1, "input-lbl", r1, NULL,
1003                              FALSE, FALSE, INPUT_COMPLETE_NONE),
1004         QUICK_SEPARATOR (FALSE),
1005         QUICK_LABELED_INPUT (text2, input_label_above, *r2, "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     int ret;
1023 
1024     ret = quick_dialog (&qdlg);
1025 
1026     return (ret != B_CANCEL) ? ret : 0;
1027 }
1028 
1029 /* --------------------------------------------------------------------------------------------- */
1030 
1031 static void
1032 add_new_entry_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1033 {
1034     char *title, *url, *to_free;
1035     int ret;
1036 
1037     /* Take current directory as default value for input fields */
1038     to_free = title = url = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
1039 
1040     ret = add_new_entry_input (_("New hotlist entry"), _("Directory label:"),
1041                                _("Directory path:"), "[Hotlist]", &title, &url);
1042     g_free (to_free);
1043 
1044     if (ret == 0)
1045         return;
1046     if (title == NULL || *title == '\0' || url == NULL || *url == '\0')
1047     {
1048         g_free (title);
1049         g_free (url);
1050         return;
1051     }
1052 
1053     if (ret == B_ENTER || ret == B_APPEND)
1054         add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AFTER);
1055     else
1056         add2hotlist (title, url, HL_TYPE_ENTRY, LISTBOX_APPEND_BEFORE);
1057 
1058     hotlist_state.modified = TRUE;
1059 }
1060 
1061 /* --------------------------------------------------------------------------------------------- */
1062 
1063 static int
1064 add_new_group_input (const char *header, const char *label, char **result)
     /* [previous][next][first][last][top][bottom][index][help]  */
1065 {
1066     quick_widget_t quick_widgets[] = {
1067         /* *INDENT-OFF* */
1068         QUICK_LABELED_INPUT (label, input_label_above, "", "input", result, NULL,
1069                              FALSE, FALSE, INPUT_COMPLETE_NONE),
1070         QUICK_START_BUTTONS (TRUE, TRUE),
1071             QUICK_BUTTON (N_("&Append"), B_APPEND, NULL, NULL),
1072             QUICK_BUTTON (N_("&Insert"), B_INSERT, NULL, NULL),
1073             QUICK_BUTTON (N_("&Cancel"), B_CANCEL, NULL, NULL),
1074         QUICK_END
1075         /* *INDENT-ON* */
1076     };
1077 
1078     WRect r = { -1, -1, 0, 64 };
1079 
1080     quick_dialog_t qdlg = {
1081         r, header, "[Hotlist]",
1082         quick_widgets, NULL, NULL
1083     };
1084 
1085     int ret;
1086 
1087     ret = quick_dialog (&qdlg);
1088 
1089     return (ret != B_CANCEL) ? ret : 0;
1090 }
1091 
1092 /* --------------------------------------------------------------------------------------------- */
1093 
1094 static void
1095 add_new_group_cmd (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1096 {
1097     char *label;
1098     int ret;
1099 
1100     ret = add_new_group_input (_("New hotlist group"), _("Name of new group:"), &label);
1101     if (ret == 0 || label == NULL || *label == '\0')
1102         return;
1103 
1104     if (ret == B_ENTER || ret == B_APPEND)
1105         add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_AFTER);
1106     else
1107         add2hotlist (label, 0, HL_TYPE_GROUP, LISTBOX_APPEND_BEFORE);
1108 
1109     hotlist_state.modified = TRUE;
1110 }
1111 
1112 /* --------------------------------------------------------------------------------------------- */
1113 
1114 static void
1115 remove_group (struct hotlist *grp)
     /* [previous][next][first][last][top][bottom][index][help]  */
1116 {
1117     struct hotlist *current = grp->head;
1118 
1119     while (current != NULL)
1120     {
1121         struct hotlist *next = current->next;
1122 
1123         if (current->type == HL_TYPE_GROUP)
1124             remove_group (current);
1125 
1126         g_free (current->label);
1127         g_free (current->directory);
1128         g_free (current);
1129 
1130         current = next;
1131     }
1132 }
1133 
1134 /* --------------------------------------------------------------------------------------------- */
1135 
1136 static void
1137 remove_from_hotlist (struct hotlist *entry)
     /* [previous][next][first][last][top][bottom][index][help]  */
1138 {
1139     if (entry == NULL)
1140         return;
1141 
1142     if (entry->type == HL_TYPE_DOTDOT)
1143         return;
1144 
1145     if (confirm_directory_hotlist_delete)
1146     {
1147         char text[BUF_MEDIUM];
1148         int result;
1149 
1150         if (safe_delete)
1151             query_set_sel (1);
1152 
1153         g_snprintf (text, sizeof (text), _("Are you sure you want to remove entry \"%s\"?"),
1154                     str_trunc (entry->label, 30));
1155         result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
1156                                _("&Yes"), _("&No"));
1157         if (result != 0)
1158             return;
1159     }
1160 
1161     if (entry->type == HL_TYPE_GROUP)
1162     {
1163         struct hotlist *head = entry->head;
1164 
1165         if (head != NULL && (head->type != HL_TYPE_DOTDOT || head->next != NULL))
1166         {
1167             char text[BUF_MEDIUM];
1168             int result;
1169 
1170             g_snprintf (text, sizeof (text), _("Group \"%s\" is not empty.\nRemove it?"),
1171                         str_trunc (entry->label, 30));
1172             result = query_dialog (Q_ ("DialogTitle|Delete"), text, D_ERROR | D_CENTER, 2,
1173                                    _("&Yes"), _("&No"));
1174             if (result != 0)
1175                 return;
1176         }
1177 
1178         remove_group (entry);
1179     }
1180 
1181     unlink_entry (entry);
1182 
1183     g_free (entry->label);
1184     g_free (entry->directory);
1185     g_free (entry);
1186     /* now remove list entry from screen */
1187     listbox_remove_current (l_hotlist);
1188     hotlist_state.modified = TRUE;
1189 }
1190 
1191 /* --------------------------------------------------------------------------------------------- */
1192 
1193 static void
1194 load_group (struct hotlist *grp)
     /* [previous][next][first][last][top][bottom][index][help]  */
1195 {
1196     gchar **profile_keys, **keys;
1197     char *group_section;
1198     struct hotlist *current = 0;
1199 
1200     group_section = find_group_section (grp);
1201 
1202     keys = mc_config_get_keys (mc_global.main_config, group_section, NULL);
1203 
1204     current_group = grp;
1205 
1206     for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
1207         add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
1208                      g_strdup (*profile_keys), HL_TYPE_GROUP, LISTBOX_APPEND_AT_END);
1209 
1210     g_strfreev (keys);
1211 
1212     keys = mc_config_get_keys (mc_global.main_config, grp->directory, NULL);
1213 
1214     for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
1215         add2hotlist (mc_config_get_string (mc_global.main_config, group_section, *profile_keys, ""),
1216                      g_strdup (*profile_keys), HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1217 
1218     g_free (group_section);
1219     g_strfreev (keys);
1220 
1221     for (current = grp->head; current; current = current->next)
1222         load_group (current);
1223 }
1224 
1225 /* --------------------------------------------------------------------------------------------- */
1226 
1227 static int
1228 hot_skip_blanks (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1229 {
1230     int c;
1231 
1232     while ((c = getc (hotlist_file)) != EOF && c != '\n' && g_ascii_isspace (c))
1233         ;
1234     return c;
1235 }
1236 
1237 /* --------------------------------------------------------------------------------------------- */
1238 
1239 static int
1240 hot_next_token (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1241 {
1242     int c, ret = 0;
1243     size_t l;
1244 
1245     if (tkn_buf == NULL)
1246         tkn_buf = g_string_new ("");
1247     g_string_set_size (tkn_buf, 0);
1248 
1249   again:
1250     c = hot_skip_blanks ();
1251     switch (c)
1252     {
1253     case EOF:
1254         ret = TKN_EOF;
1255         break;
1256     case '\n':
1257         ret = TKN_EOL;
1258         break;
1259     case '#':
1260         while ((c = getc (hotlist_file)) != EOF && c != '\n')
1261             g_string_append_c (tkn_buf, c);
1262         ret = TKN_COMMENT;
1263         break;
1264     case '"':
1265         while ((c = getc (hotlist_file)) != EOF && c != '"')
1266         {
1267             if (c == '\\')
1268             {
1269                 c = getc (hotlist_file);
1270                 if (c == EOF)
1271                 {
1272                     g_string_free (tkn_buf, TRUE);
1273                     return TKN_EOF;
1274                 }
1275             }
1276             g_string_append_c (tkn_buf, c == '\n' ? ' ' : c);
1277         }
1278         ret = (c == EOF) ? TKN_EOF : TKN_STRING;
1279         break;
1280     case '\\':
1281         c = getc (hotlist_file);
1282         if (c == EOF)
1283         {
1284             g_string_free (tkn_buf, TRUE);
1285             return TKN_EOF;
1286         }
1287         if (c == '\n')
1288             goto again;
1289 
1290         MC_FALLTHROUGH;         /* it is taken as normal character */
1291 
1292     default:
1293         do
1294         {
1295             g_string_append_c (tkn_buf, g_ascii_toupper (c));
1296         }
1297         while ((c = fgetc (hotlist_file)) != EOF && (g_ascii_isalnum (c) || !isascii (c)));
1298         if (c != EOF)
1299             ungetc (c, hotlist_file);
1300         l = tkn_buf->len;
1301         if (strncmp (tkn_buf->str, "GROUP", l) == 0)
1302             ret = TKN_GROUP;
1303         else if (strncmp (tkn_buf->str, "ENTRY", l) == 0)
1304             ret = TKN_ENTRY;
1305         else if (strncmp (tkn_buf->str, "ENDGROUP", l) == 0)
1306             ret = TKN_ENDGROUP;
1307         else if (strncmp (tkn_buf->str, "URL", l) == 0)
1308             ret = TKN_URL;
1309         else
1310             ret = TKN_UNKNOWN;
1311         break;
1312     }
1313     return ret;
1314 }
1315 
1316 /* --------------------------------------------------------------------------------------------- */
1317 
1318 static void
1319 hot_load_group (struct hotlist *grp)
     /* [previous][next][first][last][top][bottom][index][help]  */
1320 {
1321     int tkn;
1322     struct hotlist *new_grp;
1323     char *label, *url;
1324 
1325     current_group = grp;
1326 
1327     while ((tkn = hot_next_token ()) != TKN_ENDGROUP)
1328         switch (tkn)
1329         {
1330         case TKN_GROUP:
1331             CHECK_TOKEN (TKN_STRING);
1332             new_grp =
1333                 add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
1334                              LISTBOX_APPEND_AT_END);
1335             SKIP_TO_EOL;
1336             hot_load_group (new_grp);
1337             current_group = grp;
1338             break;
1339         case TKN_ENTRY:
1340             {
1341                 CHECK_TOKEN (TKN_STRING);
1342                 label = g_strndup (tkn_buf->str, tkn_buf->len);
1343                 CHECK_TOKEN (TKN_URL);
1344                 CHECK_TOKEN (TKN_STRING);
1345                 url = tilde_expand (tkn_buf->str);
1346                 add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1347                 SKIP_TO_EOL;
1348             }
1349             break;
1350         case TKN_COMMENT:
1351             label = g_strndup (tkn_buf->str, tkn_buf->len);
1352             add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
1353             break;
1354         case TKN_EOF:
1355             hotlist_state.readonly = TRUE;
1356             hotlist_state.file_error = TRUE;
1357             return;
1358         case TKN_EOL:
1359             /* skip empty lines */
1360             break;
1361         default:
1362             hotlist_state.readonly = TRUE;
1363             hotlist_state.file_error = TRUE;
1364             SKIP_TO_EOL;
1365             break;
1366         }
1367     SKIP_TO_EOL;
1368 }
1369 
1370 /* --------------------------------------------------------------------------------------------- */
1371 
1372 static void
1373 hot_load_file (struct hotlist *grp)
     /* [previous][next][first][last][top][bottom][index][help]  */
1374 {
1375     int tkn;
1376     struct hotlist *new_grp;
1377     char *label, *url;
1378 
1379     current_group = grp;
1380 
1381     while ((tkn = hot_next_token ()) != TKN_EOF)
1382         switch (tkn)
1383         {
1384         case TKN_GROUP:
1385             CHECK_TOKEN (TKN_STRING);
1386             new_grp =
1387                 add2hotlist (g_strndup (tkn_buf->str, tkn_buf->len), 0, HL_TYPE_GROUP,
1388                              LISTBOX_APPEND_AT_END);
1389             SKIP_TO_EOL;
1390             hot_load_group (new_grp);
1391             current_group = grp;
1392             break;
1393         case TKN_ENTRY:
1394             {
1395                 CHECK_TOKEN (TKN_STRING);
1396                 label = g_strndup (tkn_buf->str, tkn_buf->len);
1397                 CHECK_TOKEN (TKN_URL);
1398                 CHECK_TOKEN (TKN_STRING);
1399                 url = tilde_expand (tkn_buf->str);
1400                 add2hotlist (label, url, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1401                 SKIP_TO_EOL;
1402             }
1403             break;
1404         case TKN_COMMENT:
1405             label = g_strndup (tkn_buf->str, tkn_buf->len);
1406             add2hotlist (label, 0, HL_TYPE_COMMENT, LISTBOX_APPEND_AT_END);
1407             break;
1408         case TKN_EOL:
1409             /* skip empty lines */
1410             break;
1411         default:
1412             hotlist_state.readonly = TRUE;
1413             hotlist_state.file_error = TRUE;
1414             SKIP_TO_EOL;
1415             break;
1416         }
1417 }
1418 
1419 /* --------------------------------------------------------------------------------------------- */
1420 
1421 static void
1422 clean_up_hotlist_groups (const char *section)
     /* [previous][next][first][last][top][bottom][index][help]  */
1423 {
1424     char *grp_section;
1425 
1426     grp_section = g_strconcat (section, ".Group", (char *) NULL);
1427     if (mc_config_has_group (mc_global.main_config, section))
1428         mc_config_del_group (mc_global.main_config, section);
1429 
1430     if (mc_config_has_group (mc_global.main_config, grp_section))
1431     {
1432         char **profile_keys, **keys;
1433 
1434         keys = mc_config_get_keys (mc_global.main_config, grp_section, NULL);
1435 
1436         for (profile_keys = keys; *profile_keys != NULL; profile_keys++)
1437             clean_up_hotlist_groups (*profile_keys);
1438 
1439         g_strfreev (keys);
1440         mc_config_del_group (mc_global.main_config, grp_section);
1441     }
1442     g_free (grp_section);
1443 }
1444 
1445 /* --------------------------------------------------------------------------------------------- */
1446 
1447 static void
1448 load_hotlist (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1449 {
1450     gboolean remove_old_list = FALSE;
1451     struct stat stat_buf;
1452 
1453     if (hotlist_state.loaded)
1454     {
1455         stat (hotlist_file_name, &stat_buf);
1456         if (hotlist_file_mtime < stat_buf.st_mtime)
1457             done_hotlist ();
1458         else
1459             return;
1460     }
1461 
1462     if (hotlist_file_name == NULL)
1463         hotlist_file_name = mc_config_get_full_path (MC_HOTLIST_FILE);
1464 
1465     hotlist = g_new0 (struct hotlist, 1);
1466     hotlist->type = HL_TYPE_GROUP;
1467     hotlist->label = g_strdup (_("Top level group"));
1468     hotlist->up = hotlist;
1469     /*
1470      * compatibility :-(
1471      */
1472     hotlist->directory = g_strdup ("Hotlist");
1473 
1474     hotlist_file = fopen (hotlist_file_name, "r");
1475     if (hotlist_file == NULL)
1476     {
1477         int result;
1478 
1479         load_group (hotlist);
1480         hotlist_state.loaded = TRUE;
1481         /*
1482          * just to be sure we got copy
1483          */
1484         hotlist_state.modified = TRUE;
1485         result = save_hotlist ();
1486         hotlist_state.modified = FALSE;
1487         if (result != 0)
1488             remove_old_list = TRUE;
1489         else
1490             message (D_ERROR, _("Hotlist Load"),
1491                      _
1492                      ("MC was unable to write %s file,\nyour old hotlist entries were not deleted"),
1493                      MC_USERCONF_DIR PATH_SEP_STR MC_HOTLIST_FILE);
1494     }
1495     else
1496     {
1497         hot_load_file (hotlist);
1498         fclose (hotlist_file);
1499         hotlist_state.loaded = TRUE;
1500     }
1501 
1502     if (remove_old_list)
1503     {
1504         GError *mcerror = NULL;
1505 
1506         clean_up_hotlist_groups ("Hotlist");
1507         if (!mc_config_save_file (mc_global.main_config, &mcerror))
1508             setup_save_config_show_error (mc_global.main_config->ini_path, &mcerror);
1509 
1510         mc_error_message (&mcerror, NULL);
1511     }
1512 
1513     stat (hotlist_file_name, &stat_buf);
1514     hotlist_file_mtime = stat_buf.st_mtime;
1515     current_group = hotlist;
1516 }
1517 
1518 /* --------------------------------------------------------------------------------------------- */
1519 
1520 static void
1521 hot_save_group (struct hotlist *grp)
     /* [previous][next][first][last][top][bottom][index][help]  */
1522 {
1523     struct hotlist *current;
1524     int i;
1525     char *s;
1526 
1527 #define INDENT(n) \
1528 do { \
1529     for (i = 0; i < n; i++) \
1530         putc (' ', hotlist_file); \
1531 } while (0)
1532 
1533     for (current = grp->head; current != NULL; current = current->next)
1534         switch (current->type)
1535         {
1536         case HL_TYPE_GROUP:
1537             INDENT (list_level);
1538             fputs ("GROUP \"", hotlist_file);
1539             for (s = current->label; *s != '\0'; s++)
1540             {
1541                 if (*s == '"' || *s == '\\')
1542                     putc ('\\', hotlist_file);
1543                 putc (*s, hotlist_file);
1544             }
1545             fputs ("\"\n", hotlist_file);
1546             list_level += 2;
1547             hot_save_group (current);
1548             list_level -= 2;
1549             INDENT (list_level);
1550             fputs ("ENDGROUP\n", hotlist_file);
1551             break;
1552         case HL_TYPE_ENTRY:
1553             INDENT (list_level);
1554             fputs ("ENTRY \"", hotlist_file);
1555             for (s = current->label; *s != '\0'; s++)
1556             {
1557                 if (*s == '"' || *s == '\\')
1558                     putc ('\\', hotlist_file);
1559                 putc (*s, hotlist_file);
1560             }
1561             fputs ("\" URL \"", hotlist_file);
1562             for (s = current->directory; *s != '\0'; s++)
1563             {
1564                 if (*s == '"' || *s == '\\')
1565                     putc ('\\', hotlist_file);
1566                 putc (*s, hotlist_file);
1567             }
1568             fputs ("\"\n", hotlist_file);
1569             break;
1570         case HL_TYPE_COMMENT:
1571             fprintf (hotlist_file, "#%s\n", current->label);
1572             break;
1573         case HL_TYPE_DOTDOT:
1574             /* do nothing */
1575             break;
1576         default:
1577             break;
1578         }
1579 }
1580 
1581 /* --------------------------------------------------------------------------------------------- */
1582 
1583 static void
1584 add_dotdot_to_list (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1585 {
1586     if (current_group != hotlist && hotlist_has_dot_dot)
1587         add2hotlist (g_strdup (".."), g_strdup (".."), HL_TYPE_DOTDOT, LISTBOX_APPEND_AT_END);
1588 }
1589 
1590 /* --------------------------------------------------------------------------------------------- */
1591 /*** public functions ****************************************************************************/
1592 /* --------------------------------------------------------------------------------------------- */
1593 
1594 void
1595 add2hotlist_cmd (WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1596 {
1597     char *lc_prompt;
1598     const char *cp = N_("Label for \"%s\":");
1599     int l;
1600     char *label_string, *label;
1601 
1602 #ifdef ENABLE_NLS
1603     cp = _(cp);
1604 #endif
1605 
1606     /* extra variable to use it in the button callback */
1607     our_panel = panel;
1608 
1609     l = str_term_width1 (cp);
1610     label_string = vfs_path_to_str_flags (panel->cwd_vpath, 0, VPF_STRIP_PASSWORD);
1611     lc_prompt = g_strdup_printf (cp, str_trunc (label_string, COLS - 2 * UX - (l + 8)));
1612     label =
1613         input_dialog (_("Add to hotlist"), lc_prompt, MC_HISTORY_HOTLIST_ADD, label_string,
1614                       INPUT_COMPLETE_NONE);
1615     g_free (lc_prompt);
1616 
1617     if (label == NULL || *label == '\0')
1618     {
1619         g_free (label_string);
1620         g_free (label);
1621     }
1622     else
1623     {
1624         add2hotlist (label, label_string, HL_TYPE_ENTRY, LISTBOX_APPEND_AT_END);
1625         hotlist_state.modified = TRUE;
1626     }
1627 }
1628 
1629 /* --------------------------------------------------------------------------------------------- */
1630 
1631 char *
1632 hotlist_show (hotlist_t list_type, WPanel *panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1633 {
1634     char *target = NULL;
1635     int res;
1636 
1637     /* extra variable to use it in the button callback */
1638     our_panel = panel;
1639 
1640     hotlist_state.type = list_type;
1641     load_hotlist ();
1642 
1643     init_hotlist (list_type);
1644 
1645     /* display file info */
1646     tty_setcolor (SELECTED_COLOR);
1647 
1648     hotlist_state.running = TRUE;
1649     res = dlg_run (hotlist_dlg);
1650     hotlist_state.running = FALSE;
1651     save_hotlist ();
1652 
1653     if (res == B_ENTER)
1654     {
1655         char *text = NULL;
1656         struct hotlist *hlp = NULL;
1657 
1658         listbox_get_current (l_hotlist, &text, (void **) &hlp);
1659         target = g_strdup (hlp != NULL ? hlp->directory : text);
1660     }
1661 
1662     hotlist_done ();
1663     return target;
1664 }
1665 
1666 /* --------------------------------------------------------------------------------------------- */
1667 
1668 gboolean
1669 save_hotlist (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1670 {
1671     gboolean saved = FALSE;
1672     struct stat stat_buf;
1673 
1674     if (!hotlist_state.readonly && hotlist_state.modified && hotlist_file_name != NULL)
1675     {
1676         mc_util_make_backup_if_possible (hotlist_file_name, ".bak");
1677 
1678         hotlist_file = fopen (hotlist_file_name, "w");
1679         if (hotlist_file == NULL)
1680             mc_util_restore_from_backup_if_possible (hotlist_file_name, ".bak");
1681         else
1682         {
1683             hot_save_group (hotlist);
1684             fclose (hotlist_file);
1685             stat (hotlist_file_name, &stat_buf);
1686             hotlist_file_mtime = stat_buf.st_mtime;
1687             hotlist_state.modified = FALSE;
1688             saved = TRUE;
1689         }
1690     }
1691 
1692     return saved;
1693 }
1694 
1695 /* --------------------------------------------------------------------------------------------- */
1696 /**
1697  * Unload list from memory.
1698  * Don't confuse with hotlist_done() for GUI.
1699  */
1700 
1701 void
1702 done_hotlist (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
1703 {
1704     if (hotlist != NULL)
1705     {
1706         remove_group (hotlist);
1707         g_free (hotlist->label);
1708         g_free (hotlist->directory);
1709         MC_PTR_FREE (hotlist);
1710     }
1711 
1712     hotlist_state.loaded = FALSE;
1713 
1714     MC_PTR_FREE (hotlist_file_name);
1715     l_hotlist = NULL;
1716     current_group = NULL;
1717 
1718     if (tkn_buf != NULL)
1719     {
1720         g_string_free (tkn_buf, TRUE);
1721         tkn_buf = NULL;
1722     }
1723 }
1724 
1725 /* --------------------------------------------------------------------------------------------- */

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