root/src/filemanager/tree.c

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

DEFINITIONS

This source file includes following definitions.
  1. back_ptr
  2. forw_ptr
  3. remove_callback
  4. save_tree
  5. tree_remove_entry
  6. tree_destroy
  7. load_tree
  8. tree_show_mini_info
  9. show_tree
  10. tree_check_focus
  11. tree_move_backward
  12. tree_move_forward
  13. tree_move_to_child
  14. tree_move_to_parent
  15. tree_move_to_top
  16. tree_move_to_bottom
  17. tree_chdir_sel
  18. maybe_chdir
  19. search_tree
  20. tree_do_search
  21. tree_rescan
  22. tree_forget
  23. tree_copy
  24. tree_move
  25. tree_mkdir
  26. tree_rmdir
  27. tree_move_up
  28. tree_move_down
  29. tree_move_home
  30. tree_move_end
  31. tree_move_pgup
  32. tree_move_pgdn
  33. tree_move_left
  34. tree_move_right
  35. tree_start_search
  36. tree_toggle_navig
  37. tree_execute_cmd
  38. tree_key
  39. tree_frame
  40. tree_callback
  41. tree_mouse_callback
  42. tree_new
  43. tree_chdir
  44. tree_selected_name
  45. sync_tree
  46. find_tree

   1 /*
   2    Directory tree browser for the Midnight Commander
   3    This module has been converted to be a widget.
   4 
   5    The program load and saves the tree each time the tree widget is
   6    created and destroyed.  This is required for the future vfs layer,
   7    it will be possible to have tree views over virtual file systems.
   8 
   9    Copyright (C) 1994-2021
  10    Free Software Foundation, Inc.
  11 
  12    Written by:
  13    Janne Kukonlehto, 1994, 1996
  14    Norbert Warmuth, 1997
  15    Miguel de Icaza, 1996, 1999
  16    Slava Zanko <slavazanko@gmail.com>, 2013
  17    Andrew Borodin <aborodin@vmail.ru>, 2013, 2014, 2016
  18 
  19    This file is part of the Midnight Commander.
  20 
  21    The Midnight Commander is free software: you can redistribute it
  22    and/or modify it under the terms of the GNU General Public License as
  23    published by the Free Software Foundation, either version 3 of the License,
  24    or (at your option) any later version.
  25 
  26    The Midnight Commander is distributed in the hope that it will be useful,
  27    but WITHOUT ANY WARRANTY; without even the implied warranty of
  28    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  29    GNU General Public License for more details.
  30 
  31    You should have received a copy of the GNU General Public License
  32    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  33  */
  34 
  35 /** \file tree.c
  36  *  \brief Source: directory tree browser
  37  */
  38 
  39 #include <config.h>
  40 
  41 #include <errno.h>
  42 #include <stdio.h>
  43 #include <string.h>
  44 #include <sys/types.h>
  45 
  46 #include "lib/global.h"
  47 
  48 #include "lib/tty/tty.h"
  49 #include "lib/tty/key.h"
  50 #include "lib/skin.h"
  51 #include "lib/vfs/vfs.h"
  52 #include "lib/fileloc.h"
  53 #include "lib/strutil.h"
  54 #include "lib/util.h"
  55 #include "lib/widget.h"
  56 #include "lib/event.h"          /* mc_event_raise() */
  57 
  58 #include "src/setup.h"          /* confirm_delete, panels_options */
  59 #include "src/keymap.h"
  60 #include "src/history.h"
  61 
  62 #include "dir.h"
  63 #include "filemanager.h"        /* the_menubar */
  64 #include "file.h"               /* copy_dir_dir(), move_dir_dir(), erase_dir() */
  65 #include "layout.h"             /* command_prompt */
  66 #include "treestore.h"
  67 #include "cmd.h"
  68 #include "filegui.h"
  69 
  70 #include "tree.h"
  71 
  72 /*** global variables ****************************************************************************/
  73 
  74 /* The pointer to the tree */
  75 WTree *the_tree = NULL;
  76 
  77 /* If this is true, then when browsing the tree the other window will
  78  * automatically reload it's directory with the contents of the currently
  79  * selected directory.
  80  */
  81 gboolean xtree_mode = FALSE;
  82 
  83 /*** file scope macro definitions ****************************************************************/
  84 
  85 #define tlines(t) (t->is_panel ? WIDGET (t)->lines - 2 - \
  86                     (panels_options.show_mini_info ? 2 : 0) : WIDGET (t)->lines)
  87 
  88 /*** file scope type declarations ****************************************************************/
  89 
  90 struct WTree
  91 {
  92     Widget widget;
  93     struct TreeStore *store;
  94     tree_entry *selected_ptr;   /* The selected directory */
  95     GString *search_buffer;     /* Current search string */
  96     tree_entry **tree_shown;    /* Entries currently on screen */
  97     gboolean is_panel;          /* panel or plain widget flag */
  98     gboolean searching;         /* Are we on searching mode? */
  99     int topdiff;                /* The difference between the topmost
 100                                    shown and the selected */
 101 };
 102 
 103 /*** file scope variables ************************************************************************/
 104 
 105 /* Specifies the display mode: 1d or 2d */
 106 static gboolean tree_navigation_flag = FALSE;
 107 
 108 /* --------------------------------------------------------------------------------------------- */
 109 /*** file scope functions ************************************************************************/
 110 /* --------------------------------------------------------------------------------------------- */
 111 
 112 static void tree_rescan (void *data);
 113 
 114 /* --------------------------------------------------------------------------------------------- */
 115 
 116 static tree_entry *
 117 back_ptr (tree_entry * ptr, int *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 118 {
 119     int i;
 120 
 121     for (i = 0; ptr != NULL && ptr->prev != NULL && i < *count; ptr = ptr->prev, i++)
 122         ;
 123 
 124     *count = i;
 125     return ptr;
 126 }
 127 
 128 /* --------------------------------------------------------------------------------------------- */
 129 
 130 static tree_entry *
 131 forw_ptr (tree_entry * ptr, int *count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 132 {
 133     int i;
 134 
 135     for (i = 0; ptr != NULL && ptr->next != NULL && i < *count; ptr = ptr->next, i++)
 136         ;
 137 
 138     *count = i;
 139     return ptr;
 140 }
 141 
 142 /* --------------------------------------------------------------------------------------------- */
 143 
 144 static void
 145 remove_callback (tree_entry * entry, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 146 {
 147     WTree *tree = data;
 148 
 149     if (tree->selected_ptr == entry)
 150     {
 151         if (tree->selected_ptr->next != NULL)
 152             tree->selected_ptr = tree->selected_ptr->next;
 153         else
 154             tree->selected_ptr = tree->selected_ptr->prev;
 155     }
 156 }
 157 
 158 /* --------------------------------------------------------------------------------------------- */
 159 /** Save the ${XDG_CACHE_HOME}/mc/Tree file */
 160 
 161 static void
 162 save_tree (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 163 {
 164     int error;
 165 
 166     (void) tree;
 167 
 168     error = tree_store_save ();
 169     if (error != 0)
 170     {
 171         char *tree_name;
 172 
 173         tree_name = mc_config_get_full_path (MC_TREESTORE_FILE);
 174         fprintf (stderr, _("Cannot open the %s file for writing:\n%s\n"), tree_name,
 175                  unix_error_string (error));
 176         g_free (tree_name);
 177     }
 178 }
 179 
 180 /* --------------------------------------------------------------------------------------------- */
 181 
 182 static void
 183 tree_remove_entry (WTree * tree, const vfs_path_t * name_vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
 184 {
 185     (void) tree;
 186     tree_store_remove_entry (name_vpath);
 187 }
 188 
 189 /* --------------------------------------------------------------------------------------------- */
 190 
 191 static void
 192 tree_destroy (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 193 {
 194     tree_store_remove_entry_remove_hook (remove_callback);
 195     save_tree (tree);
 196 
 197     MC_PTR_FREE (tree->tree_shown);
 198     g_string_free (tree->search_buffer, TRUE);
 199     tree->selected_ptr = NULL;
 200 }
 201 
 202 /* --------------------------------------------------------------------------------------------- */
 203 /** Loads the .mc.tree file */
 204 
 205 static void
 206 load_tree (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 207 {
 208     vfs_path_t *vpath;
 209 
 210     tree_store_load ();
 211 
 212     tree->selected_ptr = tree->store->tree_first;
 213     vpath = vfs_path_from_str (mc_config_get_home_dir ());
 214     tree_chdir (tree, vpath);
 215     vfs_path_free (vpath, TRUE);
 216 }
 217 
 218 /* --------------------------------------------------------------------------------------------- */
 219 
 220 static void
 221 tree_show_mini_info (WTree * tree, int tree_lines, int tree_cols)
     /* [previous][next][first][last][top][bottom][index][help]  */
 222 {
 223     Widget *w = WIDGET (tree);
 224     int line;
 225 
 226     /* Show mini info */
 227     if (tree->is_panel)
 228     {
 229         if (!panels_options.show_mini_info)
 230             return;
 231         line = tree_lines + 2;
 232     }
 233     else
 234         line = tree_lines + 1;
 235 
 236     if (tree->searching)
 237     {
 238         /* Show search string */
 239         tty_setcolor (INPUT_COLOR);
 240         tty_draw_hline (w->y + line, w->x + 1, ' ', tree_cols);
 241         widget_gotoyx (w, line, 1);
 242         tty_print_char (PATH_SEP);
 243         tty_print_string (str_fit_to_term (tree->search_buffer->str, tree_cols - 2, J_LEFT_FIT));
 244         tty_print_char (' ');
 245     }
 246     else
 247     {
 248         /* Show full name of selected directory */
 249 
 250         const int *colors;
 251 
 252         colors = widget_get_colors (w);
 253         tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
 254         tty_draw_hline (w->y + line, w->x + 1, ' ', tree_cols);
 255         widget_gotoyx (w, line, 1);
 256         tty_print_string (str_fit_to_term
 257                           (vfs_path_as_str (tree->selected_ptr->name), tree_cols, J_LEFT_FIT));
 258     }
 259 }
 260 
 261 /* --------------------------------------------------------------------------------------------- */
 262 
 263 static void
 264 show_tree (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 265 {
 266     Widget *w = WIDGET (tree);
 267     tree_entry *current;
 268     int i, j;
 269     int topsublevel = 0;
 270     int x = 0, y = 0;
 271     int tree_lines, tree_cols;
 272 
 273     /* Initialize */
 274     tree_lines = tlines (tree);
 275     tree_cols = w->cols;
 276 
 277     widget_gotoyx (w, y, x);
 278     if (tree->is_panel)
 279     {
 280         tree_cols -= 2;
 281         x = y = 1;
 282     }
 283 
 284     g_free (tree->tree_shown);
 285     tree->tree_shown = g_new0 (tree_entry *, tree_lines);
 286 
 287     if (tree->store->tree_first != NULL)
 288         topsublevel = tree->store->tree_first->sublevel;
 289 
 290     if (tree->selected_ptr == NULL)
 291     {
 292         tree->selected_ptr = tree->store->tree_first;
 293         tree->topdiff = 0;
 294     }
 295     current = tree->selected_ptr;
 296 
 297     /* Calculate the directory which is to be shown on the topmost line */
 298     if (!tree_navigation_flag)
 299         current = back_ptr (current, &tree->topdiff);
 300     else
 301     {
 302         i = 0;
 303 
 304         while (current->prev != NULL && i < tree->topdiff)
 305         {
 306             current = current->prev;
 307 
 308             if (current->sublevel < tree->selected_ptr->sublevel)
 309             {
 310                 if (vfs_path_equal (current->name, tree->selected_ptr->name))
 311                     i++;
 312             }
 313             else if (current->sublevel == tree->selected_ptr->sublevel)
 314             {
 315                 const char *cname;
 316 
 317                 cname = vfs_path_as_str (current->name);
 318                 for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 319                     ;
 320                 if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 321                     i++;
 322             }
 323             else if (current->sublevel == tree->selected_ptr->sublevel + 1)
 324             {
 325                 j = vfs_path_len (tree->selected_ptr->name);
 326                 if (j > 1 && vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 327                     i++;
 328             }
 329         }
 330         tree->topdiff = i;
 331     }
 332 
 333     /* Loop for every line */
 334     for (i = 0; i < tree_lines; i++)
 335     {
 336         const int *colors;
 337 
 338         colors = widget_get_colors (w);
 339         tty_setcolor (tree->is_panel ? NORMAL_COLOR : colors[DLG_COLOR_NORMAL]);
 340 
 341         /* Move to the beginning of the line */
 342         tty_draw_hline (w->y + y + i, w->x + x, ' ', tree_cols);
 343 
 344         if (current == NULL)
 345             continue;
 346 
 347         if (tree->is_panel)
 348         {
 349             gboolean selected;
 350 
 351             selected = widget_get_state (w, WST_FOCUSED) && current == tree->selected_ptr;
 352             tty_setcolor (selected ? SELECTED_COLOR : NORMAL_COLOR);
 353         }
 354         else
 355         {
 356             int idx = current == tree->selected_ptr ? DLG_COLOR_FOCUS : DLG_COLOR_NORMAL;
 357 
 358             tty_setcolor (colors[idx]);
 359         }
 360 
 361         tree->tree_shown[i] = current;
 362         if (current->sublevel == topsublevel)
 363             /* Show full name */
 364             tty_print_string (str_fit_to_term
 365                               (vfs_path_as_str (current->name),
 366                                tree_cols + (tree->is_panel ? 0 : 1), J_LEFT_FIT));
 367         else
 368         {
 369             /* Sub level directory */
 370             tty_set_alt_charset (TRUE);
 371 
 372             /* Output branch parts */
 373             for (j = 0; j < current->sublevel - topsublevel - 1; j++)
 374             {
 375                 if (tree_cols - 8 - 3 * j < 9)
 376                     break;
 377                 tty_print_char (' ');
 378                 if ((current->submask & (1 << (j + topsublevel + 1))) != 0)
 379                     tty_print_char (ACS_VLINE);
 380                 else
 381                     tty_print_char (' ');
 382                 tty_print_char (' ');
 383             }
 384             tty_print_char (' ');
 385             j++;
 386             if (current->next == NULL || (current->next->submask & (1 << current->sublevel)) == 0)
 387                 tty_print_char (ACS_LLCORNER);
 388             else
 389                 tty_print_char (ACS_LTEE);
 390             tty_print_char (ACS_HLINE);
 391             tty_set_alt_charset (FALSE);
 392 
 393             /* Show sub-name */
 394             tty_print_char (' ');
 395             tty_print_string (str_fit_to_term
 396                               (current->subname, tree_cols - x - 3 * j, J_LEFT_FIT));
 397         }
 398 
 399         /* Calculate the next value for current */
 400         current = current->next;
 401         if (tree_navigation_flag)
 402             for (; current != NULL; current = current->next)
 403             {
 404                 if (current->sublevel < tree->selected_ptr->sublevel)
 405                 {
 406                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 407                                             vfs_path_len (current->name)))
 408                         break;
 409                 }
 410                 else if (current->sublevel == tree->selected_ptr->sublevel)
 411                 {
 412                     const char *cname;
 413 
 414                     cname = vfs_path_as_str (current->name);
 415                     for (j = strlen (cname) - 1; !IS_PATH_SEP (cname[j]); j--)
 416                         ;
 417                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name, j))
 418                         break;
 419                 }
 420                 else if (current->sublevel == tree->selected_ptr->sublevel + 1
 421                          && vfs_path_len (tree->selected_ptr->name) > 1)
 422                 {
 423                     if (vfs_path_equal_len (current->name, tree->selected_ptr->name,
 424                                             vfs_path_len (tree->selected_ptr->name)))
 425                         break;
 426                 }
 427             }
 428     }
 429 
 430     tree_show_mini_info (tree, tree_lines, tree_cols);
 431 }
 432 
 433 /* --------------------------------------------------------------------------------------------- */
 434 
 435 static void
 436 tree_check_focus (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 437 {
 438     if (tree->topdiff < 3)
 439         tree->topdiff = 3;
 440     else if (tree->topdiff >= tlines (tree) - 3)
 441         tree->topdiff = tlines (tree) - 3 - 1;
 442 }
 443 
 444 /* --------------------------------------------------------------------------------------------- */
 445 
 446 static void
 447 tree_move_backward (WTree * tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 448 {
 449     if (!tree_navigation_flag)
 450         tree->selected_ptr = back_ptr (tree->selected_ptr, &i);
 451     else
 452     {
 453         tree_entry *current;
 454         int j = 0;
 455 
 456         current = tree->selected_ptr;
 457         while (j < i && current->prev != NULL
 458                && current->prev->sublevel >= tree->selected_ptr->sublevel)
 459         {
 460             current = current->prev;
 461             if (current->sublevel == tree->selected_ptr->sublevel)
 462             {
 463                 tree->selected_ptr = current;
 464                 j++;
 465             }
 466         }
 467         i = j;
 468     }
 469 
 470     tree->topdiff -= i;
 471     tree_check_focus (tree);
 472 }
 473 
 474 /* --------------------------------------------------------------------------------------------- */
 475 
 476 static void
 477 tree_move_forward (WTree * tree, int i)
     /* [previous][next][first][last][top][bottom][index][help]  */
 478 {
 479     if (!tree_navigation_flag)
 480         tree->selected_ptr = forw_ptr (tree->selected_ptr, &i);
 481     else
 482     {
 483         tree_entry *current;
 484         int j = 0;
 485 
 486         current = tree->selected_ptr;
 487         while (j < i && current->next != NULL
 488                && current->next->sublevel >= tree->selected_ptr->sublevel)
 489         {
 490             current = current->next;
 491             if (current->sublevel == tree->selected_ptr->sublevel)
 492             {
 493                 tree->selected_ptr = current;
 494                 j++;
 495             }
 496         }
 497         i = j;
 498     }
 499 
 500     tree->topdiff += i;
 501     tree_check_focus (tree);
 502 }
 503 
 504 /* --------------------------------------------------------------------------------------------- */
 505 
 506 static void
 507 tree_move_to_child (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 508 {
 509     tree_entry *current;
 510 
 511     /* Do we have a starting point? */
 512     if (tree->selected_ptr == NULL)
 513         return;
 514 
 515     /* Take the next entry */
 516     current = tree->selected_ptr->next;
 517     /* Is it the child of the selected entry */
 518     if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 519     {
 520         /* Yes -> select this entry */
 521         tree->selected_ptr = current;
 522         tree->topdiff++;
 523         tree_check_focus (tree);
 524     }
 525     else
 526     {
 527         /* No -> rescan and try again */
 528         tree_rescan (tree);
 529         current = tree->selected_ptr->next;
 530         if (current != NULL && current->sublevel > tree->selected_ptr->sublevel)
 531         {
 532             tree->selected_ptr = current;
 533             tree->topdiff++;
 534             tree_check_focus (tree);
 535         }
 536     }
 537 }
 538 
 539 /* --------------------------------------------------------------------------------------------- */
 540 
 541 static gboolean
 542 tree_move_to_parent (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 543 {
 544     tree_entry *current;
 545     tree_entry *old;
 546 
 547     if (tree->selected_ptr == NULL)
 548         return FALSE;
 549 
 550     old = tree->selected_ptr;
 551 
 552     for (current = tree->selected_ptr->prev;
 553          current != NULL && current->sublevel >= tree->selected_ptr->sublevel;
 554          current = current->prev)
 555         tree->topdiff--;
 556 
 557     if (current == NULL)
 558         current = tree->store->tree_first;
 559     tree->selected_ptr = current;
 560     tree_check_focus (tree);
 561     return tree->selected_ptr != old;
 562 }
 563 
 564 /* --------------------------------------------------------------------------------------------- */
 565 
 566 static void
 567 tree_move_to_top (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 568 {
 569     tree->selected_ptr = tree->store->tree_first;
 570     tree->topdiff = 0;
 571 }
 572 
 573 /* --------------------------------------------------------------------------------------------- */
 574 
 575 static void
 576 tree_move_to_bottom (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 577 {
 578     tree->selected_ptr = tree->store->tree_last;
 579     tree->topdiff = tlines (tree) - 3 - 1;
 580 }
 581 
 582 /* --------------------------------------------------------------------------------------------- */
 583 
 584 static void
 585 tree_chdir_sel (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 586 {
 587     if (tree->is_panel)
 588     {
 589         WPanel *p;
 590 
 591         p = change_panel ();
 592 
 593         if (panel_cd (p, tree->selected_ptr->name, cd_exact))
 594             select_item (p);
 595         else
 596             message (D_ERROR, MSG_ERROR, _("Cannot chdir to \"%s\"\n%s"),
 597                      vfs_path_as_str (tree->selected_ptr->name), unix_error_string (errno));
 598 
 599         widget_draw (WIDGET (p));
 600         (void) change_panel ();
 601         show_tree (tree);
 602     }
 603     else
 604     {
 605         WDialog *h = DIALOG (WIDGET (tree)->owner);
 606 
 607         h->ret_value = B_ENTER;
 608         dlg_stop (h);
 609     }
 610 }
 611 
 612 /* --------------------------------------------------------------------------------------------- */
 613 
 614 static void
 615 maybe_chdir (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 616 {
 617     if (xtree_mode && tree->is_panel && is_idle ())
 618         tree_chdir_sel (tree);
 619 }
 620 
 621 /* --------------------------------------------------------------------------------------------- */
 622 /** Search tree for text */
 623 
 624 static gboolean
 625 search_tree (WTree * tree, const GString * text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 626 {
 627     tree_entry *current = tree->selected_ptr;
 628     gboolean wrapped = FALSE;
 629     gboolean found = FALSE;
 630 
 631     while (!found && (!wrapped || current != tree->selected_ptr))
 632         if (strncmp (current->subname, text->str, text->len) == 0)
 633         {
 634             tree->selected_ptr = current;
 635             found = TRUE;
 636         }
 637         else
 638         {
 639             current = current->next;
 640             if (current == NULL)
 641             {
 642                 current = tree->store->tree_first;
 643                 wrapped = TRUE;
 644             }
 645 
 646             tree->topdiff++;
 647         }
 648 
 649     tree_check_focus (tree);
 650     return found;
 651 }
 652 
 653 /* --------------------------------------------------------------------------------------------- */
 654 
 655 static void
 656 tree_do_search (WTree * tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
 657 {
 658     /* TODO: support multi-byte characters, see do_search() in panel.c */
 659 
 660     if (tree->search_buffer->len != 0 && key == KEY_BACKSPACE)
 661         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 662     else if (key != 0)
 663         g_string_append_c (tree->search_buffer, (gchar) key);
 664 
 665     if (!search_tree (tree, tree->search_buffer))
 666         g_string_set_size (tree->search_buffer, tree->search_buffer->len - 1);
 667 
 668     show_tree (tree);
 669     maybe_chdir (tree);
 670 }
 671 
 672 /* --------------------------------------------------------------------------------------------- */
 673 
 674 static void
 675 tree_rescan (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 676 {
 677     WTree *tree = data;
 678     vfs_path_t *old_vpath;
 679 
 680     old_vpath = vfs_path_clone (vfs_get_raw_current_dir ());
 681     if (old_vpath == NULL)
 682         return;
 683 
 684     if (tree->selected_ptr != NULL && mc_chdir (tree->selected_ptr->name) == 0)
 685     {
 686         int ret;
 687 
 688         tree_store_rescan (tree->selected_ptr->name);
 689         ret = mc_chdir (old_vpath);
 690         (void) ret;
 691     }
 692     vfs_path_free (old_vpath, TRUE);
 693 }
 694 
 695 /* --------------------------------------------------------------------------------------------- */
 696 
 697 static void
 698 tree_forget (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 699 {
 700     WTree *tree = data;
 701 
 702     if (tree->selected_ptr != NULL)
 703         tree_remove_entry (tree, tree->selected_ptr->name);
 704 }
 705 
 706 /* --------------------------------------------------------------------------------------------- */
 707 
 708 static void
 709 tree_copy (WTree * tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 710 {
 711     char msg[BUF_MEDIUM];
 712     char *dest;
 713 
 714     if (tree->selected_ptr == NULL)
 715         return;
 716 
 717     g_snprintf (msg, sizeof (msg), _("Copy \"%s\" directory to:"),
 718                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 719     dest = input_expand_dialog (Q_ ("DialogTitle|Copy"),
 720                                 msg, MC_HISTORY_FM_TREE_COPY, default_dest,
 721                                 INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 722 
 723     if (dest != NULL && *dest != '\0')
 724     {
 725         file_op_context_t *ctx;
 726         file_op_total_context_t *tctx;
 727 
 728         ctx = file_op_context_new (OP_COPY);
 729         tctx = file_op_total_context_new ();
 730         file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_MULTI_ITEM);
 731         tctx->ask_overwrite = FALSE;
 732         copy_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest, TRUE, FALSE,
 733                       FALSE, NULL);
 734         file_op_total_context_destroy (tctx);
 735         file_op_context_destroy (ctx);
 736     }
 737 
 738     g_free (dest);
 739 }
 740 
 741 /* --------------------------------------------------------------------------------------------- */
 742 
 743 static void
 744 tree_move (WTree * tree, const char *default_dest)
     /* [previous][next][first][last][top][bottom][index][help]  */
 745 {
 746     char msg[BUF_MEDIUM];
 747     char *dest;
 748     struct stat buf;
 749     file_op_context_t *ctx;
 750     file_op_total_context_t *tctx;
 751     vfs_path_t *dest_vpath = NULL;
 752 
 753     if (tree->selected_ptr == NULL)
 754         return;
 755 
 756     g_snprintf (msg, sizeof (msg), _("Move \"%s\" directory to:"),
 757                 str_trunc (vfs_path_as_str (tree->selected_ptr->name), 50));
 758     dest =
 759         input_expand_dialog (Q_ ("DialogTitle|Move"), msg, MC_HISTORY_FM_TREE_MOVE, default_dest,
 760                              INPUT_COMPLETE_FILENAMES | INPUT_COMPLETE_CD);
 761 
 762     if (dest == NULL || *dest == '\0')
 763         goto ret;
 764 
 765     dest_vpath = vfs_path_from_str (dest);
 766 
 767     if (mc_stat (dest_vpath, &buf))
 768     {
 769         message (D_ERROR, MSG_ERROR, _("Cannot stat the destination\n%s"),
 770                  unix_error_string (errno));
 771         goto ret;
 772     }
 773 
 774     if (!S_ISDIR (buf.st_mode))
 775     {
 776         file_error (TRUE, _("Destination \"%s\" must be a directory\n%s"), dest);
 777         goto ret;
 778     }
 779 
 780     ctx = file_op_context_new (OP_MOVE);
 781     tctx = file_op_total_context_new ();
 782     file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 783     move_dir_dir (tctx, ctx, vfs_path_as_str (tree->selected_ptr->name), dest);
 784     file_op_total_context_destroy (tctx);
 785     file_op_context_destroy (ctx);
 786 
 787   ret:
 788     vfs_path_free (dest_vpath, TRUE);
 789     g_free (dest);
 790 }
 791 
 792 /* --------------------------------------------------------------------------------------------- */
 793 
 794 #if 0
 795 static void
 796 tree_mkdir (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 797 {
 798     char old_dir[MC_MAXPATHLEN];
 799 
 800     if (tree->selected_ptr == NULL || chdir (tree->selected_ptr->name) != 0)
 801         return;
 802     /* FIXME
 803        mkdir_cmd (tree);
 804      */
 805     tree_rescan (tree);
 806     chdir (old_dir);
 807 }
 808 #endif
 809 
 810 /* --------------------------------------------------------------------------------------------- */
 811 
 812 static void
 813 tree_rmdir (void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
 814 {
 815     WTree *tree = data;
 816     file_op_context_t *ctx;
 817     file_op_total_context_t *tctx;
 818 
 819     if (tree->selected_ptr == NULL)
 820         return;
 821 
 822     if (confirm_delete)
 823     {
 824         char *buf;
 825         int result;
 826 
 827         buf = g_strdup_printf (_("Delete %s?"), vfs_path_as_str (tree->selected_ptr->name));
 828 
 829         result = query_dialog (Q_ ("DialogTitle|Delete"), buf, D_ERROR, 2, _("&Yes"), _("&No"));
 830         g_free (buf);
 831         if (result != 0)
 832             return;
 833     }
 834 
 835     ctx = file_op_context_new (OP_DELETE);
 836     tctx = file_op_total_context_new ();
 837 
 838     file_op_context_create_ui (ctx, FALSE, FILEGUI_DIALOG_ONE_ITEM);
 839     if (erase_dir (tctx, ctx, tree->selected_ptr->name) == FILE_CONT)
 840         tree_forget (tree);
 841     file_op_total_context_destroy (tctx);
 842     file_op_context_destroy (ctx);
 843 }
 844 
 845 /* --------------------------------------------------------------------------------------------- */
 846 
 847 static inline void
 848 tree_move_up (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 849 {
 850     tree_move_backward (tree, 1);
 851     show_tree (tree);
 852     maybe_chdir (tree);
 853 }
 854 
 855 /* --------------------------------------------------------------------------------------------- */
 856 
 857 static inline void
 858 tree_move_down (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 859 {
 860     tree_move_forward (tree, 1);
 861     show_tree (tree);
 862     maybe_chdir (tree);
 863 }
 864 
 865 /* --------------------------------------------------------------------------------------------- */
 866 
 867 static inline void
 868 tree_move_home (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 869 {
 870     tree_move_to_top (tree);
 871     show_tree (tree);
 872     maybe_chdir (tree);
 873 }
 874 
 875 /* --------------------------------------------------------------------------------------------- */
 876 
 877 static inline void
 878 tree_move_end (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 879 {
 880     tree_move_to_bottom (tree);
 881     show_tree (tree);
 882     maybe_chdir (tree);
 883 }
 884 
 885 /* --------------------------------------------------------------------------------------------- */
 886 
 887 static void
 888 tree_move_pgup (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 889 {
 890     tree_move_backward (tree, tlines (tree) - 1);
 891     show_tree (tree);
 892     maybe_chdir (tree);
 893 }
 894 
 895 /* --------------------------------------------------------------------------------------------- */
 896 
 897 static void
 898 tree_move_pgdn (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 899 {
 900     tree_move_forward (tree, tlines (tree) - 1);
 901     show_tree (tree);
 902     maybe_chdir (tree);
 903 }
 904 
 905 /* --------------------------------------------------------------------------------------------- */
 906 
 907 static gboolean
 908 tree_move_left (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 909 {
 910     gboolean v = FALSE;
 911 
 912     if (tree_navigation_flag)
 913     {
 914         v = tree_move_to_parent (tree);
 915         show_tree (tree);
 916         maybe_chdir (tree);
 917     }
 918 
 919     return v;
 920 }
 921 
 922 /* --------------------------------------------------------------------------------------------- */
 923 
 924 static gboolean
 925 tree_move_right (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 926 {
 927     gboolean v = FALSE;
 928 
 929     if (tree_navigation_flag)
 930     {
 931         tree_move_to_child (tree);
 932         show_tree (tree);
 933         maybe_chdir (tree);
 934         v = TRUE;
 935     }
 936 
 937     return v;
 938 }
 939 
 940 /* --------------------------------------------------------------------------------------------- */
 941 
 942 static void
 943 tree_start_search (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 944 {
 945     if (tree->searching)
 946     {
 947         if (tree->selected_ptr == tree->store->tree_last)
 948             tree_move_to_top (tree);
 949         else
 950         {
 951             gboolean i;
 952 
 953             /* set navigation mode temporarily to 'Static' because in
 954              * dynamic navigation mode tree_move_forward will not move
 955              * to a lower sublevel if necessary (sequent searches must
 956              * start with the directory followed the last found directory)
 957              */
 958             i = tree_navigation_flag;
 959             tree_navigation_flag = FALSE;
 960             tree_move_forward (tree, 1);
 961             tree_navigation_flag = i;
 962         }
 963         tree_do_search (tree, 0);
 964     }
 965     else
 966     {
 967         tree->searching = TRUE;
 968         g_string_set_size (tree->search_buffer, 0);
 969     }
 970 }
 971 
 972 /* --------------------------------------------------------------------------------------------- */
 973 
 974 static void
 975 tree_toggle_navig (WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
 976 {
 977     Widget *w = WIDGET (tree);
 978     WButtonBar *b;
 979 
 980     tree_navigation_flag = !tree_navigation_flag;
 981 
 982     b = find_buttonbar (DIALOG (w->owner));
 983     buttonbar_set_label (b, 4,
 984                          tree_navigation_flag ? Q_ ("ButtonBar|Static") : Q_ ("ButtonBar|Dynamc"),
 985                          w->keymap, w);
 986     widget_draw (WIDGET (b));
 987 }
 988 
 989 /* --------------------------------------------------------------------------------------------- */
 990 
 991 static cb_ret_t
 992 tree_execute_cmd (WTree * tree, long command)
     /* [previous][next][first][last][top][bottom][index][help]  */
 993 {
 994     cb_ret_t res = MSG_HANDLED;
 995 
 996     if (command != CK_Search)
 997         tree->searching = FALSE;
 998 
 999     switch (command)
1000     {
1001     case CK_Help:
1002         {
1003             ev_help_t event_data = { NULL, "[Directory Tree]" };
1004             mc_event_raise (MCEVENT_GROUP_CORE, "help", &event_data);
1005         }
1006         break;
1007     case CK_Forget:
1008         tree_forget (tree);
1009         break;
1010     case CK_ToggleNavigation:
1011         tree_toggle_navig (tree);
1012         break;
1013     case CK_Copy:
1014         tree_copy (tree, "");
1015         break;
1016     case CK_Move:
1017         tree_move (tree, "");
1018         break;
1019     case CK_Up:
1020         tree_move_up (tree);
1021         break;
1022     case CK_Down:
1023         tree_move_down (tree);
1024         break;
1025     case CK_Top:
1026         tree_move_home (tree);
1027         break;
1028     case CK_Bottom:
1029         tree_move_end (tree);
1030         break;
1031     case CK_PageUp:
1032         tree_move_pgup (tree);
1033         break;
1034     case CK_PageDown:
1035         tree_move_pgdn (tree);
1036         break;
1037     case CK_Enter:
1038         tree_chdir_sel (tree);
1039         break;
1040     case CK_Reread:
1041         tree_rescan (tree);
1042         break;
1043     case CK_Search:
1044         tree_start_search (tree);
1045         break;
1046     case CK_Delete:
1047         tree_rmdir (tree);
1048         break;
1049     case CK_Quit:
1050         if (!tree->is_panel)
1051             dlg_stop (DIALOG (WIDGET (tree)->owner));
1052         return res;
1053     default:
1054         res = MSG_NOT_HANDLED;
1055     }
1056 
1057     show_tree (tree);
1058 
1059     return res;
1060 }
1061 
1062 /* --------------------------------------------------------------------------------------------- */
1063 
1064 static cb_ret_t
1065 tree_key (WTree * tree, int key)
     /* [previous][next][first][last][top][bottom][index][help]  */
1066 {
1067     long command;
1068 
1069     if (is_abort_char (key))
1070     {
1071         if (tree->is_panel)
1072         {
1073             tree->searching = FALSE;
1074             show_tree (tree);
1075             return MSG_HANDLED; /* eat abort char */
1076         }
1077         /* modal tree dialog: let upper layer see the
1078            abort character and close the dialog */
1079         return MSG_NOT_HANDLED;
1080     }
1081 
1082     if (tree->searching && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1083     {
1084         tree_do_search (tree, key);
1085         show_tree (tree);
1086         return MSG_HANDLED;
1087     }
1088 
1089     command = widget_lookup_key (WIDGET (tree), key);
1090     switch (command)
1091     {
1092     case CK_IgnoreKey:
1093         break;
1094     case CK_Left:
1095         return tree_move_left (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1096     case CK_Right:
1097         return tree_move_right (tree) ? MSG_HANDLED : MSG_NOT_HANDLED;
1098     default:
1099         tree_execute_cmd (tree, command);
1100         return MSG_HANDLED;
1101     }
1102 
1103     /* Do not eat characters not meant for the tree below ' ' (e.g. C-l). */
1104     if (!command_prompt && ((key >= ' ' && key <= 255) || key == KEY_BACKSPACE))
1105     {
1106         tree_start_search (tree);
1107         tree_do_search (tree, key);
1108         return MSG_HANDLED;
1109     }
1110 
1111     return MSG_NOT_HANDLED;
1112 }
1113 
1114 /* --------------------------------------------------------------------------------------------- */
1115 
1116 static void
1117 tree_frame (WDialog * h, WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1118 {
1119     Widget *w = WIDGET (tree);
1120 
1121     (void) h;
1122 
1123     tty_setcolor (NORMAL_COLOR);
1124     widget_erase (w);
1125     if (tree->is_panel)
1126     {
1127         const char *title = _("Directory tree");
1128         const int len = str_term_width1 (title);
1129 
1130         tty_draw_box (w->y, w->x, w->lines, w->cols, FALSE);
1131 
1132         widget_gotoyx (w, 0, (w->cols - len - 2) / 2);
1133         tty_printf (" %s ", title);
1134 
1135         if (panels_options.show_mini_info)
1136         {
1137             int y;
1138 
1139             y = w->lines - 3;
1140             widget_gotoyx (w, y, 0);
1141             tty_print_alt_char (ACS_LTEE, FALSE);
1142             widget_gotoyx (w, y, w->cols - 1);
1143             tty_print_alt_char (ACS_RTEE, FALSE);
1144             tty_draw_hline (w->y + y, w->x + 1, ACS_HLINE, w->cols - 2);
1145         }
1146     }
1147 }
1148 
1149 /* --------------------------------------------------------------------------------------------- */
1150 
1151 static cb_ret_t
1152 tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
     /* [previous][next][first][last][top][bottom][index][help]  */
1153 {
1154     WTree *tree = (WTree *) w;
1155     WDialog *h = DIALOG (w->owner);
1156     WButtonBar *b;
1157 
1158     switch (msg)
1159     {
1160     case MSG_DRAW:
1161         tree_frame (h, tree);
1162         show_tree (tree);
1163         if (widget_get_state (w, WST_FOCUSED))
1164         {
1165             b = find_buttonbar (h);
1166             widget_draw (WIDGET (b));
1167         }
1168         return MSG_HANDLED;
1169 
1170     case MSG_FOCUS:
1171         b = find_buttonbar (h);
1172         buttonbar_set_label (b, 1, Q_ ("ButtonBar|Help"), w->keymap, w);
1173         buttonbar_set_label (b, 2, Q_ ("ButtonBar|Rescan"), w->keymap, w);
1174         buttonbar_set_label (b, 3, Q_ ("ButtonBar|Forget"), w->keymap, w);
1175         buttonbar_set_label (b, 4, tree_navigation_flag ? Q_ ("ButtonBar|Static")
1176                              : Q_ ("ButtonBar|Dynamc"), w->keymap, w);
1177         buttonbar_set_label (b, 5, Q_ ("ButtonBar|Copy"), w->keymap, w);
1178         buttonbar_set_label (b, 6, Q_ ("ButtonBar|RenMov"), w->keymap, w);
1179 #if 0
1180         /* FIXME: mkdir is currently defunct */
1181         buttonbar_set_label (b, 7, Q_ ("ButtonBar|Mkdir"), w->keymap, w);
1182 #else
1183         buttonbar_clear_label (b, 7, w);
1184 #endif
1185         buttonbar_set_label (b, 8, Q_ ("ButtonBar|Rmdir"), w->keymap, w);
1186 
1187         return MSG_HANDLED;
1188 
1189     case MSG_UNFOCUS:
1190         tree->searching = FALSE;
1191         return MSG_HANDLED;
1192 
1193     case MSG_KEY:
1194         return tree_key (tree, parm);
1195 
1196     case MSG_ACTION:
1197         /* command from buttonbar */
1198         return tree_execute_cmd (tree, parm);
1199 
1200     case MSG_DESTROY:
1201         tree_destroy (tree);
1202         return MSG_HANDLED;
1203 
1204     default:
1205         return widget_default_callback (w, sender, msg, parm, data);
1206     }
1207 }
1208 
1209 /* --------------------------------------------------------------------------------------------- */
1210 
1211 /**
1212   * Mouse callback
1213   */
1214 static void
1215 tree_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
     /* [previous][next][first][last][top][bottom][index][help]  */
1216 {
1217     WTree *tree = (WTree *) w;
1218     int y;
1219 
1220     y = event->y;
1221     if (tree->is_panel)
1222         y--;
1223 
1224     switch (msg)
1225     {
1226     case MSG_MOUSE_DOWN:
1227         /* rest of the upper frame - call menu */
1228         if (tree->is_panel && event->y == WIDGET (w->owner)->y)
1229         {
1230             /* return MOU_UNHANDLED */
1231             event->result.abort = TRUE;
1232         }
1233         else if (!widget_get_state (w, WST_FOCUSED))
1234             (void) change_panel ();
1235         break;
1236 
1237     case MSG_MOUSE_CLICK:
1238         {
1239             int lines;
1240 
1241             lines = tlines (tree);
1242 
1243             if (y < 0)
1244             {
1245                 tree_move_backward (tree, lines - 1);
1246                 show_tree (tree);
1247             }
1248             else if (y >= lines)
1249             {
1250                 tree_move_forward (tree, lines - 1);
1251                 show_tree (tree);
1252             }
1253             else if ((event->count & GPM_DOUBLE) != 0)
1254             {
1255                 if (tree->tree_shown[y] != NULL)
1256                 {
1257                     tree->selected_ptr = tree->tree_shown[y];
1258                     tree->topdiff = y;
1259                 }
1260 
1261                 tree_chdir_sel (tree);
1262             }
1263         }
1264         break;
1265 
1266     case MSG_MOUSE_SCROLL_UP:
1267     case MSG_MOUSE_SCROLL_DOWN:
1268         /* TODO: Ticket #2218 */
1269         break;
1270 
1271     default:
1272         break;
1273     }
1274 }
1275 
1276 /* --------------------------------------------------------------------------------------------- */
1277 /*** public functions ****************************************************************************/
1278 /* --------------------------------------------------------------------------------------------- */
1279 
1280 WTree *
1281 tree_new (int y, int x, int lines, int cols, gboolean is_panel)
     /* [previous][next][first][last][top][bottom][index][help]  */
1282 {
1283     WTree *tree;
1284     Widget *w;
1285 
1286     tree = g_new (WTree, 1);
1287 
1288     w = WIDGET (tree);
1289     widget_init (w, y, x, lines, cols, tree_callback, tree_mouse_callback);
1290     w->options |= WOP_SELECTABLE | WOP_TOP_SELECT;
1291     w->keymap = tree_map;
1292 
1293     tree->is_panel = is_panel;
1294     tree->selected_ptr = NULL;
1295 
1296     tree->store = tree_store_get ();
1297     tree_store_add_entry_remove_hook (remove_callback, tree);
1298     tree->tree_shown = NULL;
1299     tree->search_buffer = g_string_sized_new (MC_MAXPATHLEN);
1300     tree->topdiff = w->lines / 2;
1301     tree->searching = FALSE;
1302 
1303     load_tree (tree);
1304     return tree;
1305 }
1306 
1307 /* --------------------------------------------------------------------------------------------- */
1308 
1309 void
1310 tree_chdir (WTree * tree, const vfs_path_t * dir)
     /* [previous][next][first][last][top][bottom][index][help]  */
1311 {
1312     tree_entry *current;
1313 
1314     current = tree_store_whereis (dir);
1315     if (current != NULL)
1316     {
1317         tree->selected_ptr = current;
1318         tree_check_focus (tree);
1319     }
1320 }
1321 
1322 /* --------------------------------------------------------------------------------------------- */
1323 /** Return name of the currently selected entry */
1324 
1325 const vfs_path_t *
1326 tree_selected_name (const WTree * tree)
     /* [previous][next][first][last][top][bottom][index][help]  */
1327 {
1328     return tree->selected_ptr->name;
1329 }
1330 
1331 /* --------------------------------------------------------------------------------------------- */
1332 
1333 void
1334 sync_tree (const vfs_path_t * vpath)
     /* [previous][next][first][last][top][bottom][index][help]  */
1335 {
1336     tree_chdir (the_tree, vpath);
1337 }
1338 
1339 /* --------------------------------------------------------------------------------------------- */
1340 
1341 WTree *
1342 find_tree (const WDialog * h)
     /* [previous][next][first][last][top][bottom][index][help]  */
1343 {
1344     return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback);
1345 }
1346 
1347 /* --------------------------------------------------------------------------------------------- */

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