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

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