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