do not enter a directory after rename
[geeqie.git] / src / view_dir.c
1 /*
2  * Geeqie
3  * Copyright (C) 2008 The Geeqie Team
4  *
5  * Author: Laurent Monin
6  *
7  * This software is released under the GNU General Public License (GNU GPL).
8  * Please read the included file COPYING for more information.
9  * This software comes with no warranty of any kind, use at your own risk!
10  */
11
12 #include "main.h"
13 #include "view_dir.h"
14
15 #include "dnd.h"
16 #include "dupe.h"
17 #include "editors.h"
18 #include "filedata.h"
19 #include "layout_image.h"
20 #include "layout_util.h"
21 #include "ui_fileops.h"
22 #include "ui_tree_edit.h"
23 #include "ui_menu.h"
24 #include "utilops.h"
25 #include "uri_utils.h"
26 #include "view_dir_list.h"
27 #include "view_dir_tree.h"
28
29 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data);
30
31 GtkRadioActionEntry menu_view_dir_radio_entries[] = {
32   { "FolderList",       NULL,           N_("_List"),            "<meta>L",      NULL, DIRVIEW_LIST },
33   { "FolderTree",       NULL,           N_("_Tree"),            "<control>T",   NULL, DIRVIEW_TREE },
34 };
35
36 static void vd_destroy_cb(GtkWidget *widget, gpointer data)
37 {
38         ViewDir *vd = data;
39
40         file_data_unregister_notify_func(vd_notify_cb, vd);
41
42         if (vd->popup)
43                 {
44                 g_signal_handlers_disconnect_matched(G_OBJECT(vd->popup), G_SIGNAL_MATCH_DATA,
45                                                      0, 0, 0, NULL, vd);
46                 gtk_widget_destroy(vd->popup);
47                 }
48
49         switch(vd->type)
50         {
51         case DIRVIEW_LIST: vdlist_destroy_cb(widget, data); break;
52         case DIRVIEW_TREE: vdtree_destroy_cb(widget, data); break;
53         }
54
55         if (vd->pf) folder_icons_free(vd->pf);
56         if (vd->drop_list) filelist_free(vd->drop_list);
57
58         if (vd->dir_fd) file_data_unref(vd->dir_fd);
59         if (vd->info) g_free(vd->info);
60
61         g_free(vd);
62 }
63
64 ViewDir *vd_new(DirViewType type, FileData *dir_fd)
65 {
66         g_assert(VIEW_DIR_TYPES_COUNT <= G_N_ELEMENTS(menu_view_dir_radio_entries));
67
68         ViewDir *vd = g_new0(ViewDir, 1);
69
70         vd->dir_fd = NULL;
71         vd->click_fd = NULL;
72
73         vd->drop_fd = NULL;
74         vd->drop_list = NULL;
75         vd->drop_scroll_id = -1;
76         vd->drop_list = NULL;
77
78         vd->popup = NULL;
79
80         vd->dnd_drop_leave_func = NULL;
81         vd->dnd_drop_update_func = NULL;
82
83         vd->widget = gtk_scrolled_window_new(NULL, NULL);
84         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vd->widget), GTK_SHADOW_IN);
85         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vd->widget),
86                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
87
88         vd->pf = folder_icons_new();
89
90         switch(type)
91         {
92         case DIRVIEW_LIST: vd = vdlist_new(vd, dir_fd); break;
93         case DIRVIEW_TREE: vd = vdtree_new(vd, dir_fd); break;
94         }
95
96         gtk_container_add(GTK_CONTAINER(vd->widget), vd->view);
97
98         vd_dnd_init(vd);
99
100         g_signal_connect(G_OBJECT(vd->view), "row_activated",
101                          G_CALLBACK(vd_activate_cb), vd);
102         g_signal_connect(G_OBJECT(vd->widget), "destroy",
103                          G_CALLBACK(vd_destroy_cb), vd);
104         g_signal_connect(G_OBJECT(vd->view), "key_press_event",
105                          G_CALLBACK(vd_press_key_cb), vd);
106         g_signal_connect(G_OBJECT(vd->view), "button_press_event",
107                          G_CALLBACK(vd_press_cb), vd);
108         g_signal_connect(G_OBJECT(vd->view), "button_release_event",
109                          G_CALLBACK(vd_release_cb), vd);
110
111         if (dir_fd) vd_set_fd(vd, dir_fd);
112
113         file_data_register_notify_func(vd_notify_cb, vd, NOTIFY_PRIORITY_HIGH);
114
115         gtk_widget_show(vd->view);
116
117         return vd;
118 }
119
120 void vd_set_select_func(ViewDir *vd,
121                         void (*func)(ViewDir *vd, const gchar *path, gpointer data), gpointer data)
122 {
123         vd->select_func = func;
124         vd->select_data = data;
125 }
126
127 void vd_set_layout(ViewDir *vd, LayoutWindow *layout)
128 {
129         vd->layout = layout;
130 }
131
132 gint vd_set_fd(ViewDir *vd, FileData *dir_fd)
133 {
134         gint ret = FALSE;
135
136         file_data_unregister_notify_func(vd_notify_cb, vd);
137
138         switch(vd->type)
139         {
140         case DIRVIEW_LIST: ret = vdlist_set_fd(vd, dir_fd); break;
141         case DIRVIEW_TREE: ret = vdtree_set_fd(vd, dir_fd); break;
142         }
143
144         file_data_register_notify_func(vd_notify_cb, vd, NOTIFY_PRIORITY_HIGH);
145
146         return ret;
147 }
148
149 void vd_refresh(ViewDir *vd)
150 {
151         switch(vd->type)
152         {
153         case DIRVIEW_LIST: vdlist_refresh(vd); break;
154         case DIRVIEW_TREE: vdtree_refresh(vd); break;
155         }
156 }
157
158 const gchar *vd_row_get_path(ViewDir *vd, gint row)
159 {
160         const gchar *ret = NULL;
161
162         switch(vd->type)
163         {
164         case DIRVIEW_LIST: ret = vdlist_row_get_path(vd, row); break;
165         case DIRVIEW_TREE: ret = vdtree_row_get_path(vd, row); break;
166         }
167
168         return ret;
169 }
170
171 void vd_select_row(ViewDir *vd, FileData *fd)
172 {
173         switch(vd->type)
174         {
175         case DIRVIEW_LIST: vdlist_select_row(vd, fd); break;
176         case DIRVIEW_TREE: vdtree_select_row(vd, fd); break;
177         }
178 }
179
180 gint vd_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter)
181 {
182         gint ret = FALSE;
183
184         switch(vd->type)
185         {
186         case DIRVIEW_LIST: ret = vdlist_find_row(vd, fd, iter); break;
187         case DIRVIEW_TREE: ret = vdtree_find_row(vd, fd, iter, NULL); break;
188         }
189
190         return ret;
191 }
192
193 FileData *vd_get_fd_from_tree_path(ViewDir *vd, GtkTreeView *tview, GtkTreePath *tpath)
194 {
195         GtkTreeIter iter;
196         FileData *fd = NULL;
197         GtkTreeModel *store;
198
199         store = gtk_tree_view_get_model(tview);
200         gtk_tree_model_get_iter(store, &iter, tpath);
201         switch (vd->type)
202                 {
203                 case DIRVIEW_LIST:
204                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
205                         break;
206                 case DIRVIEW_TREE:
207                         {
208                         NodeData *nd;
209                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
210                         fd = (nd) ? nd->fd : NULL;
211                         };
212                         break;
213                 }
214
215         return fd;
216 }
217
218 static gint vd_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data)
219 {
220         ViewDir *vd = data;
221         FileData *fd;
222         gchar *new_path;
223         gchar *base;
224
225         fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), td->path);
226         if (!fd) return FALSE;
227
228         base = remove_level_from_path(fd->path);
229         new_path = g_build_filename(base, new, NULL);
230         g_free(base);
231
232         file_util_rename_dir(fd, new_path, vd->view);
233         
234         g_free(new_path);
235
236         return FALSE;
237 }
238
239 static void vd_rename_by_data(ViewDir *vd, FileData *fd)
240 {
241         GtkTreeModel *store;
242         GtkTreePath *tpath;
243         GtkTreeIter iter;
244
245         if (!fd || vd_find_row(vd, fd, &iter) < 0) return;
246         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
247         tpath = gtk_tree_model_get_path(store, &iter);
248
249         tree_edit_by_path(GTK_TREE_VIEW(vd->view), tpath, 0, fd->name,
250                           vd_rename_cb, vd);
251         gtk_tree_path_free(tpath);
252 }
253
254
255 void vd_color_set(ViewDir *vd, FileData *fd, gint color_set)
256 {
257         GtkTreeModel *store;
258         GtkTreeIter iter;
259
260         if (vd_find_row(vd, fd, &iter) < 0) return;
261         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
262
263         switch(vd->type)
264         {
265         case DIRVIEW_LIST:
266                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
267                 break;
268         case DIRVIEW_TREE:
269                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
270                 break;
271         }
272 }
273
274 void vd_popup_destroy_cb(GtkWidget *widget, gpointer data)
275 {
276         ViewDir *vd = data;
277
278         vd_color_set(vd, vd->click_fd, FALSE);
279         vd->click_fd = NULL;
280         vd->popup = NULL;
281
282         vd_color_set(vd, vd->drop_fd, FALSE);
283         filelist_free(vd->drop_list);
284         vd->drop_list = NULL;
285         vd->drop_fd = NULL;
286 }
287
288 /*
289  *-----------------------------------------------------------------------------
290  * drop menu (from dnd)
291  *-----------------------------------------------------------------------------
292  */
293
294 static void vd_drop_menu_copy_cb(GtkWidget *widget, gpointer data)
295 {
296         ViewDir *vd = data;
297         const gchar *path;
298         GList *list;
299
300         if (!vd->drop_fd) return;
301
302         path = vd->drop_fd->path;
303         list = vd->drop_list;
304         vd->drop_list = NULL;
305
306         file_util_copy_simple(list, path, vd->widget);
307 }
308
309 static void vd_drop_menu_move_cb(GtkWidget *widget, gpointer data)
310 {
311         ViewDir *vd = data;
312         const gchar *path;
313         GList *list;
314
315         if (!vd->drop_fd) return;
316
317         path = vd->drop_fd->path;
318         list = vd->drop_list;
319
320         vd->drop_list = NULL;
321
322         file_util_move_simple(list, path, vd->widget);
323 }
324
325 static void vd_drop_menu_filter_cb(GtkWidget *widget, gpointer data)
326 {
327         ViewDir *vd = data;
328         const gchar *path;
329         GList *list;
330         guint n;
331
332         if (!vd->drop_fd) return;
333         
334         n = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "filter_idx"));
335         if (n == 0) return;
336         n--;
337
338         path = vd->drop_fd->path;
339         list = vd->drop_list;
340
341         vd->drop_list = NULL;
342
343         file_util_start_filter_from_filelist(n, list, path, vd->widget);
344 }
345
346
347
348 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
349 {
350         GtkWidget *menu;
351         guint i;
352
353         menu = popup_menu_short_lived();
354         g_signal_connect(G_OBJECT(menu), "destroy",
355                          G_CALLBACK(vd_popup_destroy_cb), vd);
356
357         menu_item_add_stock_sensitive(menu, _("_Copy"), GTK_STOCK_COPY, active,
358                                       G_CALLBACK(vd_drop_menu_copy_cb), vd);
359         menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
360
361         for (i = 0; i < GQ_EDITOR_GENERIC_SLOTS; i++)
362                 {
363                 GtkWidget *item;
364
365                 const gchar *name = editor_get_name(i);
366                 if (!name || !editor_is_filter(i)) continue;
367
368                 item = menu_item_add_sensitive(menu, name, active, G_CALLBACK(vd_drop_menu_filter_cb), vd);
369
370                 g_object_set_data(G_OBJECT(item), "filter_idx", GUINT_TO_POINTER(i + 1));
371                 }
372
373         menu_item_add_divider(menu);
374         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, vd);
375
376         return menu;
377 }
378
379 /*
380  *-----------------------------------------------------------------------------
381  * pop-up menu
382  *-----------------------------------------------------------------------------
383  */
384
385 static void vd_pop_menu_up_cb(GtkWidget *widget, gpointer data)
386 {
387         ViewDir *vd = data;
388         gchar *path;
389
390         if (!vd->dir_fd || strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) == 0) return;
391         path = remove_level_from_path(vd->dir_fd->path);
392
393         if (vd->select_func)
394                 {
395                 vd->select_func(vd, path, vd->select_data);
396                 }
397
398         g_free(path);
399 }
400
401 static void vd_pop_menu_slide_cb(GtkWidget *widget, gpointer data)
402 {
403         ViewDir *vd = data;
404
405         if (!vd->layout) return;
406         if (!vd->click_fd) return;
407
408         layout_set_fd(vd->layout, vd->click_fd);
409         layout_select_none(vd->layout);
410         layout_image_slideshow_stop(vd->layout);
411         layout_image_slideshow_start(vd->layout);
412 }
413
414 static void vd_pop_menu_slide_rec_cb(GtkWidget *widget, gpointer data)
415 {
416         ViewDir *vd = data;
417         GList *list;
418
419         if (!vd->layout) return;
420         if (!vd->click_fd) return;
421
422         list = filelist_recursive(vd->click_fd);
423
424         layout_image_slideshow_stop(vd->layout);
425         layout_image_slideshow_start_from_list(vd->layout, list);
426 }
427
428 static void vd_pop_menu_dupe(ViewDir *vd, gint recursive)
429 {
430         DupeWindow *dw;
431         GList *list = NULL;
432
433         if (!vd->click_fd) return;
434
435         if (recursive)
436                 {
437                 list = g_list_append(list, file_data_ref(vd->click_fd));
438                 }
439         else
440                 {
441                 filelist_read(vd->click_fd, &list, NULL);
442                 list = filelist_filter(list, FALSE);
443                 }
444
445         dw = dupe_window_new(DUPE_MATCH_NAME);
446         dupe_window_add_files(dw, list, recursive);
447
448         filelist_free(list);
449 }
450
451 static void vd_pop_menu_dupe_cb(GtkWidget *widget, gpointer data)
452 {
453         ViewDir *vd = data;
454         vd_pop_menu_dupe(vd, FALSE);
455 }
456
457 static void vd_pop_menu_dupe_rec_cb(GtkWidget *widget, gpointer data)
458 {
459         ViewDir *vd = data;
460         vd_pop_menu_dupe(vd, TRUE);
461 }
462
463 static void vd_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
464 {
465         ViewDir *vd = data;
466
467         if (!vd->click_fd) return;
468         file_util_delete_dir(vd->click_fd, vd->widget);
469 }
470
471 static void vd_pop_menu_copy_path_cb(GtkWidget *widget, gpointer data)
472 {
473         ViewDir *vd = data;
474
475         if (!vd->click_fd) return;
476
477         file_util_copy_path_to_clipboard(vd->click_fd);
478 }
479
480 #define VIEW_DIR_AS_SUBMENU_KEY "view_dir_as_submenu"
481 static void vd_pop_submenu_dir_view_as_cb(GtkWidget *widget, gpointer data)
482 {
483         ViewDir *vd = data;
484
485         DirViewType new_type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), VIEW_DIR_AS_SUBMENU_KEY));
486         layout_views_set(vd->layout, new_type, vd->layout->file_view_type);
487 }
488
489 static void vd_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
490 {
491         ViewDir *vd = data;
492
493         if (vd->layout) layout_refresh(vd->layout);
494 }
495
496 static void vd_toggle_show_hidden_files_cb(GtkWidget *widget, gpointer data)
497 {
498         ViewDir *vd = data;
499
500         options->file_filter.show_hidden_files = !options->file_filter.show_hidden_files;
501         if (vd->layout) layout_refresh(vd->layout);
502 }
503
504 static void vd_pop_menu_new_rename_cb(gboolean success, const gchar *new_path, gpointer data)
505 {
506         ViewDir *vd = data;
507         FileData *fd = NULL;
508         if (!success) return;
509
510         switch(vd->type)
511                 {
512                 case DIRVIEW_LIST:
513                         {
514                         vd_refresh(vd);
515                         fd = vdlist_row_by_path(vd, new_path, NULL);
516                         };
517                         break;
518                 case DIRVIEW_TREE:
519                         {
520                         FileData *new_fd = file_data_new_simple(new_path);
521                         fd = vdtree_populate_path(vd, new_fd, TRUE, TRUE);
522                         file_data_unref(new_fd);
523                         }
524                         break;
525                 }
526         vd_rename_by_data(vd, fd);
527 }
528
529 static void vd_pop_menu_new_cb(GtkWidget *widget, gpointer data)
530 {
531         ViewDir *vd = data;
532         FileData *dir_fd = NULL;
533
534         switch(vd->type)
535                 {
536                 case DIRVIEW_LIST:
537                         {
538                         if (!vd->dir_fd) return;
539                         dir_fd = vd->dir_fd;
540                         };
541                         break;
542                 case DIRVIEW_TREE:
543                         {
544                         if (!vd->click_fd) return;
545                         dir_fd = vd->click_fd;
546                         };
547                         break;
548                 }
549
550         file_util_create_dir(dir_fd, widget, vd_pop_menu_new_rename_cb, vd);
551 }
552
553 static void vd_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
554 {
555         ViewDir *vd = data;
556
557         vd_rename_by_data(vd, vd->click_fd);
558 }
559
560 GtkWidget *vd_pop_menu(ViewDir *vd, FileData *fd)
561 {
562         GtkWidget *menu;
563         GtkWidget *submenu;
564         GtkWidget *item;
565         gint active;
566         gint rename_delete_active = FALSE;
567         gint new_folder_active = FALSE;
568         gint i;
569
570         active = (fd != NULL);
571         switch(vd->type)
572                 {
573                 case DIRVIEW_LIST:
574                         {
575                         /* check using . (always row 0) */
576                         new_folder_active = (vd->dir_fd && access_file(vd->dir_fd->path , W_OK | X_OK));
577
578                         /* ignore .. and . */
579                         rename_delete_active = (new_folder_active && fd &&
580                                 strcmp(fd->name, ".") != 0 &&
581                                 strcmp(fd->name, "..") != 0 &&
582                                 access_file(fd->path, W_OK | X_OK));
583                         };
584                         break;
585                 case DIRVIEW_TREE:
586                         {
587                         if (fd)
588                                 {
589                                 gchar *parent;
590                                 new_folder_active = (fd && access_file(fd->path, W_OK | X_OK));
591                                 parent = remove_level_from_path(fd->path);
592                                 rename_delete_active = access_file(parent, W_OK | X_OK);
593                                 g_free(parent);
594                                 };
595                         }
596                         break;
597                 }
598
599         menu = popup_menu_short_lived();
600         g_signal_connect(G_OBJECT(menu), "destroy",
601                          G_CALLBACK(vd_popup_destroy_cb), vd);
602
603         menu_item_add_stock_sensitive(menu, _("_Up to parent"), GTK_STOCK_GO_UP,
604                                       (vd->dir_fd && strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) != 0),
605                                       G_CALLBACK(vd_pop_menu_up_cb), vd);
606
607         menu_item_add_divider(menu);
608         menu_item_add_sensitive(menu, _("_Slideshow"), active,
609                                 G_CALLBACK(vd_pop_menu_slide_cb), vd);
610         menu_item_add_sensitive(menu, _("Slideshow recursive"), active,
611                                 G_CALLBACK(vd_pop_menu_slide_rec_cb), vd);
612
613         menu_item_add_divider(menu);
614         menu_item_add_stock_sensitive(menu, _("Find _duplicates..."), GTK_STOCK_FIND, active,
615                                       G_CALLBACK(vd_pop_menu_dupe_cb), vd);
616         menu_item_add_stock_sensitive(menu, _("Find duplicates recursive..."), GTK_STOCK_FIND, active,
617                                       G_CALLBACK(vd_pop_menu_dupe_rec_cb), vd);
618
619         menu_item_add_divider(menu);
620
621         menu_item_add_sensitive(menu, _("_New folder..."), new_folder_active,
622                                 G_CALLBACK(vd_pop_menu_new_cb), vd);
623
624         menu_item_add_sensitive(menu, _("_Rename..."), rename_delete_active,
625                                 G_CALLBACK(vd_pop_menu_rename_cb), vd);
626         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, rename_delete_active,
627                                       G_CALLBACK(vd_pop_menu_delete_cb), vd);
628
629         if (options->show_copy_path)
630                 menu_item_add(menu, _("_Copy path"),
631                               G_CALLBACK(vd_pop_menu_copy_path_cb), vd);
632
633         menu_item_add_divider(menu);
634
635         item = menu_item_add(menu, _("_View as"), NULL, NULL);
636         submenu = gtk_menu_new();
637         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
638
639         for (i = 0; i < VIEW_DIR_TYPES_COUNT; i++)
640                 {
641                 item = menu_item_add_check(submenu, _(menu_view_dir_radio_entries[i].label),
642                                            ((gint) vd->type == menu_view_dir_radio_entries[i].value),
643                                            G_CALLBACK(vd_pop_submenu_dir_view_as_cb), vd);
644                 g_object_set_data(G_OBJECT(item), VIEW_DIR_AS_SUBMENU_KEY, GINT_TO_POINTER(menu_view_dir_radio_entries[i].value));
645                 }
646
647         menu_item_add_check(menu, _("Show _hidden files"), options->file_filter.show_hidden_files,
648                             G_CALLBACK(vd_toggle_show_hidden_files_cb), vd);
649
650         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH,
651                             G_CALLBACK(vd_pop_menu_refresh_cb), vd);
652
653         return menu;
654 }
655
656 /*
657  *-----------------------------------------------------------------------------
658  * dnd
659  *-----------------------------------------------------------------------------
660  */
661
662 static GtkTargetEntry vd_dnd_drop_types[] = {
663         { "text/uri-list", 0, TARGET_URI_LIST }
664 };
665 static gint vd_dnd_drop_types_count = 1;
666
667 static void vd_dest_set(ViewDir *vd, gint enable)
668 {
669         if (enable)
670                 {
671                 gtk_drag_dest_set(vd->view,
672                                   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
673                                   vd_dnd_drop_types, vd_dnd_drop_types_count,
674                                   GDK_ACTION_MOVE | GDK_ACTION_COPY);
675                 }
676         else
677                 {
678                 gtk_drag_dest_unset(vd->view);
679                 }
680 }
681
682 static void vd_dnd_get(GtkWidget *widget, GdkDragContext *context,
683                            GtkSelectionData *selection_data, guint info,
684                            guint time, gpointer data)
685 {
686         ViewDir *vd = data;
687         GList *list;
688         gchar *uritext = NULL;
689         gint length = 0;
690
691         if (!vd->click_fd) return;
692
693         switch (info)
694                 {
695                 case TARGET_URI_LIST:
696                 case TARGET_TEXT_PLAIN:
697                         list = g_list_prepend(NULL, vd->click_fd);
698                         uritext = uri_text_from_filelist(list, &length, (info == TARGET_TEXT_PLAIN));
699                         g_list_free(list);
700                         break;
701                 }
702         if (uritext)
703                 {
704                 gtk_selection_data_set(selection_data, selection_data->target,
705                                        8, (guchar *)uritext, length);
706                 g_free(uritext);
707                 }
708 }
709
710 static void vd_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
711 {
712         ViewDir *vd = data;
713
714         vd_color_set(vd, vd->click_fd, TRUE);
715         vd_dest_set(vd, FALSE);
716 }
717
718 static void vd_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
719 {
720         ViewDir *vd = data;
721
722         vd_color_set(vd, vd->click_fd, FALSE);
723
724         if (vd->type == DIRVIEW_LIST && context->action == GDK_ACTION_MOVE)
725                 {
726                 vd_refresh(vd);
727                 }
728         vd_dest_set(vd, TRUE);
729 }
730
731 static void vd_dnd_drop_receive(GtkWidget *widget,
732                                 GdkDragContext *context, gint x, gint y,
733                                 GtkSelectionData *selection_data, guint info,
734                                 guint time, gpointer data)
735 {
736         ViewDir *vd = data;
737         GtkTreePath *tpath;
738         FileData *fd = NULL;
739
740         vd->click_fd = NULL;
741
742         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y,
743                                           &tpath, NULL, NULL, NULL))
744                 {
745                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
746                 gtk_tree_path_free(tpath);
747                 }
748
749         if (!fd) return;
750
751         if (info == TARGET_URI_LIST)
752                 {
753                 GList *list;
754                 gint active;
755                 gint done = FALSE;
756
757                 list = uri_filelist_from_text((gchar *)selection_data->data, TRUE);
758                 if (!list) return;
759
760                 active = access_file(fd->path, W_OK | X_OK);
761
762                 vd_color_set(vd, fd, TRUE);
763
764                 if (active)
765                         {
766                         if (context->actions == GDK_ACTION_COPY)
767                                 {
768                                 file_util_copy_simple(list, fd->path, vd->widget);
769                                 done = TRUE;
770                                 }
771                         else if (context->actions == GDK_ACTION_MOVE)
772                                 {
773                                 file_util_move_simple(list, fd->path, vd->widget);
774                                 done = TRUE;
775                                 }
776                         }
777
778                 if (done == FALSE)
779                         {
780                         vd->popup = vd_drop_menu(vd, active);
781                         gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL, 0, time);
782                         }
783
784                 vd->drop_fd = fd;
785                 vd->drop_list = list;
786                 }
787 }
788
789 static void vd_dnd_drop_update(ViewDir *vd, gint x, gint y)
790 {
791         GtkTreePath *tpath;
792         FileData *fd = NULL;
793
794         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vd->view), x, y,
795                                           &tpath, NULL, NULL, NULL))
796                 {
797                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), tpath);
798                 gtk_tree_path_free(tpath);
799                 }
800
801         if (fd != vd->drop_fd)
802                 {
803                 vd_color_set(vd, vd->drop_fd, FALSE);
804                 vd_color_set(vd, fd, TRUE);
805                 if (fd && vd->dnd_drop_update_func) vd->dnd_drop_update_func(vd);
806                 }
807
808         vd->drop_fd = fd;
809 }
810
811 void vd_dnd_drop_scroll_cancel(ViewDir *vd)
812 {
813         if (vd->drop_scroll_id != -1) g_source_remove(vd->drop_scroll_id);
814         vd->drop_scroll_id = -1;
815 }
816
817 static gint vd_auto_scroll_idle_cb(gpointer data)
818 {
819         ViewDir *vd = data;
820
821         if (vd->drop_fd)
822                 {
823                 GdkWindow *window;
824                 gint x, y;
825                 gint w, h;
826
827                 window = vd->view->window;
828                 gdk_window_get_pointer(window, &x, &y, NULL);
829                 gdk_drawable_get_size(window, &w, &h);
830                 if (x >= 0 && x < w && y >= 0 && y < h)
831                         {
832                         vd_dnd_drop_update(vd, x, y);
833                         }
834                 }
835
836         vd->drop_scroll_id = -1;
837         return FALSE;
838 }
839
840 static gint vd_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data)
841 {
842         ViewDir *vd = data;
843
844         if (!vd->drop_fd || vd->drop_list) return FALSE;
845
846         if (vd->drop_scroll_id == -1) vd->drop_scroll_id = g_idle_add(vd_auto_scroll_idle_cb, vd);
847
848         return TRUE;
849 }
850
851 static gint vd_dnd_drop_motion(GtkWidget *widget, GdkDragContext *context,
852                                    gint x, gint y, guint time, gpointer data)
853 {
854         ViewDir *vd = data;
855
856         vd->click_fd = NULL;
857
858         if (gtk_drag_get_source_widget(context) == vd->view)
859                 {
860                 /* from same window */
861                 gdk_drag_status(context, 0, time);
862                 return TRUE;
863                 }
864         else
865                 {
866                 gdk_drag_status(context, context->suggested_action, time);
867                 }
868
869         vd_dnd_drop_update(vd, x, y);
870
871         if (vd->drop_fd)
872                 {
873                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vd->view));
874                 widget_auto_scroll_start(vd->view, adj, -1, -1, vd_auto_scroll_notify_cb, vd);
875                 }
876
877         return FALSE;
878 }
879
880 static void vd_dnd_drop_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data)
881 {
882         ViewDir *vd = data;
883
884         if (vd->drop_fd != vd->click_fd) vd_color_set(vd, vd->drop_fd, FALSE);
885
886         vd->drop_fd = NULL;
887
888         if (vd->dnd_drop_leave_func) vd->dnd_drop_leave_func(vd);
889 }
890
891 void vd_dnd_init(ViewDir *vd)
892 {
893         gtk_drag_source_set(vd->view, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
894                             dnd_file_drag_types, dnd_file_drag_types_count,
895                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
896         g_signal_connect(G_OBJECT(vd->view), "drag_data_get",
897                          G_CALLBACK(vd_dnd_get), vd);
898         g_signal_connect(G_OBJECT(vd->view), "drag_begin",
899                          G_CALLBACK(vd_dnd_begin), vd);
900         g_signal_connect(G_OBJECT(vd->view), "drag_end",
901                          G_CALLBACK(vd_dnd_end), vd);
902
903         vd_dest_set(vd, TRUE);
904         g_signal_connect(G_OBJECT(vd->view), "drag_data_received",
905                          G_CALLBACK(vd_dnd_drop_receive), vd);
906         g_signal_connect(G_OBJECT(vd->view), "drag_motion",
907                          G_CALLBACK(vd_dnd_drop_motion), vd);
908         g_signal_connect(G_OBJECT(vd->view), "drag_leave",
909                          G_CALLBACK(vd_dnd_drop_leave), vd);
910 }
911
912 /*
913  *----------------------------------------------------------------------------
914  * callbacks
915  *----------------------------------------------------------------------------
916  */
917
918 void vd_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
919 {
920         ViewDir *vd = data;
921         GtkTreeModel *store;
922         GtkTreeIter iter;
923         GtkTreePath *tpath;
924         gint cw, ch;
925
926         if (vd_find_row(vd, vd->click_fd, &iter) < 0) return;
927         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
928         tpath = gtk_tree_model_get_path(store, &iter);
929         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
930         gtk_tree_path_free(tpath);
931         *y += ch;
932         popup_menu_position_clamp(menu, x, y, 0);
933 }
934
935 void vd_activate_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *column, gpointer data)
936 {
937         ViewDir *vd = data;
938         FileData *fd = vd_get_fd_from_tree_path(vd, tview, tpath);
939
940         vd_select_row(vd, fd);
941 }
942
943 static GdkColor *vd_color_shifted(GtkWidget *widget)
944 {
945         static GdkColor color;
946         static GtkWidget *done = NULL;
947
948         if (done != widget)
949                 {
950                 GtkStyle *style;
951
952                 style = gtk_widget_get_style(widget);
953                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
954                 shift_color(&color, -1, 0);
955                 done = widget;
956                 }
957
958         return &color;
959 }
960
961 void vd_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
962                  GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
963 {
964         ViewDir *vd = data;
965         gboolean set;
966
967         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
968         g_object_set(G_OBJECT(cell),
969                      "cell-background-gdk", vd_color_shifted(vd->view),
970                      "cell-background-set", set, NULL);
971 }
972
973 gint vd_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
974 {
975         ViewDir *vd = data;
976         GtkTreePath *tpath;
977         FileData *fd = NULL;
978
979         if (!vd->click_fd) return FALSE;
980         vd_color_set(vd, vd->click_fd, FALSE);
981
982         if (bevent->button != MOUSE_BUTTON_LEFT) return TRUE;
983
984         if ((bevent->x != 0 || bevent->y != 0) &&
985             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
986                                           &tpath, NULL, NULL, NULL))
987                 {
988                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
989                 gtk_tree_path_free(tpath);
990                 }
991
992         if (fd && vd->click_fd == fd)
993                 {
994                 vdlist_select_row(vd, vd->click_fd);
995                 }
996
997         return FALSE;
998 }
999
1000 gint vd_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1001 {
1002         ViewDir *vd = data;
1003         gint ret = FALSE;
1004
1005         switch(vd->type)
1006         {
1007         case DIRVIEW_LIST: ret = vdlist_press_key_cb(widget, event, data); break;
1008         case DIRVIEW_TREE: ret = vdtree_press_key_cb(widget, event, data); break;
1009         }
1010
1011         return ret;
1012 }
1013
1014 gint vd_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1015 {
1016         ViewDir *vd = data;
1017         gint ret = FALSE;
1018
1019         switch(vd->type)
1020         {
1021         case DIRVIEW_LIST: ret = vdlist_press_cb(widget, bevent, data); break;
1022         case DIRVIEW_TREE: ret = vdtree_press_cb(widget, bevent, data); break;
1023         }
1024
1025         return ret;
1026 }
1027
1028 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data)
1029 {
1030         ViewDir *vd = data;
1031         gboolean refresh;
1032         gchar *base;
1033
1034         if (!S_ISDIR(fd->mode)) return; /* this gives correct results even on recently deleted files/directories */
1035
1036         base = remove_level_from_path(fd->path);
1037
1038         if (vd->type == DIRVIEW_LIST)
1039                 {
1040                 refresh = (fd == vd->dir_fd);
1041
1042                 if (!refresh)
1043                         {
1044                         refresh = (strcmp(base, vd->dir_fd->path) == 0);
1045                         }
1046
1047                 if (type == NOTIFY_TYPE_CHANGE && fd->change)
1048                         {
1049                         if (!refresh && fd->change->dest)
1050                                 {
1051                                 gchar *dest_base = remove_level_from_path(fd->change->dest);
1052                                 refresh = (strcmp(dest_base, vd->dir_fd->path) == 0);
1053                                 g_free(dest_base);
1054                                 }
1055
1056                         if (!refresh && fd->change->source)
1057                                 {
1058                                 gchar *source_base = remove_level_from_path(fd->change->source);
1059                                 refresh = (strcmp(source_base, vd->dir_fd->path) == 0);
1060                                 g_free(source_base);
1061                                 }
1062                         }
1063                 
1064                 if (refresh) vd_refresh(vd);
1065                 }
1066
1067         if (vd->type == DIRVIEW_TREE)
1068                 {
1069                 GtkTreeIter iter;
1070                 FileData *base_fd = file_data_new_simple(base);
1071
1072                 if (vd_find_row(vd, base_fd, &iter))
1073                         {
1074                         vdtree_populate_path_by_iter(vd, &iter, TRUE, vd->dir_fd);
1075                         }
1076
1077                 file_data_unref(base_fd);
1078                 }
1079
1080         g_free(base);
1081 }
1082 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */