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

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