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