Merge few more vdlist/vdtree functions.
[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 "dupe.h"
16 #include "filelist.h"
17 #include "layout_image.h"
18 #include "layout_util.h"
19 #include "ui_fileops.h"
20 #include "ui_tree_edit.h"
21 #include "ui_menu.h"
22 #include "utilops.h"
23 #include "view_dir_list.h"
24 #include "view_dir_tree.h"
25
26 GtkRadioActionEntry menu_view_dir_radio_entries[] = {
27   { "FolderList",       NULL,           N_("List"),             "<meta>L",      NULL, DIRVIEW_LIST },
28   { "FolderTree",       NULL,           N_("Tr_ee"),            "<control>T",   NULL, DIRVIEW_TREE },
29 };
30
31 void vd_destroy_cb(GtkWidget *widget, gpointer data)
32 {
33         ViewDir *vd = data;
34
35         if (vd->popup)
36                 {
37                 g_signal_handlers_disconnect_matched(G_OBJECT(vd->popup), G_SIGNAL_MATCH_DATA,
38                                                      0, 0, 0, NULL, vd);
39                 gtk_widget_destroy(vd->popup);
40                 }
41
42         if (vd->widget_destroy_cb) vd->widget_destroy_cb(widget, data);
43
44         if (vd->pf) folder_icons_free(vd->pf);
45         if (vd->drop_list) filelist_free(vd->drop_list);
46
47         if (vd->path) g_free(vd->path);
48         if (vd->info) g_free(vd->info);
49
50         g_free(vd);
51 }
52
53 ViewDir *vd_new(DirViewType type, const gchar *path)
54 {
55         ViewDir *vd = g_new0(ViewDir, 1);
56
57         vd->path = NULL;
58         vd->click_fd = NULL;
59
60         vd->drop_fd = NULL;
61         vd->drop_list = NULL;
62         vd->drop_scroll_id = -1;
63         vd->drop_list = NULL;
64
65         vd->popup = NULL;
66         vd->pf = NULL;
67
68         vd->widget = gtk_scrolled_window_new(NULL, NULL);
69         gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(vd->widget), GTK_SHADOW_IN);
70         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(vd->widget),
71                                        GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
72
73         switch(type)
74         {
75         case DIRVIEW_LIST: vd = vdlist_new(vd, path); break;
76         case DIRVIEW_TREE: vd = vdtree_new(vd, path); break;
77         }
78
79         g_signal_connect(G_OBJECT(vd->widget), "destroy",
80                          G_CALLBACK(vd_destroy_cb), vd);
81
82         vd->pf = folder_icons_new();
83
84         return vd;
85 }
86         
87 void vd_set_select_func(ViewDir *vd,
88                         void (*func)(ViewDir *vd, const gchar *path, gpointer data), gpointer data)
89 {
90         vd->select_func = func;
91         vd->select_data = data;
92 }
93
94 void vd_set_layout(ViewDir *vd, LayoutWindow *layout)
95 {
96         vd->layout = layout;
97 }
98
99 gint vd_set_path(ViewDir *vd, const gchar *path)
100 {
101         gint ret = FALSE;
102
103         switch(vd->type)
104         {
105         case DIRVIEW_LIST: ret = vdlist_set_path(vd, path); break;
106         case DIRVIEW_TREE: ret = vdtree_set_path(vd, path); break;
107         }
108
109         return ret;
110 }
111
112 void vd_refresh(ViewDir *vd)
113 {
114         switch(vd->type)
115         {
116         case DIRVIEW_LIST: return vdlist_refresh(vd);
117         case DIRVIEW_TREE: return vdtree_refresh(vd);
118         }
119 }
120
121 const gchar *vd_row_get_path(ViewDir *vd, gint row)
122 {
123         const gchar *ret = NULL;
124
125         switch(vd->type)
126         {
127         case DIRVIEW_LIST: ret = vdlist_row_get_path(vd, row); break;
128         case DIRVIEW_TREE: ret = vdtree_row_get_path(vd, row); break;
129         }
130
131         return ret;
132 }
133
134 gint vd_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter)
135 {
136         gint ret = FALSE;
137
138         switch(vd->type)
139         {
140         case DIRVIEW_LIST: ret = vdlist_find_row(vd, fd, iter); break;
141         case DIRVIEW_TREE: ret = vdtree_find_row(vd, fd, iter, NULL); break;
142         }
143
144         return ret;
145 }
146
147 static gint vd_rename_cb(TreeEditData *td, const gchar *old, const gchar *new, gpointer data)
148 {
149         ViewDir *vd = data;
150         GtkTreeModel *store;
151         GtkTreeIter iter;
152         FileData *fd;
153         gchar *old_path;
154         gchar *new_path;
155         gchar *base;
156
157         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
158         if (!gtk_tree_model_get_iter(store, &iter, td->path)) return FALSE;
159
160         switch(vd->type)
161         {
162         case DIRVIEW_LIST:
163                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
164                 break;
165         case DIRVIEW_TREE:
166                 {
167                 NodeData *nd;
168                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &nd, -1);
169                 if (!nd) return FALSE;
170                 fd = nd->fd;
171                 };
172                 break;
173         }
174
175         if (!fd) return FALSE;
176
177         old_path = g_strdup(fd->path);
178
179         base = remove_level_from_path(old_path);
180         new_path = concat_dir_and_file(base, new);
181         g_free(base);
182
183         if (file_util_rename_dir(fd, new_path, vd->view))
184                 {
185
186                 if (vd->type == DIRVIEW_TREE) vdtree_populate_path(vd, new_path, TRUE, TRUE);
187                 if (vd->layout && strcmp(vd->path, old_path) == 0)
188                         {
189                         layout_set_path(vd->layout, new_path);
190                         }
191                 else
192                         {
193                         if (vd->type == DIRVIEW_LIST) vd_refresh(vd);
194                         }
195                 }
196
197         g_free(old_path);
198         g_free(new_path);
199
200         return FALSE;
201 }
202
203 static void vd_rename_by_data(ViewDir *vd, FileData *fd)
204 {
205         GtkTreeModel *store;
206         GtkTreePath *tpath;
207         GtkTreeIter iter;
208
209         if (!fd || vd_find_row(vd, fd, &iter) < 0) return;
210         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
211         tpath = gtk_tree_model_get_path(store, &iter);
212
213         tree_edit_by_path(GTK_TREE_VIEW(vd->view), tpath, 0, fd->name,
214                           vd_rename_cb, vd);
215         gtk_tree_path_free(tpath);
216 }
217
218
219 void vd_color_set(ViewDir *vd, FileData *fd, gint color_set)
220 {
221         GtkTreeModel *store;
222         GtkTreeIter iter;
223
224         if (vd_find_row(vd, fd, &iter) < 0) return;
225         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
226
227         switch(vd->type)
228         {
229         case DIRVIEW_LIST:
230                 gtk_list_store_set(GTK_LIST_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
231                 break;
232         case DIRVIEW_TREE:
233                 gtk_tree_store_set(GTK_TREE_STORE(store), &iter, DIR_COLUMN_COLOR, color_set, -1);
234                 break;
235         }
236 }
237
238 void vd_popup_destroy_cb(GtkWidget *widget, gpointer data)
239 {
240         ViewDir *vd = data;
241
242         vd_color_set(vd, vd->click_fd, FALSE);
243         vd->click_fd = NULL;
244         vd->popup = NULL;
245
246         vd_color_set(vd, vd->drop_fd, FALSE);
247         filelist_free(vd->drop_list);
248         vd->drop_list = NULL;
249         vd->drop_fd = NULL;
250 }
251
252 /*
253  *-----------------------------------------------------------------------------
254  * drop menu (from dnd)
255  *-----------------------------------------------------------------------------
256  */
257
258 static void vd_drop_menu_copy_cb(GtkWidget *widget, gpointer data)
259 {
260         ViewDir *vd = data;
261         const gchar *path;
262         GList *list;
263
264         if (!vd->drop_fd) return;
265
266         path = vd->drop_fd->path;
267         list = vd->drop_list;
268         vd->drop_list = NULL;
269
270         file_util_copy_simple(list, path);
271 }
272
273 static void vd_drop_menu_move_cb(GtkWidget *widget, gpointer data)
274 {
275         ViewDir *vd = data;
276         const gchar *path;
277         GList *list;
278
279         if (!vd->drop_fd) return;
280
281         path = vd->drop_fd->path;
282         list = vd->drop_list;
283
284         vd->drop_list = NULL;
285
286         file_util_move_simple(list, path);
287 }
288
289 GtkWidget *vd_drop_menu(ViewDir *vd, gint active)
290 {
291         GtkWidget *menu;
292
293         menu = popup_menu_short_lived();
294         g_signal_connect(G_OBJECT(menu), "destroy",
295                          G_CALLBACK(vd_popup_destroy_cb), vd);
296
297         menu_item_add_stock_sensitive(menu, _("_Copy"), GTK_STOCK_COPY, active,
298                                       G_CALLBACK(vd_drop_menu_copy_cb), vd);
299         menu_item_add_sensitive(menu, _("_Move"), active, G_CALLBACK(vd_drop_menu_move_cb), vd);
300
301         menu_item_add_divider(menu);
302         menu_item_add_stock(menu, _("Cancel"), GTK_STOCK_CANCEL, NULL, vd);
303
304         return menu;
305 }
306
307 /*
308  *-----------------------------------------------------------------------------
309  * pop-up menu
310  *-----------------------------------------------------------------------------
311  */ 
312
313 static void vd_pop_menu_up_cb(GtkWidget *widget, gpointer data)
314 {
315         ViewDir *vd = data;
316         gchar *path;
317
318         if (!vd->path || strcmp(vd->path, "/") == 0) return;
319         path = remove_level_from_path(vd->path);
320
321         if (vd->select_func)
322                 {
323                 vd->select_func(vd, path, vd->select_data);
324                 }
325
326         g_free(path);
327 }
328
329 static void vd_pop_menu_slide_cb(GtkWidget *widget, gpointer data)
330 {
331         ViewDir *vd = data;
332         gchar *path;
333
334         if (!vd->layout) return;
335         if (!vd->click_fd) return;
336
337         path = vd->click_fd->path;
338
339         layout_set_path(vd->layout, path);
340         layout_select_none(vd->layout);
341         layout_image_slideshow_stop(vd->layout);
342         layout_image_slideshow_start(vd->layout);
343 }
344
345 static void vd_pop_menu_slide_rec_cb(GtkWidget *widget, gpointer data)
346 {
347         ViewDir *vd = data;
348         gchar *path;
349         GList *list;
350
351         if (!vd->layout) return;
352         if (!vd->click_fd) return;
353
354         path = vd->click_fd->path;
355
356         list = filelist_recursive(path);
357
358         layout_image_slideshow_stop(vd->layout);
359         layout_image_slideshow_start_from_list(vd->layout, list);
360 }
361
362 static void vd_pop_menu_dupe(ViewDir *vd, gint recursive)
363 {
364         DupeWindow *dw;
365         GList *list = NULL;
366
367         if (!vd->click_fd) return;
368
369         if (recursive)
370                 {
371                 list = g_list_append(list, file_data_ref(vd->click_fd));
372                 }
373         else
374                 {
375                 filelist_read(vd->click_fd->path, &list, NULL);
376                 list = filelist_filter(list, FALSE);
377                 }
378
379         dw = dupe_window_new(DUPE_MATCH_NAME);
380         dupe_window_add_files(dw, list, recursive);
381
382         filelist_free(list);
383 }
384
385 static void vd_pop_menu_dupe_cb(GtkWidget *widget, gpointer data)
386 {
387         ViewDir *vd = data;
388         vd_pop_menu_dupe(vd, FALSE);
389 }
390
391 static void vd_pop_menu_dupe_rec_cb(GtkWidget *widget, gpointer data)
392 {
393         ViewDir *vd = data;
394         vd_pop_menu_dupe(vd, TRUE);
395 }
396
397 static void vd_pop_menu_delete_cb(GtkWidget *widget, gpointer data)
398 {
399         ViewDir *vd = data;
400
401         if (!vd->click_fd) return;
402         file_util_delete_dir(vd->click_fd, vd->widget);
403 }
404
405 static void vd_pop_menu_dir_view_as_cb(GtkWidget *widget, gpointer data)
406 {
407         ViewDir *vd = data;
408         DirViewType new_type = DIRVIEW_LIST;
409
410         if (!vd->layout) return;
411
412         switch(vd->type)
413         {
414         case DIRVIEW_LIST: new_type = DIRVIEW_TREE; break;
415         case DIRVIEW_TREE: new_type = DIRVIEW_LIST; break;
416         }
417         
418         layout_views_set(vd->layout, new_type, vd->layout->icon_view);
419 }
420
421 static void vd_pop_menu_refresh_cb(GtkWidget *widget, gpointer data)
422 {
423         ViewDir *vd = data;
424
425         if (vd->layout) layout_refresh(vd->layout);
426 }
427
428 static void vd_toggle_show_hidden_files_cb(GtkWidget *widget, gpointer data)
429 {
430         ViewDir *vd = data;
431
432         options->file_filter.show_hidden_files = !options->file_filter.show_hidden_files;
433         if (vd->layout) layout_refresh(vd->layout);
434 }
435
436 static void vd_pop_menu_new_cb(GtkWidget *widget, gpointer data)
437 {
438         ViewDir *vd = data;
439         const gchar *path = NULL;
440         gchar *new_path;
441         gchar *buf;
442
443         switch(vd->type)
444                 {
445                 case DIRVIEW_LIST:
446                         {
447                         if (!vd->path) return;
448                         path = vd->path;
449                         };
450                         break;
451                 case DIRVIEW_TREE:
452                         {
453                         if (!vd->click_fd) return;
454                         path = vd->click_fd->path;
455                         };
456                         break;
457                 }
458
459         buf = concat_dir_and_file(path, _("new_folder"));
460         new_path = unique_filename(buf, NULL, NULL, FALSE);
461         g_free(buf);
462         if (!new_path) return;
463
464         if (!mkdir_utf8(new_path, 0755))
465                 {
466                 gchar *text;
467
468                 text = g_strdup_printf(_("Unable to create folder:\n%s"), new_path);
469                 file_util_warning_dialog(_("Error creating folder"), text, GTK_STOCK_DIALOG_ERROR, vd->view);
470                 g_free(text);
471                 }
472         else
473                 {
474                 FileData *fd = NULL;
475
476                 switch(vd->type)
477                         {
478                         case DIRVIEW_LIST:
479                                 {
480                                 vd_refresh(vd);
481                                 fd = vdlist_row_by_path(vd, new_path, NULL);
482                                 };
483                                 break;
484                         case DIRVIEW_TREE:
485                                 fd = vdtree_populate_path(vd, new_path, TRUE, TRUE);
486                                 break;
487                         }
488                 vd_rename_by_data(vd, fd);
489                 }
490
491         g_free(new_path);
492 }
493
494 static void vd_pop_menu_rename_cb(GtkWidget *widget, gpointer data)
495 {
496         ViewDir *vd = data;
497         
498         vd_rename_by_data(vd, vd->click_fd);
499 }
500
501 GtkWidget *vd_pop_menu(ViewDir *vd, FileData *fd)
502 {
503         GtkWidget *menu;
504         gint active;
505         gint rename_delete_active = FALSE;
506         gint new_folder_active = FALSE;
507
508         active = (fd != NULL);
509         if (fd)
510                 {
511                 switch(vd->type)
512                         {
513                         case DIRVIEW_LIST:
514                                 {
515                                 /* check using . (always row 0) */
516                                 new_folder_active = (vd->path && access_file(vd->path , W_OK | X_OK));
517
518                                 /* ignore .. and . */
519                                 rename_delete_active = (new_folder_active &&
520                                         strcmp(fd->name, ".") != 0 &&
521                                         strcmp(fd->name, "..") != 0 &&
522                                         access_file(fd->path, W_OK | X_OK));
523                                 };
524                                 break;
525                         case DIRVIEW_TREE:
526                                 {
527                                 gchar *parent;
528
529                                 new_folder_active = (fd && access_file(fd->path, W_OK | X_OK));
530                                 parent = remove_level_from_path(fd->path);
531                                 rename_delete_active = access_file(parent, W_OK | X_OK);
532                                 g_free(parent);
533                                 };
534                                 break;
535                         }
536
537                 }
538         menu = popup_menu_short_lived();
539         g_signal_connect(G_OBJECT(menu), "destroy",
540                          G_CALLBACK(vd_popup_destroy_cb), vd);
541
542         menu_item_add_stock_sensitive(menu, _("_Up to parent"), GTK_STOCK_GO_UP,
543                                       (vd->path && strcmp(vd->path, "/") != 0),
544                                       G_CALLBACK(vd_pop_menu_up_cb), vd);
545
546         menu_item_add_divider(menu);
547         menu_item_add_sensitive(menu, _("_Slideshow"), active,
548                                 G_CALLBACK(vd_pop_menu_slide_cb), vd);
549         menu_item_add_sensitive(menu, _("Slideshow recursive"), active,
550                                 G_CALLBACK(vd_pop_menu_slide_rec_cb), vd);
551
552         menu_item_add_divider(menu);
553         menu_item_add_stock_sensitive(menu, _("Find _duplicates..."), GTK_STOCK_FIND, active,
554                                       G_CALLBACK(vd_pop_menu_dupe_cb), vd);
555         menu_item_add_stock_sensitive(menu, _("Find duplicates recursive..."), GTK_STOCK_FIND, active,
556                                       G_CALLBACK(vd_pop_menu_dupe_rec_cb), vd);
557
558         menu_item_add_divider(menu);
559
560         menu_item_add_sensitive(menu, _("_New folder..."), new_folder_active,
561                                 G_CALLBACK(vd_pop_menu_new_cb), vd);
562
563         menu_item_add_sensitive(menu, _("_Rename..."), rename_delete_active,
564                                 G_CALLBACK(vd_pop_menu_rename_cb), vd);
565         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, rename_delete_active,
566                                       G_CALLBACK(vd_pop_menu_delete_cb), vd);
567
568         menu_item_add_divider(menu);
569         /* FIXME */
570         menu_item_add_check(menu, _("View as _tree"), vd->type,
571                             G_CALLBACK(vd_pop_menu_dir_view_as_cb), vd);
572         menu_item_add_check(menu, _("Show _hidden files"), options->file_filter.show_hidden_files,
573                             G_CALLBACK(vd_toggle_show_hidden_files_cb), vd);
574
575         menu_item_add_stock(menu, _("Re_fresh"), GTK_STOCK_REFRESH,
576                             G_CALLBACK(vd_pop_menu_refresh_cb), vd);
577
578         return menu;
579 }
580