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