added done callback to utilops
[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         if (vd->layout && vd->dir_fd != fd)
235                 {
236                 layout_set_path(vd->layout, new_path);
237                 }
238
239         g_free(new_path);
240
241         return FALSE;
242 }
243
244 static void vd_rename_by_data(ViewDir *vd, FileData *fd)
245 {
246         GtkTreeModel *store;
247         GtkTreePath *tpath;
248         GtkTreeIter iter;
249
250         if (!fd || vd_find_row(vd, fd, &iter) < 0) return;
251         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
252         tpath = gtk_tree_model_get_path(store, &iter);
253
254         tree_edit_by_path(GTK_TREE_VIEW(vd->view), tpath, 0, fd->name,
255                           vd_rename_cb, vd);
256         gtk_tree_path_free(tpath);
257 }
258
259
260 void vd_color_set(ViewDir *vd, FileData *fd, gint color_set)
261 {
262         GtkTreeModel *store;
263         GtkTreeIter iter;
264
265         if (vd_find_row(vd, fd, &iter) < 0) return;
266         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
267
268         switch(vd->type)
269         {
270         case DIRVIEW_LIST:
271                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
272                 break;
273         case DIRVIEW_TREE:
274                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
275                 break;
276         }
277 }
278
279 void vd_popup_destroy_cb(GtkWidget *widget, gpointer data)
280 {
281         ViewDir *vd = data;
282
283         vd_color_set(vd, vd->click_fd, FALSE);
284         vd->click_fd = NULL;
285         vd->popup = NULL;
286
287         vd_color_set(vd, vd->drop_fd, FALSE);
288         filelist_free(vd->drop_list);
289         vd->drop_list = NULL;
290         vd->drop_fd = NULL;
291 }
292
293 /*
294  *-----------------------------------------------------------------------------
295  * drop menu (from dnd)
296  *-----------------------------------------------------------------------------
297  */
298
299 static void vd_drop_menu_copy_cb(GtkWidget *widget, gpointer data)
300 {
301         ViewDir *vd = data;
302         const gchar *path;
303         GList *list;
304
305         if (!vd->drop_fd) return;
306
307         path = vd->drop_fd->path;
308         list = vd->drop_list;
309         vd->drop_list = NULL;
310
311         file_util_copy_simple(list, path, vd->widget);
312 }
313
314 static void vd_drop_menu_move_cb(GtkWidget *widget, gpointer data)
315 {
316         ViewDir *vd = data;
317         const gchar *path;
318         GList *list;
319
320         if (!vd->drop_fd) return;
321
322         path = vd->drop_fd->path;
323         list = vd->drop_list;
324
325         vd->drop_list = NULL;
326
327         file_util_move_simple(list, path, vd->widget);
328 }
329
330 static void vd_drop_menu_filter_cb(GtkWidget *widget, gpointer data)
331 {
332         ViewDir *vd = data;
333         const gchar *path;
334         GList *list;
335         guint n;
336
337         if (!vd->drop_fd) return;
338         
339         n = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(widget), "filter_idx"));
340         if (n == 0) return;
341         n--;
342
343         path = vd->drop_fd->path;
344         list = vd->drop_list;
345
346         vd->drop_list = NULL;
347
348         file_util_start_filter_from_filelist(n, list, path, vd->widget);
349 }
350
351
352
353 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
354 {
355         GtkWidget *menu;
356         guint i;
357
358         menu = popup_menu_short_lived();
359         g_signal_connect(G_OBJECT(menu), "destroy",
360                          G_CALLBACK(vd_popup_destroy_cb), vd);
361
362         menu_item_add_stock_sensitive(menu, _("_Copy"), GTK_STOCK_COPY, active,
363                                       G_CALLBACK(vd_drop_menu_copy_cb), vd);
364         menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
365
366         for (i = 0; i < GQ_EDITOR_GENERIC_SLOTS; i++)
367                 {
368                 GtkWidget *item;
369
370                 const gchar *name = editor_get_name(i);
371                 if (!name || !editor_is_filter(i)) continue;
372
373                 item = menu_item_add_sensitive(menu, name, active, G_CALLBACK(vd_drop_menu_filter_cb), vd);
374
375                 g_object_set_data(G_OBJECT(item), "filter_idx", GUINT_TO_POINTER(i + 1));
376                 }
377
378         menu_item_add_divider(menu);
379         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, vd);
380
381         return menu;
382 }
383
384 /*
385  *-----------------------------------------------------------------------------
386  * pop-up menu
387  *-----------------------------------------------------------------------------
388  */
389
390 static void vd_pop_menu_up_cb(GtkWidget *widget, gpointer data)
391 {
392         ViewDir *vd = data;
393         gchar *path;
394
395         if (!vd->dir_fd || strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) == 0) return;
396         path = remove_level_from_path(vd->dir_fd->path);
397
398         if (vd->select_func)
399                 {
400                 vd->select_func(vd, path, vd->select_data);
401                 }
402
403         g_free(path);
404 }
405
406 static void vd_pop_menu_slide_cb(GtkWidget *widget, gpointer data)
407 {
408         ViewDir *vd = data;
409
410         if (!vd->layout) return;
411         if (!vd->click_fd) return;
412
413         layout_set_fd(vd->layout, vd->click_fd);
414         layout_select_none(vd->layout);
415         layout_image_slideshow_stop(vd->layout);
416         layout_image_slideshow_start(vd->layout);
417 }
418
419 static void vd_pop_menu_slide_rec_cb(GtkWidget *widget, gpointer data)
420 {
421         ViewDir *vd = data;
422         GList *list;
423
424         if (!vd->layout) return;
425         if (!vd->click_fd) return;
426
427         list = filelist_recursive(vd->click_fd);
428
429         layout_image_slideshow_stop(vd->layout);
430         layout_image_slideshow_start_from_list(vd->layout, list);
431 }
432
433 static void vd_pop_menu_dupe(ViewDir *vd, gint recursive)
434 {
435         DupeWindow *dw;
436         GList *list = NULL;
437
438         if (!vd->click_fd) return;
439
440         if (recursive)
441                 {
442                 list = g_list_append(list, file_data_ref(vd->click_fd));
443                 }
444         else
445                 {
446                 filelist_read(vd->click_fd, &list, NULL);
447                 list = filelist_filter(list, FALSE);
448                 }
449
450         dw = dupe_window_new(DUPE_MATCH_NAME);
451         dupe_window_add_files(dw, list, recursive);
452
453         filelist_free(list);
454 }
455
456 static void vd_pop_menu_dupe_cb(GtkWidget *widget, gpointer data)
457 {
458         ViewDir *vd = data;
459         vd_pop_menu_dupe(vd, FALSE);
460 }
461
462 static void vd_pop_menu_dupe_rec_cb(GtkWidget *widget, gpointer data)
463 {
464         ViewDir *vd = data;
465         vd_pop_menu_dupe(vd, TRUE);
466 }
467
468 static void vd_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
469 {
470         ViewDir *vd = data;
471
472         if (!vd->click_fd) return;
473         file_util_delete_dir(vd->click_fd, vd->widget);
474 }
475
476 static void vd_pop_menu_copy_path_cb(GtkWidget *widget, gpointer data)
477 {
478         ViewDir *vd = data;
479
480         if (!vd->click_fd) return;
481
482         file_util_copy_path_to_clipboard(vd->click_fd);
483 }
484
485 #define VIEW_DIR_AS_SUBMENU_KEY "view_dir_as_submenu"
486 static void vd_pop_submenu_dir_view_as_cb(GtkWidget *widget, gpointer data)
487 {
488         ViewDir *vd = data;
489
490         DirViewType new_type = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), VIEW_DIR_AS_SUBMENU_KEY));
491         layout_views_set(vd->layout, new_type, vd->layout->file_view_type);
492 }
493
494 static void vd_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
495 {
496         ViewDir *vd = data;
497
498         if (vd->layout) layout_refresh(vd->layout);
499 }
500
501 static void vd_toggle_show_hidden_files_cb(GtkWidget *widget, gpointer data)
502 {
503         ViewDir *vd = data;
504
505         options->file_filter.show_hidden_files = !options->file_filter.show_hidden_files;
506         if (vd->layout) layout_refresh(vd->layout);
507 }
508
509 static void vd_pop_menu_new_rename_cb(gboolean success, const gchar *new_path, gpointer data)
510 {
511         ViewDir *vd = data;
512         FileData *fd = NULL;
513         if (!success) return;
514
515         switch(vd->type)
516                 {
517                 case DIRVIEW_LIST:
518                         {
519                         vd_refresh(vd);
520                         fd = vdlist_row_by_path(vd, new_path, NULL);
521                         };
522                         break;
523                 case DIRVIEW_TREE:
524                         {
525                         FileData *new_fd = file_data_new_simple(new_path);
526                         fd = vdtree_populate_path(vd, new_fd, TRUE, TRUE);
527                         file_data_unref(new_fd);
528                         }
529                         break;
530                 }
531         vd_rename_by_data(vd, fd);
532 }
533
534 static void vd_pop_menu_new_cb(GtkWidget *widget, gpointer data)
535 {
536         ViewDir *vd = data;
537         FileData *dir_fd = NULL;
538
539         switch(vd->type)
540                 {
541                 case DIRVIEW_LIST:
542                         {
543                         if (!vd->dir_fd) return;
544                         dir_fd = vd->dir_fd;
545                         };
546                         break;
547                 case DIRVIEW_TREE:
548                         {
549                         if (!vd->click_fd) return;
550                         dir_fd = vd->click_fd;
551                         };
552                         break;
553                 }
554
555         file_util_create_dir(dir_fd, widget, vd_pop_menu_new_rename_cb, vd);
556 }
557
558 static void vd_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
559 {
560         ViewDir *vd = data;
561
562         vd_rename_by_data(vd, vd->click_fd);
563 }
564
565 GtkWidget *vd_pop_menu(ViewDir *vd, FileData *fd)
566 {
567         GtkWidget *menu;
568         GtkWidget *submenu;
569         GtkWidget *item;
570         gint active;
571         gint rename_delete_active = FALSE;
572         gint new_folder_active = FALSE;
573         gint i;
574
575         active = (fd != NULL);
576         switch(vd->type)
577                 {
578                 case DIRVIEW_LIST:
579                         {
580                         /* check using . (always row 0) */
581                         new_folder_active = (vd->dir_fd && access_file(vd->dir_fd->path , W_OK | X_OK));
582
583                         /* ignore .. and . */
584                         rename_delete_active = (new_folder_active && fd &&
585                                 strcmp(fd->name, ".") != 0 &&
586                                 strcmp(fd->name, "..") != 0 &&
587                                 access_file(fd->path, W_OK | X_OK));
588                         };
589                         break;
590                 case DIRVIEW_TREE:
591                         {
592                         if (fd)
593                                 {
594                                 gchar *parent;
595                                 new_folder_active = (fd && access_file(fd->path, W_OK | X_OK));
596                                 parent = remove_level_from_path(fd->path);
597                                 rename_delete_active = access_file(parent, W_OK | X_OK);
598                                 g_free(parent);
599                                 };
600                         }
601                         break;
602                 }
603
604         menu = popup_menu_short_lived();
605         g_signal_connect(G_OBJECT(menu), "destroy",
606                          G_CALLBACK(vd_popup_destroy_cb), vd);
607
608         menu_item_add_stock_sensitive(menu, _("_Up to parent"), GTK_STOCK_GO_UP,
609                                       (vd->dir_fd && strcmp(vd->dir_fd->path, G_DIR_SEPARATOR_S) != 0),
610                                       G_CALLBACK(vd_pop_menu_up_cb), vd);
611
612         menu_item_add_divider(menu);
613         menu_item_add_sensitive(menu, _("_Slideshow"), active,
614                                 G_CALLBACK(vd_pop_menu_slide_cb), vd);
615         menu_item_add_sensitive(menu, _("Slideshow recursive"), active,
616                                 G_CALLBACK(vd_pop_menu_slide_rec_cb), vd);
617
618         menu_item_add_divider(menu);
619         menu_item_add_stock_sensitive(menu, _("Find _duplicates..."), GTK_STOCK_FIND, active,
620                                       G_CALLBACK(vd_pop_menu_dupe_cb), vd);
621         menu_item_add_stock_sensitive(menu, _("Find duplicates recursive..."), GTK_STOCK_FIND, active,
622                                       G_CALLBACK(vd_pop_menu_dupe_rec_cb), vd);
623
624         menu_item_add_divider(menu);
625
626         menu_item_add_sensitive(menu, _("_New folder..."), new_folder_active,
627                                 G_CALLBACK(vd_pop_menu_new_cb), vd);
628
629         menu_item_add_sensitive(menu, _("_Rename..."), rename_delete_active,
630                                 G_CALLBACK(vd_pop_menu_rename_cb), vd);
631         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, rename_delete_active,
632                                       G_CALLBACK(vd_pop_menu_delete_cb), vd);
633
634         if (options->show_copy_path)
635                 menu_item_add(menu, _("_Copy path"),
636                               G_CALLBACK(vd_pop_menu_copy_path_cb), vd);
637
638         menu_item_add_divider(menu);
639
640         item = menu_item_add(menu, _("_View as"), NULL, NULL);
641         submenu = gtk_menu_new();
642         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
643
644         for (i = 0; i < VIEW_DIR_TYPES_COUNT; i++)
645                 {
646                 item = menu_item_add_check(submenu, _(menu_view_dir_radio_entries[i].label),
647                                            ((gint) vd->type == menu_view_dir_radio_entries[i].value),
648                                            G_CALLBACK(vd_pop_submenu_dir_view_as_cb), vd);
649                 g_object_set_data(G_OBJECT(item), VIEW_DIR_AS_SUBMENU_KEY, GINT_TO_POINTER(menu_view_dir_radio_entries[i].value));
650                 }
651
652         menu_item_add_check(menu, _("Show _hidden files"), options->file_filter.show_hidden_files,
653                             G_CALLBACK(vd_toggle_show_hidden_files_cb), vd);
654
655         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH,
656                             G_CALLBACK(vd_pop_menu_refresh_cb), vd);
657
658         return menu;
659 }
660
661 /*
662  *-----------------------------------------------------------------------------
663  * dnd
664  *-----------------------------------------------------------------------------
665  */
666
667 static GtkTargetEntry vd_dnd_drop_types[] = {
668         { "text/uri-list", 0, TARGET_URI_LIST }
669 };
670 static gint vd_dnd_drop_types_count = 1;
671
672 static void vd_dest_set(ViewDir *vd, gint enable)
673 {
674         if (enable)
675                 {
676                 gtk_drag_dest_set(vd->view,
677                                   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
678                                   vd_dnd_drop_types, vd_dnd_drop_types_count,
679                                   GDK_ACTION_MOVE | GDK_ACTION_COPY);
680                 }
681         else
682                 {
683                 gtk_drag_dest_unset(vd->view);
684                 }
685 }
686
687 static void vd_dnd_get(GtkWidget *widget, GdkDragContext *context,
688                            GtkSelectionData *selection_data, guint info,
689                            guint time, gpointer data)
690 {
691         ViewDir *vd = data;
692         GList *list;
693         gchar *uritext = NULL;
694         gint length = 0;
695
696         if (!vd->click_fd) return;
697
698         switch (info)
699                 {
700                 case TARGET_URI_LIST:
701                 case TARGET_TEXT_PLAIN:
702                         list = g_list_prepend(NULL, vd->click_fd);
703                         uritext = uri_text_from_filelist(list, &length, (info == TARGET_TEXT_PLAIN));
704                         g_list_free(list);
705                         break;
706                 }
707         if (uritext)
708                 {
709                 gtk_selection_data_set(selection_data, selection_data->target,
710                                        8, (guchar *)uritext, length);
711                 g_free(uritext);
712                 }
713 }
714
715 static void vd_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
716 {
717         ViewDir *vd = data;
718
719         vd_color_set(vd, vd->click_fd, TRUE);
720         vd_dest_set(vd, FALSE);
721 }
722
723 static void vd_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
724 {
725         ViewDir *vd = data;
726
727         vd_color_set(vd, vd->click_fd, FALSE);
728
729         if (vd->type == DIRVIEW_LIST && context->action == GDK_ACTION_MOVE)
730                 {
731                 vd_refresh(vd);
732                 }
733         vd_dest_set(vd, TRUE);
734 }
735
736 static void vd_dnd_drop_receive(GtkWidget *widget,
737                                 GdkDragContext *context, gint x, gint y,
738                                 GtkSelectionData *selection_data, guint info,
739                                 guint time, gpointer data)
740 {
741         ViewDir *vd = data;
742         GtkTreePath *tpath;
743         FileData *fd = NULL;
744
745         vd->click_fd = NULL;
746
747         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y,
748                                           &tpath, NULL, NULL, NULL))
749                 {
750                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
751                 gtk_tree_path_free(tpath);
752                 }
753
754         if (!fd) return;
755
756         if (info == TARGET_URI_LIST)
757                 {
758                 GList *list;
759                 gint active;
760                 gint done = FALSE;
761
762                 list = uri_filelist_from_text((gchar *)selection_data->data, TRUE);
763                 if (!list) return;
764
765                 active = access_file(fd->path, W_OK | X_OK);
766
767                 vd_color_set(vd, fd, TRUE);
768
769                 if (active)
770                         {
771                         if (context->actions == GDK_ACTION_COPY)
772                                 {
773                                 file_util_copy_simple(list, fd->path, vd->widget);
774                                 done = TRUE;
775                                 }
776                         else if (context->actions == GDK_ACTION_MOVE)
777                                 {
778                                 file_util_move_simple(list, fd->path, vd->widget);
779                                 done = TRUE;
780                                 }
781                         }
782
783                 if (done == FALSE)
784                         {
785                         vd->popup = vd_drop_menu(vd, active);
786                         gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL, 0, time);
787                         }
788
789                 vd->drop_fd = fd;
790                 vd->drop_list = list;
791                 }
792 }
793
794 static void vd_dnd_drop_update(ViewDir *vd, gint x, gint y)
795 {
796         GtkTreePath *tpath;
797         FileData *fd = NULL;
798
799         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vd->view), x, y,
800                                           &tpath, NULL, NULL, NULL))
801                 {
802                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), tpath);
803                 gtk_tree_path_free(tpath);
804                 }
805
806         if (fd != vd->drop_fd)
807                 {
808                 vd_color_set(vd, vd->drop_fd, FALSE);
809                 vd_color_set(vd, fd, TRUE);
810                 if (fd && vd->dnd_drop_update_func) vd->dnd_drop_update_func(vd);
811                 }
812
813         vd->drop_fd = fd;
814 }
815
816 void vd_dnd_drop_scroll_cancel(ViewDir *vd)
817 {
818         if (vd->drop_scroll_id != -1) g_source_remove(vd->drop_scroll_id);
819         vd->drop_scroll_id = -1;
820 }
821
822 static gint vd_auto_scroll_idle_cb(gpointer data)
823 {
824         ViewDir *vd = data;
825
826         if (vd->drop_fd)
827                 {
828                 GdkWindow *window;
829                 gint x, y;
830                 gint w, h;
831
832                 window = vd->view->window;
833                 gdk_window_get_pointer(window, &x, &y, NULL);
834                 gdk_drawable_get_size(window, &w, &h);
835                 if (x >= 0 && x < w && y >= 0 && y < h)
836                         {
837                         vd_dnd_drop_update(vd, x, y);
838                         }
839                 }
840
841         vd->drop_scroll_id = -1;
842         return FALSE;
843 }
844
845 static gint vd_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data)
846 {
847         ViewDir *vd = data;
848
849         if (!vd->drop_fd || vd->drop_list) return FALSE;
850
851         if (vd->drop_scroll_id == -1) vd->drop_scroll_id = g_idle_add(vd_auto_scroll_idle_cb, vd);
852
853         return TRUE;
854 }
855
856 static gint vd_dnd_drop_motion(GtkWidget *widget, GdkDragContext *context,
857                                    gint x, gint y, guint time, gpointer data)
858 {
859         ViewDir *vd = data;
860
861         vd->click_fd = NULL;
862
863         if (gtk_drag_get_source_widget(context) == vd->view)
864                 {
865                 /* from same window */
866                 gdk_drag_status(context, 0, time);
867                 return TRUE;
868                 }
869         else
870                 {
871                 gdk_drag_status(context, context->suggested_action, time);
872                 }
873
874         vd_dnd_drop_update(vd, x, y);
875
876         if (vd->drop_fd)
877                 {
878                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vd->view));
879                 widget_auto_scroll_start(vd->view, adj, -1, -1, vd_auto_scroll_notify_cb, vd);
880                 }
881
882         return FALSE;
883 }
884
885 static void vd_dnd_drop_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data)
886 {
887         ViewDir *vd = data;
888
889         if (vd->drop_fd != vd->click_fd) vd_color_set(vd, vd->drop_fd, FALSE);
890
891         vd->drop_fd = NULL;
892
893         if (vd->dnd_drop_leave_func) vd->dnd_drop_leave_func(vd);
894 }
895
896 void vd_dnd_init(ViewDir *vd)
897 {
898         gtk_drag_source_set(vd->view, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
899                             dnd_file_drag_types, dnd_file_drag_types_count,
900                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
901         g_signal_connect(G_OBJECT(vd->view), "drag_data_get",
902                          G_CALLBACK(vd_dnd_get), vd);
903         g_signal_connect(G_OBJECT(vd->view), "drag_begin",
904                          G_CALLBACK(vd_dnd_begin), vd);
905         g_signal_connect(G_OBJECT(vd->view), "drag_end",
906                          G_CALLBACK(vd_dnd_end), vd);
907
908         vd_dest_set(vd, TRUE);
909         g_signal_connect(G_OBJECT(vd->view), "drag_data_received",
910                          G_CALLBACK(vd_dnd_drop_receive), vd);
911         g_signal_connect(G_OBJECT(vd->view), "drag_motion",
912                          G_CALLBACK(vd_dnd_drop_motion), vd);
913         g_signal_connect(G_OBJECT(vd->view), "drag_leave",
914                          G_CALLBACK(vd_dnd_drop_leave), vd);
915 }
916
917 /*
918  *----------------------------------------------------------------------------
919  * callbacks
920  *----------------------------------------------------------------------------
921  */
922
923 void vd_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
924 {
925         ViewDir *vd = data;
926         GtkTreeModel *store;
927         GtkTreeIter iter;
928         GtkTreePath *tpath;
929         gint cw, ch;
930
931         if (vd_find_row(vd, vd->click_fd, &iter) < 0) return;
932         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
933         tpath = gtk_tree_model_get_path(store, &iter);
934         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
935         gtk_tree_path_free(tpath);
936         *y += ch;
937         popup_menu_position_clamp(menu, x, y, 0);
938 }
939
940 void vd_activate_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *column, gpointer data)
941 {
942         ViewDir *vd = data;
943         FileData *fd = vd_get_fd_from_tree_path(vd, tview, tpath);
944
945         vd_select_row(vd, fd);
946 }
947
948 static GdkColor *vd_color_shifted(GtkWidget *widget)
949 {
950         static GdkColor color;
951         static GtkWidget *done = NULL;
952
953         if (done != widget)
954                 {
955                 GtkStyle *style;
956
957                 style = gtk_widget_get_style(widget);
958                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
959                 shift_color(&color, -1, 0);
960                 done = widget;
961                 }
962
963         return &color;
964 }
965
966 void vd_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
967                  GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
968 {
969         ViewDir *vd = data;
970         gboolean set;
971
972         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
973         g_object_set(G_OBJECT(cell),
974                      "cell-background-gdk", vd_color_shifted(vd->view),
975                      "cell-background-set", set, NULL);
976 }
977
978 gint vd_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
979 {
980         ViewDir *vd = data;
981         GtkTreePath *tpath;
982         FileData *fd = NULL;
983
984         if (!vd->click_fd) return FALSE;
985         vd_color_set(vd, vd->click_fd, FALSE);
986
987         if (bevent->button != MOUSE_BUTTON_LEFT) return TRUE;
988
989         if ((bevent->x != 0 || bevent->y != 0) &&
990             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
991                                           &tpath, NULL, NULL, NULL))
992                 {
993                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
994                 gtk_tree_path_free(tpath);
995                 }
996
997         if (fd && vd->click_fd == fd)
998                 {
999                 vdlist_select_row(vd, vd->click_fd);
1000                 }
1001
1002         return FALSE;
1003 }
1004
1005 gint vd_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1006 {
1007         ViewDir *vd = data;
1008         gint ret = FALSE;
1009
1010         switch(vd->type)
1011         {
1012         case DIRVIEW_LIST: ret = vdlist_press_key_cb(widget, event, data); break;
1013         case DIRVIEW_TREE: ret = vdtree_press_key_cb(widget, event, data); break;
1014         }
1015
1016         return ret;
1017 }
1018
1019 gint vd_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
1020 {
1021         ViewDir *vd = data;
1022         gint ret = FALSE;
1023
1024         switch(vd->type)
1025         {
1026         case DIRVIEW_LIST: ret = vdlist_press_cb(widget, bevent, data); break;
1027         case DIRVIEW_TREE: ret = vdtree_press_cb(widget, bevent, data); break;
1028         }
1029
1030         return ret;
1031 }
1032
1033 static void vd_notify_cb(FileData *fd, NotifyType type, gpointer data)
1034 {
1035         ViewDir *vd = data;
1036         gboolean refresh;
1037         gchar *base;
1038
1039         if (!S_ISDIR(fd->mode)) return; /* this gives correct results even on recently deleted files/directories */
1040
1041         base = remove_level_from_path(fd->path);
1042
1043         if (vd->type == DIRVIEW_LIST)
1044                 {
1045                 refresh = (fd == vd->dir_fd);
1046
1047                 if (!refresh)
1048                         {
1049                         refresh = (strcmp(base, vd->dir_fd->path) == 0);
1050                         }
1051
1052                 if (type == NOTIFY_TYPE_CHANGE && fd->change)
1053                         {
1054                         if (!refresh && fd->change->dest)
1055                                 {
1056                                 gchar *dest_base = remove_level_from_path(fd->change->dest);
1057                                 refresh = (strcmp(dest_base, vd->dir_fd->path) == 0);
1058                                 g_free(dest_base);
1059                                 }
1060
1061                         if (!refresh && fd->change->source)
1062                                 {
1063                                 gchar *source_base = remove_level_from_path(fd->change->source);
1064                                 refresh = (strcmp(source_base, vd->dir_fd->path) == 0);
1065                                 g_free(source_base);
1066                                 }
1067                         }
1068                 
1069                 if (refresh) vd_refresh(vd);
1070                 }
1071
1072         if (vd->type == DIRVIEW_TREE)
1073                 {
1074                 GtkTreeIter iter;
1075                 FileData *base_fd = file_data_new_simple(base);
1076
1077                 if (vd_find_row(vd, base_fd, &iter))
1078                         {
1079                         vdtree_populate_path_by_iter(vd, &iter, TRUE, vd->dir_fd);
1080                         }
1081
1082                 file_data_unref(base_fd);
1083                 }
1084
1085         g_free(base);
1086 }
1087 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */