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

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