Reduce code redundancy with the introduction of a new
[geeqie.git] / src / view_dir.c
1 /*
2  * Geeqie
3  * (C) 2008 Vladimir Nadvornik
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 "filelist.h"
18 #include "layout_image.h"
19 #include "layout_util.h"
20 #include "ui_bookmark.h"
21 #include "ui_fileops.h"
22 #include "ui_tree_edit.h"
23 #include "ui_menu.h"
24 #include "utilops.h"
25 #include "view_dir_list.h"
26 #include "view_dir_tree.h"
27
28 GtkRadioActionEntry menu_view_dir_radio_entries[] = {
29   { "FolderList",       NULL,           N_("List"),             "<meta>L",      NULL, DIRVIEW_LIST },
30   { "FolderTree",       NULL,           N_("Tr_ee"),            "<control>T",   NULL, DIRVIEW_TREE },
31 };
32
33 void vd_destroy_cb(GtkWidget *widget, gpointer data)
34 {
35         ViewDir *vd = data;
36
37         if (vd->popup)
38                 {
39                 g_signal_handlers_disconnect_matched(G_OBJECT(vd->popup), G_SIGNAL_MATCH_DATA,
40                                                      0, 0, 0, NULL, vd);
41                 gtk_widget_destroy(vd->popup);
42                 }
43
44         if (vd->widget_destroy_cb) vd->widget_destroy_cb(widget, data);
45
46         if (vd->pf) folder_icons_free(vd->pf);
47         if (vd->drop_list) filelist_free(vd->drop_list);
48
49         if (vd->path) g_free(vd->path);
50         if (vd->info) g_free(vd->info);
51
52         g_free(vd);
53 }
54
55 ViewDir *vd_new(DirViewType type, const gchar *path)
56 {
57         ViewDir *vd = g_new0(ViewDir, 1);
58
59         vd->path = NULL;
60         vd->click_fd = NULL;
61
62         vd->drop_fd = NULL;
63         vd->drop_list = NULL;
64         vd->drop_scroll_id = -1;
65         vd->drop_list = NULL;
66
67         vd->popup = NULL;
68
69         vd->widget = gtk_scrolled_window_new(NULL, NULL);
70         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vd->widget), GTK_SHADOW_IN);
71         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vd->widget),
72                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
73
74         vd->pf = folder_icons_new();
75
76         switch(type)
77         {
78         case DIRVIEW_LIST: vd = vdlist_new(vd, path); break;
79         case DIRVIEW_TREE: vd = vdtree_new(vd, path); break;
80         }
81
82         g_signal_connect(G_OBJECT(vd->widget), "destroy",
83                          G_CALLBACK(vd_destroy_cb), vd);
84
85         return vd;
86 }
87         
88 void vd_set_select_func(ViewDir *vd,
89                         void (*func)(ViewDir *vd, const gchar *path, gpointer data), gpointer data)
90 {
91         vd->select_func = func;
92         vd->select_data = data;
93 }
94
95 void vd_set_layout(ViewDir *vd, LayoutWindow *layout)
96 {
97         vd->layout = layout;
98 }
99
100 gint vd_set_path(ViewDir *vd, const gchar *path)
101 {
102         gint ret = FALSE;
103
104         switch(vd->type)
105         {
106         case DIRVIEW_LIST: ret = vdlist_set_path(vd, path); break;
107         case DIRVIEW_TREE: ret = vdtree_set_path(vd, path); break;
108         }
109
110         return ret;
111 }
112
113 void vd_refresh(ViewDir *vd)
114 {
115         switch(vd->type)
116         {
117         case DIRVIEW_LIST: return vdlist_refresh(vd);
118         case DIRVIEW_TREE: return vdtree_refresh(vd);
119         }
120 }
121
122 const gchar *vd_row_get_path(ViewDir *vd, gint row)
123 {
124         const gchar *ret = NULL;
125
126         switch(vd->type)
127         {
128         case DIRVIEW_LIST: ret = vdlist_row_get_path(vd, row); break;
129         case DIRVIEW_TREE: ret = vdtree_row_get_path(vd, row); break;
130         }
131
132         return ret;
133 }
134
135 void vd_select_row(ViewDir *vd, FileData *fd)
136 {
137         switch(vd->type)
138         {
139         case DIRVIEW_LIST: vdlist_select_row(vd, fd); break;
140         case DIRVIEW_TREE: vdtree_select_row(vd, fd); break;
141         }
142 }
143
144 gint vd_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter)
145 {
146         gint ret = FALSE;
147
148         switch(vd->type)
149         {
150         case DIRVIEW_LIST: ret = vdlist_find_row(vd, fd, iter); break;
151         case DIRVIEW_TREE: ret = vdtree_find_row(vd, fd, iter, NULL); break;
152         }
153
154         return ret;
155 }
156
157 FileData *vd_get_fd_from_tree_path(ViewDir *vd, GtkTreeView *tview, GtkTreePath *tpath)
158 {
159         GtkTreeIter iter;
160         FileData *fd = NULL;
161         GtkTreeModel *store;
162
163         store = gtk_tree_view_get_model(tview);
164         gtk_tree_model_get_iter(store, &iter, tpath);
165         switch (vd->type)
166                 {
167                 case DIRVIEW_LIST:
168                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
169                         break;
170                 case DIRVIEW_TREE:
171                         {
172                         NodeData *nd;
173                         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
174                         fd = (nd) ? nd->fd : NULL;
175                         };
176                         break;
177                 }
178
179         return fd;
180 }
181
182 static gint vd_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data)
183 {
184         ViewDir *vd = data;
185         FileData *fd;
186         gchar *old_path;
187         gchar *new_path;
188         gchar *base;
189
190         fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), td->path);
191         if (!fd) return FALSE;
192
193         old_path = g_strdup(fd->path);
194
195         base = remove_level_from_path(old_path);
196         new_path = concat_dir_and_file(base, new);
197         g_free(base);
198
199         if (file_util_rename_dir(fd, new_path, vd->view))
200                 {
201
202                 if (vd->type == DIRVIEW_TREE) vdtree_populate_path(vd, new_path, TRUE, TRUE);
203                 if (vd->layout && strcmp(vd->path, old_path) == 0)
204                         {
205                         layout_set_path(vd->layout, new_path);
206                         }
207                 else
208                         {
209                         if (vd->type == DIRVIEW_LIST) vd_refresh(vd);
210                         }
211                 }
212
213         g_free(old_path);
214         g_free(new_path);
215
216         return FALSE;
217 }
218
219 static void vd_rename_by_data(ViewDir *vd, FileData *fd)
220 {
221         GtkTreeModel *store;
222         GtkTreePath *tpath;
223         GtkTreeIter iter;
224
225         if (!fd || vd_find_row(vd, fd, &iter) < 0) return;
226         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
227         tpath = gtk_tree_model_get_path(store, &iter);
228
229         tree_edit_by_path(GTK_TREE_VIEW(vd->view), tpath, 0, fd->name,
230                           vd_rename_cb, vd);
231         gtk_tree_path_free(tpath);
232 }
233
234
235 void vd_color_set(ViewDir *vd, FileData *fd, gint color_set)
236 {
237         GtkTreeModel *store;
238         GtkTreeIter iter;
239
240         if (vd_find_row(vd, fd, &iter) < 0) return;
241         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
242
243         switch(vd->type)
244         {
245         case DIRVIEW_LIST:
246                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
247                 break;
248         case DIRVIEW_TREE:
249                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
250                 break;
251         }
252 }
253
254 void vd_popup_destroy_cb(GtkWidget *widget, gpointer data)
255 {
256         ViewDir *vd = data;
257
258         vd_color_set(vd, vd->click_fd, FALSE);
259         vd->click_fd = NULL;
260         vd->popup = NULL;
261
262         vd_color_set(vd, vd->drop_fd, FALSE);
263         filelist_free(vd->drop_list);
264         vd->drop_list = NULL;
265         vd->drop_fd = NULL;
266 }
267
268 /*
269  *-----------------------------------------------------------------------------
270  * drop menu (from dnd)
271  *-----------------------------------------------------------------------------
272  */
273
274 static void vd_drop_menu_copy_cb(GtkWidget *widget, gpointer data)
275 {
276         ViewDir *vd = data;
277         const gchar *path;
278         GList *list;
279
280         if (!vd->drop_fd) return;
281
282         path = vd->drop_fd->path;
283         list = vd->drop_list;
284         vd->drop_list = NULL;
285
286         file_util_copy_simple(list, path);
287 }
288
289 static void vd_drop_menu_move_cb(GtkWidget *widget, gpointer data)
290 {
291         ViewDir *vd = data;
292         const gchar *path;
293         GList *list;
294
295         if (!vd->drop_fd) return;
296
297         path = vd->drop_fd->path;
298         list = vd->drop_list;
299
300         vd->drop_list = NULL;
301
302         file_util_move_simple(list, path);
303 }
304
305 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
306 {
307         GtkWidget *menu;
308
309         menu = popup_menu_short_lived();
310         g_signal_connect(G_OBJECT(menu), "destroy",
311                          G_CALLBACK(vd_popup_destroy_cb), vd);
312
313         menu_item_add_stock_sensitive(menu, _("_Copy"), GTK_STOCK_COPY, active,
314                                       G_CALLBACK(vd_drop_menu_copy_cb), vd);
315         menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
316
317         menu_item_add_divider(menu);
318         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, vd);
319
320         return menu;
321 }
322
323 /*
324  *-----------------------------------------------------------------------------
325  * pop-up menu
326  *-----------------------------------------------------------------------------
327  */ 
328
329 static void vd_pop_menu_up_cb(GtkWidget *widget, gpointer data)
330 {
331         ViewDir *vd = data;
332         gchar *path;
333
334         if (!vd->path || strcmp(vd->path, "/") == 0) return;
335         path = remove_level_from_path(vd->path);
336
337         if (vd->select_func)
338                 {
339                 vd->select_func(vd, path, vd->select_data);
340                 }
341
342         g_free(path);
343 }
344
345 static void vd_pop_menu_slide_cb(GtkWidget *widget, gpointer data)
346 {
347         ViewDir *vd = data;
348         gchar *path;
349
350         if (!vd->layout) return;
351         if (!vd->click_fd) return;
352
353         path = vd->click_fd->path;
354
355         layout_set_path(vd->layout, path);
356         layout_select_none(vd->layout);
357         layout_image_slideshow_stop(vd->layout);
358         layout_image_slideshow_start(vd->layout);
359 }
360
361 static void vd_pop_menu_slide_rec_cb(GtkWidget *widget, gpointer data)
362 {
363         ViewDir *vd = data;
364         gchar *path;
365         GList *list;
366
367         if (!vd->layout) return;
368         if (!vd->click_fd) return;
369
370         path = vd->click_fd->path;
371
372         list = filelist_recursive(path);
373
374         layout_image_slideshow_stop(vd->layout);
375         layout_image_slideshow_start_from_list(vd->layout, list);
376 }
377
378 static void vd_pop_menu_dupe(ViewDir *vd, gint recursive)
379 {
380         DupeWindow *dw;
381         GList *list = NULL;
382
383         if (!vd->click_fd) return;
384
385         if (recursive)
386                 {
387                 list = g_list_append(list, file_data_ref(vd->click_fd));
388                 }
389         else
390                 {
391                 filelist_read(vd->click_fd->path, &list, NULL);
392                 list = filelist_filter(list, FALSE);
393                 }
394
395         dw = dupe_window_new(DUPE_MATCH_NAME);
396         dupe_window_add_files(dw, list, recursive);
397
398         filelist_free(list);
399 }
400
401 static void vd_pop_menu_dupe_cb(GtkWidget *widget, gpointer data)
402 {
403         ViewDir *vd = data;
404         vd_pop_menu_dupe(vd, FALSE);
405 }
406
407 static void vd_pop_menu_dupe_rec_cb(GtkWidget *widget, gpointer data)
408 {
409         ViewDir *vd = data;
410         vd_pop_menu_dupe(vd, TRUE);
411 }
412
413 static void vd_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
414 {
415         ViewDir *vd = data;
416
417         if (!vd->click_fd) return;
418         file_util_delete_dir(vd->click_fd, vd->widget);
419 }
420
421 static void vd_pop_menu_dir_view_as_cb(GtkWidget *widget, gpointer data)
422 {
423         ViewDir *vd = data;
424         DirViewType new_type = DIRVIEW_LIST;
425
426         if (!vd->layout) return;
427
428         switch(vd->type)
429         {
430         case DIRVIEW_LIST: new_type = DIRVIEW_TREE; break;
431         case DIRVIEW_TREE: new_type = DIRVIEW_LIST; break;
432         }
433         
434         layout_views_set(vd->layout, new_type, vd->layout->icon_view);
435 }
436
437 static void vd_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
438 {
439         ViewDir *vd = data;
440
441         if (vd->layout) layout_refresh(vd->layout);
442 }
443
444 static void vd_toggle_show_hidden_files_cb(GtkWidget *widget, gpointer data)
445 {
446         ViewDir *vd = data;
447
448         options->file_filter.show_hidden_files = !options->file_filter.show_hidden_files;
449         if (vd->layout) layout_refresh(vd->layout);
450 }
451
452 static void vd_pop_menu_new_cb(GtkWidget *widget, gpointer data)
453 {
454         ViewDir *vd = data;
455         const gchar *path = NULL;
456         gchar *new_path;
457         gchar *buf;
458
459         switch(vd->type)
460                 {
461                 case DIRVIEW_LIST:
462                         {
463                         if (!vd->path) return;
464                         path = vd->path;
465                         };
466                         break;
467                 case DIRVIEW_TREE:
468                         {
469                         if (!vd->click_fd) return;
470                         path = vd->click_fd->path;
471                         };
472                         break;
473                 }
474
475         buf = concat_dir_and_file(path, _("new_folder"));
476         new_path = unique_filename(buf, NULL, NULL, FALSE);
477         g_free(buf);
478         if (!new_path) return;
479
480         if (!mkdir_utf8(new_path, 0755))
481                 {
482                 gchar *text;
483
484                 text = g_strdup_printf(_("Unable to create folder:\n%s"), new_path);
485                 file_util_warning_dialog(_("Error creating folder"), text, GTK_STOCK_DIALOG_ERROR, vd->view);
486                 g_free(text);
487                 }
488         else
489                 {
490                 FileData *fd = NULL;
491
492                 switch(vd->type)
493                         {
494                         case DIRVIEW_LIST:
495                                 {
496                                 vd_refresh(vd);
497                                 fd = vdlist_row_by_path(vd, new_path, NULL);
498                                 };
499                                 break;
500                         case DIRVIEW_TREE:
501                                 fd = vdtree_populate_path(vd, new_path, TRUE, TRUE);
502                                 break;
503                         }
504                 vd_rename_by_data(vd, fd);
505                 }
506
507         g_free(new_path);
508 }
509
510 static void vd_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
511 {
512         ViewDir *vd = data;
513         
514         vd_rename_by_data(vd, vd->click_fd);
515 }
516
517 GtkWidget *vd_pop_menu(ViewDir *vd, FileData *fd)
518 {
519         GtkWidget *menu;
520         gint active;
521         gint rename_delete_active = FALSE;
522         gint new_folder_active = FALSE;
523
524         active = (fd != NULL);
525         switch(vd->type)
526                 {
527                 case DIRVIEW_LIST:
528                         {
529                         /* check using . (always row 0) */
530                         new_folder_active = (vd->path && access_file(vd->path , W_OK | X_OK));
531
532                         /* ignore .. and . */
533                         rename_delete_active = (new_folder_active && fd &&
534                                 strcmp(fd->name, ".") != 0 &&
535                                 strcmp(fd->name, "..") != 0 &&
536                                 access_file(fd->path, W_OK | X_OK));
537                         };
538                         break;
539                 case DIRVIEW_TREE:
540                         {
541                         if (fd)
542                                 {
543                                 gchar *parent;
544                                 new_folder_active = (fd && access_file(fd->path, W_OK | X_OK));
545                                 parent = remove_level_from_path(fd->path);
546                                 rename_delete_active = access_file(parent, W_OK | X_OK);
547                                 g_free(parent);
548                                 };
549                         }
550                         break;
551                 }
552
553         menu = popup_menu_short_lived();
554         g_signal_connect(G_OBJECT(menu), "destroy",
555                          G_CALLBACK(vd_popup_destroy_cb), vd);
556
557         menu_item_add_stock_sensitive(menu, _("_Up to parent"), GTK_STOCK_GO_UP,
558                                       (vd->path && strcmp(vd->path, "/") != 0),
559                                       G_CALLBACK(vd_pop_menu_up_cb), vd);
560
561         menu_item_add_divider(menu);
562         menu_item_add_sensitive(menu, _("_Slideshow"), active,
563                                 G_CALLBACK(vd_pop_menu_slide_cb), vd);
564         menu_item_add_sensitive(menu, _("Slideshow recursive"), active,
565                                 G_CALLBACK(vd_pop_menu_slide_rec_cb), vd);
566
567         menu_item_add_divider(menu);
568         menu_item_add_stock_sensitive(menu, _("Find _duplicates..."), GTK_STOCK_FIND, active,
569                                       G_CALLBACK(vd_pop_menu_dupe_cb), vd);
570         menu_item_add_stock_sensitive(menu, _("Find duplicates recursive..."), GTK_STOCK_FIND, active,
571                                       G_CALLBACK(vd_pop_menu_dupe_rec_cb), vd);
572
573         menu_item_add_divider(menu);
574
575         menu_item_add_sensitive(menu, _("_New folder..."), new_folder_active,
576                                 G_CALLBACK(vd_pop_menu_new_cb), vd);
577
578         menu_item_add_sensitive(menu, _("_Rename..."), rename_delete_active,
579                                 G_CALLBACK(vd_pop_menu_rename_cb), vd);
580         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, rename_delete_active,
581                                       G_CALLBACK(vd_pop_menu_delete_cb), vd);
582
583         menu_item_add_divider(menu);
584         /* FIXME */
585         menu_item_add_check(menu, _("View as _tree"), vd->type,
586                             G_CALLBACK(vd_pop_menu_dir_view_as_cb), vd);
587         menu_item_add_check(menu, _("Show _hidden files"), options->file_filter.show_hidden_files,
588                             G_CALLBACK(vd_toggle_show_hidden_files_cb), vd);
589
590         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH,
591                             G_CALLBACK(vd_pop_menu_refresh_cb), vd);
592
593         return menu;
594 }
595
596 /*
597  *-----------------------------------------------------------------------------
598  * dnd
599  *-----------------------------------------------------------------------------
600  */
601
602 static GtkTargetEntry vd_dnd_drop_types[] = {
603         { "text/uri-list", 0, TARGET_URI_LIST }
604 };
605 static gint vd_dnd_drop_types_count = 1;
606
607 static void vd_dest_set(ViewDir *vd, gint enable)
608 {
609         if (enable)
610                 {
611                 gtk_drag_dest_set(vd->view,
612                                   GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
613                                   vd_dnd_drop_types, vd_dnd_drop_types_count,
614                                   GDK_ACTION_MOVE | GDK_ACTION_COPY);
615                 }
616         else
617                 {
618                 gtk_drag_dest_unset(vd->view);
619                 }
620 }
621
622 static void vd_dnd_get(GtkWidget *widget, GdkDragContext *context,
623                            GtkSelectionData *selection_data, guint info,
624                            guint time, gpointer data)
625 {
626         ViewDir *vd = data;
627         GList *list;
628         gchar *uritext = NULL;
629         gint length = 0;
630
631         if (!vd->click_fd) return;
632
633         switch (info)
634                 {
635                 case TARGET_URI_LIST:
636                 case TARGET_TEXT_PLAIN:
637                         list = g_list_prepend(NULL, vd->click_fd);
638                         uritext = uri_text_from_filelist(list, &length, (info == TARGET_TEXT_PLAIN));
639                         g_list_free(list);
640                         break;
641                 }
642         if (uritext)
643                 {
644                 gtk_selection_data_set (selection_data, selection_data->target,
645                                 8, (guchar *)uritext, length);
646                 g_free(uritext);
647                 }
648 }
649
650 static void vd_dnd_begin(GtkWidget *widget, GdkDragContext *context, gpointer data)
651 {
652         ViewDir *vd = data;
653
654         vd_color_set(vd, vd->click_fd, TRUE);
655         vd_dest_set(vd, FALSE);
656 }
657
658 static void vd_dnd_end(GtkWidget *widget, GdkDragContext *context, gpointer data)
659 {
660         ViewDir *vd = data;
661
662         vd_color_set(vd, vd->click_fd, FALSE);
663
664         if (vd->type == DIRVIEW_LIST && context->action == GDK_ACTION_MOVE)
665                 {
666                 vd_refresh(vd);
667                 }
668         vd_dest_set(vd, TRUE);
669 }
670
671 static void vd_dnd_drop_receive(GtkWidget *widget,
672                                     GdkDragContext *context, gint x, gint y,
673                                     GtkSelectionData *selection_data, guint info,
674                                     guint time, gpointer data)
675 {
676         ViewDir *vd = data;
677         GtkTreePath *tpath;
678         FileData *fd = NULL;
679
680         vd->click_fd = NULL;
681
682         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y,
683                                           &tpath, NULL, NULL, NULL))
684                 {
685                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
686                 gtk_tree_path_free(tpath);
687                 }
688
689         if (!fd) return;
690
691         if (info == TARGET_URI_LIST)
692                 {
693                 GList *list;
694                 gint active;
695
696                 list = uri_filelist_from_text((gchar *)selection_data->data, TRUE);
697                 if (!list) return;
698
699                 active = access_file(fd->path, W_OK | X_OK);
700
701                 vd_color_set(vd, fd, TRUE);
702                 vd->popup = vd_drop_menu(vd, active);
703                 gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL, 0, time);
704
705                 vd->drop_fd = fd;
706                 vd->drop_list = list;
707                 }
708 }
709
710 static void vd_drop_update(ViewDir *vd, gint x, gint y)
711 {
712         GtkTreePath *tpath;
713         FileData *fd = NULL;
714
715         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(vd->view), x, y,
716                                           &tpath, NULL, NULL, NULL))
717                 {
718                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(vd->view), tpath);
719                 gtk_tree_path_free(tpath);
720                 }
721
722         if (fd != vd->drop_fd)
723                 {
724                 vd_color_set(vd, vd->drop_fd, FALSE);
725                 vd_color_set(vd, fd, TRUE);
726                 if (vd->type == DIRVIEW_TREE && fd) vdtree_dnd_drop_expand(vd);
727                 }
728
729         vd->drop_fd = fd;
730 }
731
732 void vd_dnd_drop_scroll_cancel(ViewDir *vd)
733 {
734         if (vd->drop_scroll_id != -1) g_source_remove(vd->drop_scroll_id);
735         vd->drop_scroll_id = -1;
736 }
737
738 static gint vd_auto_scroll_idle_cb(gpointer data)
739 {
740         ViewDir *vd = data;
741
742         if (vd->drop_fd)
743                 {
744                 GdkWindow *window;
745                 gint x, y;
746                 gint w, h;
747
748                 window = vd->view->window;
749                 gdk_window_get_pointer(window, &x, &y, NULL);
750                 gdk_drawable_get_size(window, &w, &h);
751                 if (x >= 0 && x < w && y >= 0 && y < h)
752                         {
753                         vd_drop_update(vd, x, y);
754                         }
755                 }
756
757         vd->drop_scroll_id = -1;
758         return FALSE;
759 }
760
761 static gint vd_auto_scroll_notify_cb(GtkWidget *widget, gint x, gint y, gpointer data)
762 {
763         ViewDir *vd = data;
764
765         if (!vd->drop_fd || vd->drop_list) return FALSE;
766
767         if (vd->drop_scroll_id == -1) vd->drop_scroll_id = g_idle_add(vd_auto_scroll_idle_cb, vd);
768
769         return TRUE;
770 }
771
772 static gint vd_dnd_drop_motion(GtkWidget *widget, GdkDragContext *context,
773                                    gint x, gint y, guint time, gpointer data)
774 {
775         ViewDir *vd = data;
776
777         vd->click_fd = NULL;
778
779         if (gtk_drag_get_source_widget(context) == vd->view)
780                 {
781                 /* from same window */
782                 gdk_drag_status(context, 0, time);
783                 return TRUE;
784                 }
785         else
786                 {
787                 gdk_drag_status(context, context->suggested_action, time);
788                 }
789
790         vd_drop_update(vd, x, y);
791
792         if (vd->drop_fd)
793                 {
794                 GtkAdjustment *adj = gtk_tree_view_get_vadjustment(GTK_TREE_VIEW(vd->view));
795                 widget_auto_scroll_start(vd->view, adj, -1, -1, vd_auto_scroll_notify_cb, vd);
796                 }
797
798         return FALSE;
799 }
800
801 static void vd_dnd_drop_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer data)
802 {
803         ViewDir *vd = data;
804
805         if (vd->drop_fd != vd->click_fd) vd_color_set(vd, vd->drop_fd, FALSE);
806
807         vd->drop_fd = NULL;
808
809         if (vd->type == DIRVIEW_TREE) vdtree_dnd_drop_expand_cancel(vd);
810 }
811
812 void vd_dnd_init(ViewDir *vd)
813 {
814         gtk_drag_source_set(vd->view, GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
815                             dnd_file_drag_types, dnd_file_drag_types_count,
816                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_ASK);
817         g_signal_connect(G_OBJECT(vd->view), "drag_data_get",
818                          G_CALLBACK(vd_dnd_get), vd);
819         g_signal_connect(G_OBJECT(vd->view), "drag_begin",
820                          G_CALLBACK(vd_dnd_begin), vd);
821         g_signal_connect(G_OBJECT(vd->view), "drag_end",
822                          G_CALLBACK(vd_dnd_end), vd);
823
824         vd_dest_set(vd, TRUE);
825         g_signal_connect(G_OBJECT(vd->view), "drag_data_received",
826                          G_CALLBACK(vd_dnd_drop_receive), vd);
827         g_signal_connect(G_OBJECT(vd->view), "drag_motion",
828                          G_CALLBACK(vd_dnd_drop_motion), vd);
829         g_signal_connect(G_OBJECT(vd->view), "drag_leave",
830                          G_CALLBACK(vd_dnd_drop_leave), vd);
831 }
832
833 /*
834  *----------------------------------------------------------------------------
835  * callbacks
836  *----------------------------------------------------------------------------
837  */
838
839 void vd_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
840 {
841         ViewDir *vd = data;
842         GtkTreeModel *store;
843         GtkTreeIter iter;
844         GtkTreePath *tpath;
845         gint cw, ch;
846
847         if (vd_find_row(vd, vd->click_fd, &iter) < 0) return;
848         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
849         tpath = gtk_tree_model_get_path(store, &iter);
850         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
851         gtk_tree_path_free(tpath);
852         *y += ch;
853         popup_menu_position_clamp(menu, x, y, 0);
854 }
855
856 void vd_activate_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *column, gpointer data)
857 {
858         ViewDir *vd = data;
859         FileData *fd = vd_get_fd_from_tree_path(vd, tview, tpath);
860
861         vd_select_row(vd, fd);
862 }
863
864 static GdkColor *vd_color_shifted(GtkWidget *widget)
865 {
866         static GdkColor color;
867         static GtkWidget *done = NULL;
868
869         if (done != widget)
870                 {
871                 GtkStyle *style;
872
873                 style = gtk_widget_get_style(widget);
874                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
875                 shift_color(&color, -1, 0);
876                 done = widget;
877                 }
878
879         return &color;
880 }
881
882 void vd_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
883                  GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
884 {
885         ViewDir *vd = data;
886         gboolean set;
887
888         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
889         g_object_set(G_OBJECT(cell),
890                      "cell-background-gdk", vd_color_shifted(vd->view),
891                      "cell-background-set", set, NULL);
892 }
893
894 gint vd_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
895 {
896         ViewDir *vd = data;
897         GtkTreePath *tpath;
898         FileData *fd = NULL;
899
900         vd_color_set(vd, vd->click_fd, FALSE);
901
902         if (bevent->button != 1) return TRUE;
903
904         if ((bevent->x != 0 || bevent->y != 0) &&
905             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
906                                           &tpath, NULL, NULL, NULL))
907                 {
908                 fd = vd_get_fd_from_tree_path(vd, GTK_TREE_VIEW(widget), tpath);
909                 gtk_tree_path_free(tpath);
910                 }
911
912         if (fd && vd->click_fd == fd)
913                 {
914                 vdlist_select_row(vd, vd->click_fd);
915                 }
916
917         return TRUE;
918 }
919