Merge dirlist/dirview dnd code.
[geeqie.git] / src / view_dir_list.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  *
5  * Author: John Ellis
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_list.h"
14
15 #include "dnd.h"
16 #include "dupe.h"
17 #include "filelist.h"
18 #include "layout.h"
19 #include "layout_image.h"
20 #include "layout_util.h"
21 #include "utilops.h"
22 #include "ui_bookmark.h"
23 #include "ui_fileops.h"
24 #include "ui_menu.h"
25 #include "ui_tree_edit.h"
26 #include "view_dir.h"
27
28 #include <gdk/gdkkeysyms.h> /* for keyboard values */
29
30
31 #define VDLIST_PAD 4
32
33 #define VDLIST_INFO(_vd_, _part_) (((ViewDirInfoList *)(_vd_->info))->_part_)
34
35
36 /*
37  *-----------------------------------------------------------------------------
38  * misc
39  *-----------------------------------------------------------------------------
40  */
41
42 gint vdlist_find_row(ViewDir *vd, FileData *fd, GtkTreeIter *iter)
43 {
44         GtkTreeModel *store;
45         gint valid;
46         gint row = 0;
47
48         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
49         valid = gtk_tree_model_get_iter_first(store, iter);
50         while (valid)
51                 {
52                 FileData *fd_n;
53                 gtk_tree_model_get(GTK_TREE_MODEL(store), iter, DIR_COLUMN_POINTER, &fd_n, -1);
54                 if (fd_n == fd) return row;
55
56                 valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), iter);
57                 row++;
58                 }
59
60         return -1;
61 }
62
63
64 FileData *vdlist_row_by_path(ViewDir *vd, const gchar *path, gint *row)
65 {
66         GList *work;
67         gint n;
68
69         if (!path)
70                 {
71                 if (row) *row = -1;
72                 return NULL;
73                 }
74
75         n = 0;
76         work = VDLIST_INFO(vd, list);
77         while (work)
78                 {
79                 FileData *fd = work->data;
80                 if (strcmp(fd->path, path) == 0)
81                         {
82                         if (row) *row = n;
83                         return fd;
84                         }
85                 work = work->next;
86                 n++;
87                 }
88
89         if (row) *row = -1;
90         return NULL;
91 }
92
93 /*
94  *-----------------------------------------------------------------------------
95  * dnd
96  *-----------------------------------------------------------------------------
97  */
98
99 static void vdlist_scroll_to_row(ViewDir *vd, FileData *fd, gfloat y_align)
100 {
101         GtkTreeIter iter;
102
103         if (GTK_WIDGET_REALIZED(vd->view) && vd_find_row(vd, fd, &iter) >= 0)
104                 {
105                 GtkTreeModel *store;
106                 GtkTreePath *tpath;
107
108                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
109                 tpath = gtk_tree_model_get_path(store, &iter);
110                 gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(vd->view), tpath, NULL, TRUE, y_align, 0.0);
111                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(vd->view), tpath, NULL, FALSE);
112                 gtk_tree_path_free(tpath);
113
114                 if (!GTK_WIDGET_HAS_FOCUS(vd->view)) gtk_widget_grab_focus(vd->view);
115                 }
116 }
117
118 /*
119  *-----------------------------------------------------------------------------
120  * main
121  *-----------------------------------------------------------------------------
122  */ 
123
124 static void vdlist_select_row(ViewDir *vd, FileData *fd)
125 {
126         if (fd && vd->select_func)
127                 {
128                 gchar *path;
129
130                 path = g_strdup(fd->path);
131                 vd->select_func(vd, path, vd->select_data);
132                 g_free(path);
133                 }
134 }
135
136 const gchar *vdlist_row_get_path(ViewDir *vd, gint row)
137 {
138         FileData *fd;
139
140         fd = g_list_nth_data(VDLIST_INFO(vd, list), row);
141
142         if (fd) return fd->path;
143
144         return NULL;
145 }
146
147 static void vdlist_populate(ViewDir *vd)
148 {
149         GtkListStore *store;
150         GList *work;
151
152         store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view)));
153         gtk_list_store_clear(store);
154
155         work = VDLIST_INFO(vd, list);
156         while (work)
157                 {
158                 FileData *fd;
159                 GtkTreeIter iter;
160                 GdkPixbuf *pixbuf;
161
162                 fd = work->data;
163
164                 if (access_file(fd->path, R_OK | X_OK) && fd->name)
165                         {
166                         if (fd->name[0] == '.' && fd->name[1] == '\0')
167                                 {
168                                 pixbuf = vd->pf->open;
169                                 }
170                         else if (fd->name[0] == '.' && fd->name[1] == '.' && fd->name[2] == '\0')
171                                 {
172                                 pixbuf = vd->pf->parent;
173                                 }
174                         else
175                                 {
176                                 pixbuf = vd->pf->close;
177                                 }
178                         }
179                 else
180                         {
181                         pixbuf = vd->pf->deny;
182                         }
183
184                 gtk_list_store_append(store, &iter);
185                 gtk_list_store_set(store, &iter,
186                                    DIR_COLUMN_POINTER, fd,
187                                    DIR_COLUMN_ICON, pixbuf,
188                                    DIR_COLUMN_NAME, fd->name, -1);
189
190                 work = work->next;
191                 }
192
193         vd->click_fd = NULL;
194         vd->drop_fd = NULL;
195 }
196
197 gint vdlist_set_path(ViewDir *vd, const gchar *path)
198 {
199         gint ret;
200         FileData *fd;
201         gchar *old_path = NULL;
202         gchar *filepath;
203
204         if (!path) return FALSE;
205         if (vd->path && strcmp(path, vd->path) == 0) return TRUE;
206
207         if (vd->path)
208                 {
209                 gchar *base;
210
211                 base = remove_level_from_path(vd->path);
212                 if (strcmp(base, path) == 0)
213                         {
214                         old_path = g_strdup(filename_from_path(vd->path));
215                         }
216                 g_free(base);
217                 }
218
219         g_free(vd->path);
220         vd->path = g_strdup(path);
221
222         filelist_free(VDLIST_INFO(vd, list));
223         VDLIST_INFO(vd, list) = NULL;
224
225         ret = filelist_read(vd->path, NULL, &VDLIST_INFO(vd, list));
226
227         VDLIST_INFO(vd, list) = filelist_sort(VDLIST_INFO(vd, list), SORT_NAME, TRUE);
228
229         /* add . and .. */
230
231         if (strcmp(vd->path, "/") != 0)
232                 {
233                 filepath = g_strconcat(vd->path, "/", "..", NULL); 
234                 fd = file_data_new_simple(filepath);
235                 VDLIST_INFO(vd, list) = g_list_prepend(VDLIST_INFO(vd, list), fd);
236                 g_free(filepath);
237                 }
238         
239         if (options->file_filter.show_dot_directory)
240                 {
241                 filepath = g_strconcat(vd->path, "/", ".", NULL); 
242                 fd = file_data_new_simple(filepath);
243                 VDLIST_INFO(vd, list) = g_list_prepend(VDLIST_INFO(vd, list), fd);
244                 g_free(filepath);
245         }
246
247         vdlist_populate(vd);
248
249         if (old_path)
250                 {
251                 /* scroll to make last path visible */
252                 FileData *found = NULL;
253                 GList *work;
254
255                 work = VDLIST_INFO(vd, list);
256                 while (work && !found)
257                         {
258                         FileData *fd = work->data;
259                         if (strcmp(old_path, fd->name) == 0) found = fd;
260                         work = work->next;
261                         }
262
263                 if (found) vdlist_scroll_to_row(vd, found, 0.5);
264
265                 g_free(old_path);
266                 return ret;
267                 }
268
269         if (GTK_WIDGET_REALIZED(vd->view))
270                 {
271                 gtk_tree_view_scroll_to_point(GTK_TREE_VIEW(vd->view), 0, 0);
272                 }
273
274         return ret;
275 }
276
277 void vdlist_refresh(ViewDir *vd)
278 {
279         gchar *path;
280
281         path = g_strdup(vd->path);
282         vd->path = NULL;
283         vdlist_set_path(vd, path);
284         g_free(path);
285 }
286
287 static void vdlist_menu_position_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
288 {
289         ViewDir *vd = data;
290         GtkTreeModel *store;
291         GtkTreeIter iter;
292         GtkTreePath *tpath;
293         gint cw, ch;
294
295         if (vd_find_row(vd, vd->click_fd, &iter) < 0) return;
296         store = gtk_tree_view_get_model(GTK_TREE_VIEW(vd->view));
297         tpath = gtk_tree_model_get_path(store, &iter);
298         tree_view_get_cell_clamped(GTK_TREE_VIEW(vd->view), tpath, 0, TRUE, x, y, &cw, &ch);
299         gtk_tree_path_free(tpath);
300         *y += ch;
301         popup_menu_position_clamp(menu, x, y, 0);
302 }
303
304 static gint vdlist_press_key_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
305 {
306         ViewDir *vd = data;
307         GtkTreePath *tpath;
308         
309         if (event->keyval != GDK_Menu) return FALSE;
310
311         gtk_tree_view_get_cursor(GTK_TREE_VIEW(vd->view), &tpath, NULL);
312         if (tpath)
313                 {
314                 GtkTreeModel *store;
315                 GtkTreeIter iter;
316
317                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
318                 gtk_tree_model_get_iter(store, &iter, tpath);
319                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &vd->click_fd, -1);
320                 
321                 gtk_tree_path_free(tpath);
322                 }
323         else
324                 {
325                 vd->click_fd = NULL;
326                 }
327
328         vd_color_set(vd, vd->click_fd, TRUE);
329
330         vd->popup = vd_pop_menu(vd, vd->click_fd);
331
332         gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, vdlist_menu_position_cb, vd, 0, GDK_CURRENT_TIME);
333
334         return TRUE;
335 }
336
337 static gint vdlist_press_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
338 {
339         ViewDir *vd = data;
340         GtkTreePath *tpath;
341         GtkTreeIter iter;
342         FileData *fd = NULL;
343
344         if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
345                                           &tpath, NULL, NULL, NULL))
346                 {
347                 GtkTreeModel *store;
348
349                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
350                 gtk_tree_model_get_iter(store, &iter, tpath);
351                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
352                 gtk_tree_view_set_cursor(GTK_TREE_VIEW(widget), tpath, NULL, FALSE);
353                 gtk_tree_path_free(tpath);
354                 }
355
356         vd->click_fd = fd;
357         vd_color_set(vd, vd->click_fd, TRUE);
358
359         if (bevent->button == 3)
360                 {
361                 vd->popup = vd_pop_menu(vd, vd->click_fd);
362                 gtk_menu_popup(GTK_MENU(vd->popup), NULL, NULL, NULL, NULL,
363                                bevent->button, bevent->time);
364                 }
365
366         return TRUE;
367 }
368
369 static gint vdlist_release_cb(GtkWidget *widget, GdkEventButton *bevent, gpointer data)
370 {
371         ViewDir *vd = data;
372         GtkTreePath *tpath;
373         GtkTreeIter iter;
374         FileData *fd = NULL;
375
376         vd_color_set(vd, vd->click_fd, FALSE);
377
378         if (bevent->button != 1) return TRUE;
379
380         if ((bevent->x != 0 || bevent->y != 0) &&
381             gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bevent->x, bevent->y,
382                                           &tpath, NULL, NULL, NULL))
383                 {
384                 GtkTreeModel *store;
385
386                 store = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
387                 gtk_tree_model_get_iter(store, &iter, tpath);
388                 gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
389                 gtk_tree_path_free(tpath);
390                 }
391
392         if (fd && vd->click_fd == fd)
393                 {
394                 vdlist_select_row(vd, vd->click_fd);
395                 }
396
397         return TRUE;
398 }
399
400 static void vdlist_select_cb(GtkTreeView *tview, GtkTreePath *tpath, GtkTreeViewColumn *column, gpointer data)
401 {
402         ViewDir *vd = data;
403         GtkTreeModel *store;
404         GtkTreeIter iter;
405         FileData *fd;
406
407         store = gtk_tree_view_get_model(tview);
408         gtk_tree_model_get_iter(store, &iter, tpath);
409         gtk_tree_model_get(store, &iter, DIR_COLUMN_POINTER, &fd, -1);
410
411         vdlist_select_row(vd, fd);
412 }
413
414 static GdkColor *vdlist_color_shifted(GtkWidget *widget)
415 {
416         static GdkColor color;
417         static GtkWidget *done = NULL;
418
419         if (done != widget)
420                 {
421                 GtkStyle *style;
422                 
423                 style = gtk_widget_get_style(widget);
424                 memcpy(&color, &style->base[GTK_STATE_NORMAL], sizeof(color));
425                 shift_color(&color, -1, 0);
426                 done = widget;
427                 }
428
429         return &color;
430 }
431
432 static void vdlist_color_cb(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
433                             GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
434 {
435         ViewDir *vd = data;
436         gboolean set;
437
438         gtk_tree_model_get(tree_model, iter, DIR_COLUMN_COLOR, &set, -1);
439         g_object_set(G_OBJECT(cell),
440                      "cell-background-gdk", vdlist_color_shifted(vd->view),
441                      "cell-background-set", set, NULL);
442 }
443
444 static void vdlist_destroy_cb(GtkWidget *widget, gpointer data)
445 {
446         ViewDir *vd = data;
447
448         vd_dnd_drop_scroll_cancel(vd);
449         widget_auto_scroll_stop(vd->view);
450
451         filelist_free(VDLIST_INFO(vd, list));
452 }
453
454 ViewDir *vdlist_new(ViewDir *vd, const gchar *path)
455 {
456         GtkListStore *store;
457         GtkTreeSelection *selection;
458         GtkTreeViewColumn *column;
459         GtkCellRenderer *renderer;
460
461         vd->info = g_new0(ViewDirInfoList, 1);
462         vd->type = DIRVIEW_LIST;
463         vd->widget_destroy_cb = vdlist_destroy_cb;
464
465         VDLIST_INFO(vd, list) = NULL;
466
467         store = gtk_list_store_new(4, G_TYPE_POINTER, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_BOOLEAN);
468         vd->view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
469         g_object_unref(store);
470
471         gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(vd->view), FALSE);
472         gtk_tree_view_set_enable_search(GTK_TREE_VIEW(vd->view), FALSE);
473         g_signal_connect(G_OBJECT(vd->view), "row_activated",
474
475                          G_CALLBACK(vdlist_select_cb), vd);
476
477         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(vd->view));
478         gtk_tree_selection_set_mode(selection, GTK_SELECTION_NONE);
479
480         column = gtk_tree_view_column_new();
481         gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
482
483         renderer = gtk_cell_renderer_pixbuf_new();
484         gtk_tree_view_column_pack_start(column, renderer, FALSE);
485         gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", DIR_COLUMN_ICON);
486         gtk_tree_view_column_set_cell_data_func(column, renderer, vdlist_color_cb, vd, NULL);
487
488         renderer = gtk_cell_renderer_text_new();
489         gtk_tree_view_column_pack_start(column, renderer, TRUE);
490         gtk_tree_view_column_add_attribute(column, renderer, "text", DIR_COLUMN_NAME);
491         gtk_tree_view_column_set_cell_data_func(column, renderer, vdlist_color_cb, vd, NULL);
492
493         gtk_tree_view_append_column(GTK_TREE_VIEW(vd->view), column);
494
495         g_signal_connect(G_OBJECT(vd->view), "key_press_event",
496                            G_CALLBACK(vdlist_press_key_cb), vd);
497         gtk_container_add(GTK_CONTAINER(vd->widget), vd->view);
498         gtk_widget_show(vd->view);
499
500         vd_dnd_init(vd);
501
502         g_signal_connect(G_OBJECT(vd->view), "button_press_event",
503                          G_CALLBACK(vdlist_press_cb), vd);
504         g_signal_connect(G_OBJECT(vd->view), "button_release_event",
505                          G_CALLBACK(vdlist_release_cb), vd);
506
507         if (path) vdlist_set_path(vd, path);
508
509         return vd;
510 }