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 *
back_ptr(tree_entry * ptr,int * count)117 back_ptr (tree_entry * ptr, int *count)
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 *
forw_ptr(tree_entry * ptr,int * count)131 forw_ptr (tree_entry * ptr, int *count)
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
remove_callback(tree_entry * entry,void * data)145 remove_callback (tree_entry * entry, void *data)
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
save_tree(WTree * tree)162 save_tree (WTree * tree)
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
tree_remove_entry(WTree * tree,const vfs_path_t * name_vpath)183 tree_remove_entry (WTree * tree, const vfs_path_t * name_vpath)
184 {
185     (void) tree;
186     tree_store_remove_entry (name_vpath);
187 }
188 
189 /* --------------------------------------------------------------------------------------------- */
190 
191 static void
tree_destroy(WTree * tree)192 tree_destroy (WTree * tree)
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
load_tree(WTree * tree)206 load_tree (WTree * tree)
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
tree_show_mini_info(WTree * tree,int tree_lines,int tree_cols)221 tree_show_mini_info (WTree * tree, int tree_lines, int tree_cols)
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
show_tree(WTree * tree)264 show_tree (WTree * tree)
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
tree_check_focus(WTree * tree)436 tree_check_focus (WTree * tree)
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
tree_move_backward(WTree * tree,int i)447 tree_move_backward (WTree * tree, int i)
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
tree_move_forward(WTree * tree,int i)477 tree_move_forward (WTree * tree, int i)
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
tree_move_to_child(WTree * tree)507 tree_move_to_child (WTree * tree)
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
tree_move_to_parent(WTree * tree)542 tree_move_to_parent (WTree * tree)
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
tree_move_to_top(WTree * tree)567 tree_move_to_top (WTree * tree)
568 {
569     tree->selected_ptr = tree->store->tree_first;
570     tree->topdiff = 0;
571 }
572 
573 /* --------------------------------------------------------------------------------------------- */
574 
575 static void
tree_move_to_bottom(WTree * tree)576 tree_move_to_bottom (WTree * tree)
577 {
578     tree->selected_ptr = tree->store->tree_last;
579     tree->topdiff = tlines (tree) - 3 - 1;
580 }
581 
582 /* --------------------------------------------------------------------------------------------- */
583 
584 static void
tree_chdir_sel(WTree * tree)585 tree_chdir_sel (WTree * tree)
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
maybe_chdir(WTree * tree)615 maybe_chdir (WTree * tree)
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
search_tree(WTree * tree,const GString * text)625 search_tree (WTree * tree, const GString * text)
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
tree_do_search(WTree * tree,int key)656 tree_do_search (WTree * tree, int key)
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
tree_rescan(void * data)675 tree_rescan (void *data)
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
tree_forget(void * data)698 tree_forget (void *data)
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
tree_copy(WTree * tree,const char * default_dest)709 tree_copy (WTree * tree, const char *default_dest)
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
tree_move(WTree * tree,const char * default_dest)744 tree_move (WTree * tree, const char *default_dest)
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)
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
tree_rmdir(void * data)813 tree_rmdir (void *data)
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
tree_move_up(WTree * tree)848 tree_move_up (WTree * tree)
849 {
850     tree_move_backward (tree, 1);
851     show_tree (tree);
852     maybe_chdir (tree);
853 }
854 
855 /* --------------------------------------------------------------------------------------------- */
856 
857 static inline void
tree_move_down(WTree * tree)858 tree_move_down (WTree * tree)
859 {
860     tree_move_forward (tree, 1);
861     show_tree (tree);
862     maybe_chdir (tree);
863 }
864 
865 /* --------------------------------------------------------------------------------------------- */
866 
867 static inline void
tree_move_home(WTree * tree)868 tree_move_home (WTree * tree)
869 {
870     tree_move_to_top (tree);
871     show_tree (tree);
872     maybe_chdir (tree);
873 }
874 
875 /* --------------------------------------------------------------------------------------------- */
876 
877 static inline void
tree_move_end(WTree * tree)878 tree_move_end (WTree * tree)
879 {
880     tree_move_to_bottom (tree);
881     show_tree (tree);
882     maybe_chdir (tree);
883 }
884 
885 /* --------------------------------------------------------------------------------------------- */
886 
887 static void
tree_move_pgup(WTree * tree)888 tree_move_pgup (WTree * tree)
889 {
890     tree_move_backward (tree, tlines (tree) - 1);
891     show_tree (tree);
892     maybe_chdir (tree);
893 }
894 
895 /* --------------------------------------------------------------------------------------------- */
896 
897 static void
tree_move_pgdn(WTree * tree)898 tree_move_pgdn (WTree * tree)
899 {
900     tree_move_forward (tree, tlines (tree) - 1);
901     show_tree (tree);
902     maybe_chdir (tree);
903 }
904 
905 /* --------------------------------------------------------------------------------------------- */
906 
907 static gboolean
tree_move_left(WTree * tree)908 tree_move_left (WTree * tree)
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
tree_move_right(WTree * tree)925 tree_move_right (WTree * tree)
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
tree_start_search(WTree * tree)943 tree_start_search (WTree * tree)
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
tree_toggle_navig(WTree * tree)975 tree_toggle_navig (WTree * tree)
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
tree_execute_cmd(WTree * tree,long command)992 tree_execute_cmd (WTree * tree, long command)
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
tree_key(WTree * tree,int key)1065 tree_key (WTree * tree, int key)
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
tree_frame(WDialog * h,WTree * tree)1117 tree_frame (WDialog * h, WTree * tree)
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
tree_callback(Widget * w,Widget * sender,widget_msg_t msg,int parm,void * data)1152 tree_callback (Widget * w, Widget * sender, widget_msg_t msg, int parm, void *data)
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
tree_mouse_callback(Widget * w,mouse_msg_t msg,mouse_event_t * event)1215 tree_mouse_callback (Widget * w, mouse_msg_t msg, mouse_event_t * event)
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 *
tree_new(int y,int x,int lines,int cols,gboolean is_panel)1281 tree_new (int y, int x, int lines, int cols, gboolean is_panel)
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
tree_chdir(WTree * tree,const vfs_path_t * dir)1310 tree_chdir (WTree * tree, const vfs_path_t * dir)
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 *
tree_selected_name(const WTree * tree)1326 tree_selected_name (const WTree * tree)
1327 {
1328     return tree->selected_ptr->name;
1329 }
1330 
1331 /* --------------------------------------------------------------------------------------------- */
1332 
1333 void
sync_tree(const vfs_path_t * vpath)1334 sync_tree (const vfs_path_t * vpath)
1335 {
1336     tree_chdir (the_tree, vpath);
1337 }
1338 
1339 /* --------------------------------------------------------------------------------------------- */
1340 
1341 WTree *
find_tree(const WDialog * h)1342 find_tree (const WDialog * h)
1343 {
1344     return (WTree *) widget_find_by_type (CONST_WIDGET (h), tree_callback);
1345 }
1346 
1347 /* --------------------------------------------------------------------------------------------- */
1348