Update copyright in all files
[geeqie.git] / src / pan-view.c
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include "main.h"
23 #include "pan-view.h"
24
25 #include "bar_exif.h"
26 #include "dnd.h"
27 #include "editors.h"
28 #include "exif.h"
29 #include "metadata.h"
30 #include "fullscreen.h"
31 #include "history_list.h"
32 #include "img-view.h"
33 #include "menu.h"
34 #include "misc.h"
35 #include "pan-types.h"
36 #include "thumb.h"
37 #include "ui_fileops.h"
38 #include "ui_menu.h"
39 #include "ui_tabcomp.h"
40 #include "ui_utildlg.h"
41 #include "uri_utils.h"
42 #include "utilops.h"
43 #include "window.h"
44
45 #include <gdk/gdkkeysyms.h> /* for keyboard values */
46
47 #include <math.h>
48
49
50 #define PAN_WINDOW_DEFAULT_WIDTH 720
51 #define PAN_WINDOW_DEFAULT_HEIGHT 500
52
53 #define PAN_TILE_SIZE 512
54
55 #define ZOOM_INCREMENT 1.0
56 #define ZOOM_LABEL_WIDTH 64
57
58
59 #define PAN_PREF_GROUP          "pan_view_options"
60 #define PAN_PREF_HIDE_WARNING   "hide_performance_warning"
61 #define PAN_PREF_EXIF_PAN_DATE  "use_exif_date"
62 #define PAN_PREF_INFO_IMAGE     "info_image_size"
63 #define PAN_PREF_INFO_EXIF      "info_includes_exif"
64
65
66 static GList *pan_window_list = NULL;
67
68
69 static void pan_layout_update_idle(PanWindow *pw);
70
71 static void pan_fullscreen_toggle(PanWindow *pw, gboolean force_off);
72
73 static void pan_search_toggle_visible(PanWindow *pw, gboolean enable);
74 static void pan_search_activate(PanWindow *pw);
75
76 static void pan_window_close(PanWindow *pw);
77
78 static GtkWidget *pan_popup_menu(PanWindow *pw);
79
80 static void pan_window_dnd_init(PanWindow *pw);
81
82
83 /*
84  *-----------------------------------------------------------------------------
85  * the image/thumb loader queue
86  *-----------------------------------------------------------------------------
87  */
88
89 static gboolean pan_queue_step(PanWindow *pw);
90
91
92 static void pan_queue_thumb_done_cb(ThumbLoader *tl, gpointer data)
93 {
94         PanWindow *pw = data;
95
96         if (pw->queue_pi)
97                 {
98                 PanItem *pi;
99                 gint rc;
100
101                 pi = pw->queue_pi;
102                 pw->queue_pi = NULL;
103
104                 pi->queued = FALSE;
105
106                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
107                 pi->pixbuf = thumb_loader_get_pixbuf(tl);
108
109                 rc = pi->refcount;
110                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
111                 pi->refcount = rc;
112                 }
113
114         thumb_loader_free(pw->tl);
115         pw->tl = NULL;
116
117         while (pan_queue_step(pw));
118 }
119
120 static void pan_queue_image_done_cb(ImageLoader *il, gpointer data)
121 {
122         PanWindow *pw = data;
123
124         if (pw->queue_pi)
125                 {
126                 PanItem *pi;
127                 gint rc;
128
129                 pi = pw->queue_pi;
130                 pw->queue_pi = NULL;
131
132                 pi->queued = FALSE;
133
134                 if (pi->pixbuf) g_object_unref(pi->pixbuf);
135                 pi->pixbuf = image_loader_get_pixbuf(pw->il);
136                 if (pi->pixbuf) g_object_ref(pi->pixbuf);
137
138                 if (pi->pixbuf && pw->size != PAN_IMAGE_SIZE_100 &&
139                     (gdk_pixbuf_get_width(pi->pixbuf) > pi->width ||
140                      gdk_pixbuf_get_height(pi->pixbuf) > pi->height))
141                         {
142                         GdkPixbuf *tmp;
143
144                         tmp = pi->pixbuf;
145                         pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height,
146                                                              (GdkInterpType)options->image.zoom_quality);
147                         g_object_unref(tmp);
148                         }
149
150                 rc = pi->refcount;
151                 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
152                 pi->refcount = rc;
153                 }
154
155         image_loader_free(pw->il);
156         pw->il = NULL;
157
158         while (pan_queue_step(pw));
159 }
160
161 static gboolean pan_queue_step(PanWindow *pw)
162 {
163         PanItem *pi;
164
165         if (!pw->queue) return FALSE;
166
167         pi = pw->queue->data;
168         pw->queue = g_list_remove(pw->queue, pi);
169         pw->queue_pi = pi;
170
171         if (!pw->queue_pi->fd)
172                 {
173                 pw->queue_pi->queued = FALSE;
174                 pw->queue_pi = NULL;
175                 return TRUE;
176                 }
177
178         image_loader_free(pw->il);
179         pw->il = NULL;
180         thumb_loader_free(pw->tl);
181         pw->tl = NULL;
182
183         if (pi->type == PAN_ITEM_IMAGE)
184                 {
185                 pw->il = image_loader_new(pi->fd);
186
187                 if (pw->size != PAN_IMAGE_SIZE_100)
188                         {
189                         image_loader_set_requested_size(pw->il, pi->width, pi->height);
190                         }
191
192                 g_signal_connect(G_OBJECT(pw->il), "error", (GCallback)pan_queue_image_done_cb, pw);
193                 g_signal_connect(G_OBJECT(pw->il), "done", (GCallback)pan_queue_image_done_cb, pw);
194
195                 if (image_loader_start(pw->il)) return FALSE;
196
197                 image_loader_free(pw->il);
198                 pw->il = NULL;
199                 }
200         else if (pi->type == PAN_ITEM_THUMB)
201                 {
202                 pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE);
203
204                 if (!pw->tl->standard_loader)
205                         {
206                         /* The classic loader will recreate a thumbnail any time we
207                          * request a different size than what exists. This view will
208                          * almost never use the user configured sizes so disable cache.
209                          */
210                         thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE);
211                         }
212
213                 thumb_loader_set_callbacks(pw->tl,
214                                            pan_queue_thumb_done_cb,
215                                            pan_queue_thumb_done_cb,
216                                            NULL, pw);
217
218                 if (thumb_loader_start(pw->tl, pi->fd)) return FALSE;
219
220                 thumb_loader_free(pw->tl);
221                 pw->tl = NULL;
222                 }
223
224         pw->queue_pi->queued = FALSE;
225         pw->queue_pi = NULL;
226         return TRUE;
227 }
228
229 static void pan_queue_add(PanWindow *pw, PanItem *pi)
230 {
231         if (!pi || pi->queued || pi->pixbuf) return;
232         if (pw->size <= PAN_IMAGE_SIZE_THUMB_NONE &&
233             (!pi->key || strcmp(pi->key, "info") != 0) )
234                 {
235                 return;
236                 }
237
238         pi->queued = TRUE;
239         pw->queue = g_list_prepend(pw->queue, pi);
240
241         if (!pw->tl && !pw->il) while (pan_queue_step(pw));
242 }
243
244
245 /*
246  *-----------------------------------------------------------------------------
247  * tile request/dispose handlers
248  *-----------------------------------------------------------------------------
249  */
250
251 static gboolean pan_window_request_tile_cb(PixbufRenderer *pr, gint x, gint y,
252                                            gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
253 {
254         PanWindow *pw = data;
255         GList *list;
256         GList *work;
257         gint i;
258
259         pixbuf_set_rect_fill(pixbuf,
260                              0, 0, width, height,
261                              PAN_BACKGROUND_COLOR, 255);
262
263         for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE)
264                 {
265                 gint rx, ry, rw, rh;
266
267                 if (util_clip_region(x, y, width, height,
268                                      i, y, 1, height,
269                                      &rx, &ry, &rw, &rh))
270                         {
271                         pixbuf_draw_rect_fill(pixbuf,
272                                               rx - x, ry - y, rw, rh,
273                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
274                         }
275                 }
276         for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE)
277                 {
278                 gint rx, ry, rw, rh;
279
280                 if (util_clip_region(x, y, width, height,
281                                      x, i, width, 1,
282                                      &rx, &ry, &rw, &rh))
283                         {
284                         pixbuf_draw_rect_fill(pixbuf,
285                                               rx - x, ry - y, rw, rh,
286                                               PAN_GRID_COLOR, PAN_GRID_ALPHA);
287                         }
288                 }
289
290         list = pan_layout_intersect(pw, x, y, width, height);
291         work = list;
292         while (work)
293                 {
294                 PanItem *pi;
295                 gboolean queue = FALSE;
296
297                 pi = work->data;
298                 work = work->next;
299
300                 pi->refcount++;
301
302                 switch (pi->type)
303                         {
304                         case PAN_ITEM_BOX:
305                                 queue = pan_item_box_draw(pw, pi, pixbuf, pr, x, y, width, height);
306                                 break;
307                         case PAN_ITEM_TRIANGLE:
308                                 queue = pan_item_tri_draw(pw, pi, pixbuf, pr, x, y, width, height);
309                                 break;
310                         case PAN_ITEM_TEXT:
311                                 queue = pan_item_text_draw(pw, pi, pixbuf, pr, x, y, width, height);
312                                 break;
313                         case PAN_ITEM_THUMB:
314                                 queue = pan_item_thumb_draw(pw, pi, pixbuf, pr, x, y, width, height);
315                                 break;
316                         case PAN_ITEM_IMAGE:
317                                 queue = pan_item_image_draw(pw, pi, pixbuf, pr, x, y, width, height);
318                                 break;
319                         case PAN_ITEM_NONE:
320                         default:
321                                 break;
322                         }
323
324                 if (queue) pan_queue_add(pw, pi);
325                 }
326
327         g_list_free(list);
328
329         return TRUE;
330 }
331
332 static void pan_window_dispose_tile_cb(PixbufRenderer *pr, gint x, gint y,
333                                        gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
334 {
335         PanWindow *pw = data;
336         GList *list;
337         GList *work;
338
339         list = pan_layout_intersect(pw, x, y, width, height);
340         work = list;
341         while (work)
342                 {
343                 PanItem *pi;
344
345                 pi = work->data;
346                 work = work->next;
347
348                 if (pi->refcount > 0)
349                         {
350                         pi->refcount--;
351
352                         if (pi->refcount == 0)
353                                 {
354                                 if (pi->queued)
355                                         {
356                                         pw->queue = g_list_remove(pw->queue, pi);
357                                         pi->queued = FALSE;
358                                         }
359                                 if (pw->queue_pi == pi) pw->queue_pi = NULL;
360                                 if (pi->pixbuf)
361                                         {
362                                         g_object_unref(pi->pixbuf);
363                                         pi->pixbuf = NULL;
364                                         }
365                                 }
366                         }
367                 }
368
369         g_list_free(list);
370 }
371
372
373 /*
374  *-----------------------------------------------------------------------------
375  * misc
376  *-----------------------------------------------------------------------------
377  */
378
379 static void pan_window_message(PanWindow *pw, const gchar *text)
380 {
381         GList *work;
382         gint count = 0;
383         gint64 size = 0;
384         gchar *ss;
385         gchar *buf;
386
387         if (text)
388                 {
389                 gtk_label_set_text(GTK_LABEL(pw->label_message), text);
390                 return;
391                 }
392
393         work = pw->list_static;
394         if (pw->layout == PAN_LAYOUT_CALENDAR)
395                 {
396                 while (work)
397                         {
398                         PanItem *pi;
399
400                         pi = work->data;
401                         work = work->next;
402
403                         if (pi->fd &&
404                             pi->type == PAN_ITEM_BOX &&
405                             pi->key && strcmp(pi->key, "dot") == 0)
406                                 {
407                                 size += pi->fd->size;
408                                 count++;
409                                 }
410                         }
411                 }
412         else
413                 {
414                 while (work)
415                         {
416                         PanItem *pi;
417
418                         pi = work->data;
419                         work = work->next;
420
421                         if (pi->fd &&
422                             (pi->type == PAN_ITEM_THUMB || pi->type == PAN_ITEM_IMAGE))
423                                 {
424                                 size += pi->fd->size;
425                                 count++;
426                                 }
427                         }
428                 }
429
430         ss = text_from_size_abrev(size);
431         buf = g_strdup_printf(_("%d images, %s"), count, ss);
432         g_free(ss);
433         gtk_label_set_text(GTK_LABEL(pw->label_message), buf);
434         g_free(buf);
435 }
436
437 static void pan_warning_folder(const gchar *path, GtkWidget *parent)
438 {
439         gchar *message;
440
441         message = g_strdup_printf(_("The pan view does not support the folder \"%s\"."), path);
442         warning_dialog(_("Folder not supported"), message,
443                       GTK_STOCK_DIALOG_INFO, parent);
444         g_free(message);
445 }
446
447 static void pan_window_zoom_limit(PanWindow *pw)
448 {
449         gdouble min;
450
451         switch (pw->size)
452                 {
453                 case PAN_IMAGE_SIZE_THUMB_DOTS:
454                 case PAN_IMAGE_SIZE_THUMB_NONE:
455                 case PAN_IMAGE_SIZE_THUMB_SMALL:
456                 case PAN_IMAGE_SIZE_THUMB_NORMAL:
457 #if 0
458                         /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */
459                         min = -16.0;
460                         break;
461 #endif
462                 case PAN_IMAGE_SIZE_THUMB_LARGE:
463                         min = -6.0;
464                         break;
465                 case PAN_IMAGE_SIZE_10:
466                 case PAN_IMAGE_SIZE_25:
467                         min = -4.0;
468                         break;
469                 case PAN_IMAGE_SIZE_33:
470                 case PAN_IMAGE_SIZE_50:
471                 case PAN_IMAGE_SIZE_100:
472                 default:
473                         min = -2.0;
474                         break;
475                 }
476
477         image_zoom_set_limits(pw->imd, min, 32.0);
478 }
479
480
481 /*
482  *-----------------------------------------------------------------------------
483  * cache
484  *-----------------------------------------------------------------------------
485  */
486
487 static gint pan_cache_sort_file_cb(gpointer a, gpointer b)
488 {
489         PanCacheData *pca = a;
490         PanCacheData *pcb = b;
491         return filelist_sort_compare_filedata(pca->fd, pcb->fd);
492 }
493 GList *pan_cache_sort(GList *list, SortType method, gboolean ascend)
494 {
495         return filelist_sort_full(list, method, ascend, (GCompareFunc) pan_cache_sort_file_cb);
496 }
497
498
499 static void pan_cache_free(PanWindow *pw)
500 {
501         GList *work;
502
503         work = pw->cache_list;
504         while (work)
505                 {
506                 PanCacheData *pc;
507
508                 pc = work->data;
509                 work = work->next;
510
511                 cache_sim_data_free(pc->cd);
512                 file_data_unref(pc->fd);
513                 g_free(pc);
514                 }
515
516         g_list_free(pw->cache_list);
517         pw->cache_list = NULL;
518
519         filelist_free(pw->cache_todo);
520         pw->cache_todo = NULL;
521
522         pw->cache_count = 0;
523         pw->cache_total = 0;
524         pw->cache_tick = 0;
525
526         cache_loader_free(pw->cache_cl);
527         pw->cache_cl = NULL;
528 }
529
530 static void pan_cache_fill(PanWindow *pw, FileData *dir_fd)
531 {
532         GList *list;
533
534         pan_cache_free(pw);
535
536         list = pan_list_tree(dir_fd, SORT_NAME, TRUE, pw->ignore_symlinks);
537         pw->cache_todo = g_list_reverse(list);
538
539         pw->cache_total = g_list_length(pw->cache_todo);
540 }
541
542 static void pan_cache_step_done_cb(CacheLoader *cl, gint error, gpointer data)
543 {
544         PanWindow *pw = data;
545
546         if (pw->cache_list)
547                 {
548                 PanCacheData *pc;
549                 pc = pw->cache_list->data;
550
551                 if (!pc->cd)
552                         {
553                         pc->cd = cl->cd;
554                         cl->cd = NULL;
555                         }
556                 }
557
558         cache_loader_free(cl);
559         pw->cache_cl = NULL;
560
561         pan_layout_update_idle(pw);
562 }
563
564 static gboolean pan_cache_step(PanWindow *pw)
565 {
566         FileData *fd;
567         PanCacheData *pc;
568         CacheDataType load_mask;
569
570         if (!pw->cache_todo) return TRUE;
571
572         fd = pw->cache_todo->data;
573         pw->cache_todo = g_list_remove(pw->cache_todo, fd);
574
575         pc = g_new0(PanCacheData, 1);
576         pc->fd = file_data_ref(fd);
577
578         pc->cd = NULL;
579
580         pw->cache_list = g_list_prepend(pw->cache_list, pc);
581
582         cache_loader_free(pw->cache_cl);
583
584         load_mask = CACHE_LOADER_NONE;
585         if (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) load_mask |= CACHE_LOADER_DIMENSIONS;
586         if (pw->exif_date_enable) load_mask |= CACHE_LOADER_DATE;
587         pw->cache_cl = cache_loader_new(pc->fd, load_mask,
588                                         pan_cache_step_done_cb, pw);
589         return (pw->cache_cl == NULL);
590 }
591
592 /* This sync date function is optimized for lists with a common sort */
593 void pan_cache_sync_date(PanWindow *pw, GList *list)
594 {
595         GList *haystack;
596         GList *work;
597
598         haystack = g_list_copy(pw->cache_list);
599
600         work = list;
601         while (work)
602                 {
603                 FileData *fd;
604                 GList *needle;
605
606                 fd = work->data;
607                 work = work->next;
608
609                 needle = haystack;
610                 while (needle)
611                         {
612                         PanCacheData *pc;
613
614                         pc = needle->data;
615                         if (pc->fd == fd)
616                                 {
617                                 if (pc->cd && pc->cd->have_date && pc->cd->date >= 0)
618                                         {
619                                         fd->date = pc->cd->date;
620                                         }
621
622                                 haystack = g_list_delete_link(haystack, needle);
623                                 needle = NULL;
624                                 }
625                         else
626                                 {
627                                 needle = needle->next;
628                                 }
629                         }
630                 }
631
632         g_list_free(haystack);
633 }
634
635 /*
636  *-----------------------------------------------------------------------------
637  * item grid
638  *-----------------------------------------------------------------------------
639  */
640
641 static void pan_grid_clear(PanWindow *pw)
642 {
643         GList *work;
644
645         work = pw->list_grid;
646         while (work)
647                 {
648                 PanGrid *pg;
649
650                 pg = work->data;
651                 work = work->next;
652
653                 g_list_free(pg->list);
654                 g_free(pg);
655                 }
656
657         g_list_free(pw->list_grid);
658         pw->list_grid = NULL;
659
660         pw->list = g_list_concat(pw->list, pw->list_static);
661         pw->list_static = NULL;
662 }
663
664 static void pan_grid_build(PanWindow *pw, gint width, gint height, gint grid_size)
665 {
666         GList *work;
667         gint col, row;
668         gint cw, ch;
669         gint l;
670         gint i, j;
671
672         pan_grid_clear(pw);
673
674         l = g_list_length(pw->list);
675
676         if (l < 1) return;
677
678         col = (gint)(sqrt((gdouble)l / grid_size) * width / height + 0.999);
679         col = CLAMP(col, 1, l / grid_size + 1);
680         row = (gint)((gdouble)l / grid_size / col);
681         if (row < 1) row = 1;
682
683         /* limit minimum size of grid so that a tile will always fit regardless of position */
684         cw = MAX((gint)ceil((gdouble)width / col), PAN_TILE_SIZE * 2);
685         ch = MAX((gint)ceil((gdouble)height / row), PAN_TILE_SIZE * 2);
686
687         row = row * 2 - 1;
688         col = col * 2 - 1;
689
690         DEBUG_1("intersect speedup grid is %dx%d, based on %d average per grid", col, row, grid_size);
691
692         for (j = 0; j < row; j++)
693             for (i = 0; i < col; i++)
694                 {
695                 if ((i + 1) * cw / 2 < width && (j + 1) * ch / 2 < height)
696                         {
697                         PanGrid *pg;
698
699                         pg = g_new0(PanGrid, 1);
700                         pg->x = i * cw / 2;
701                         pg->y = j * ch / 2;
702                         pg->w = cw;
703                         pg->h = ch;
704
705                         pw->list_grid = g_list_prepend(pw->list_grid, pg);
706
707                         DEBUG_1("grid section: %d,%d (%dx%d)", pg->x, pg->y, pg->w, pg->h);
708                         }
709                 }
710
711         work = pw->list;
712         while (work)
713                 {
714                 PanItem *pi;
715                 GList *grid;
716
717                 pi = work->data;
718                 work = work->next;
719
720                 grid = pw->list_grid;
721                 while (grid)
722                         {
723                         PanGrid *pg;
724                         gint rx, ry, rw, rh;
725
726                         pg = grid->data;
727                         grid = grid->next;
728
729                         if (util_clip_region(pi->x, pi->y, pi->width, pi->height,
730                                              pg->x, pg->y, pg->w, pg->h,
731                                              &rx, &ry, &rw, &rh))
732                                 {
733                                 pg->list = g_list_prepend(pg->list, pi);
734                                 }
735                         }
736                 }
737
738         work = pw->list_grid;
739         while (work)
740                 {
741                 PanGrid *pg;
742
743                 pg = work->data;
744                 work = work->next;
745
746                 pg->list = g_list_reverse(pg->list);
747                 }
748
749         pw->list_static = pw->list;
750         pw->list = NULL;
751 }
752
753
754 /*
755  *-----------------------------------------------------------------------------
756  * layout state reset
757  *-----------------------------------------------------------------------------
758  */
759
760 static void pan_window_items_free(PanWindow *pw)
761 {
762         GList *work;
763
764         pan_grid_clear(pw);
765
766         work = pw->list;
767         while (work)
768                 {
769                 PanItem *pi = work->data;
770                 work = work->next;
771
772                 pan_item_free(pi);
773                 }
774
775         g_list_free(pw->list);
776         pw->list = NULL;
777
778         g_list_free(pw->queue);
779         pw->queue = NULL;
780         pw->queue_pi = NULL;
781
782         image_loader_free(pw->il);
783         pw->il = NULL;
784
785         thumb_loader_free(pw->tl);
786         pw->tl = NULL;
787
788         pw->click_pi = NULL;
789         pw->search_pi = NULL;
790 }
791
792
793 /*
794  *-----------------------------------------------------------------------------
795  * layout generation, queries, sizing
796  *-----------------------------------------------------------------------------
797  */
798
799 static void pan_layout_compute(PanWindow *pw, FileData *dir_fd,
800                                gint *width, gint *height,
801                                gint *scroll_x, gint *scroll_y)
802 {
803         pan_window_items_free(pw);
804
805         switch (pw->size)
806                 {
807                 case PAN_IMAGE_SIZE_THUMB_DOTS:
808                         pw->thumb_size = PAN_THUMB_SIZE_DOTS;
809                         pw->thumb_gap = PAN_THUMB_GAP_DOTS;
810                         break;
811                 case PAN_IMAGE_SIZE_THUMB_NONE:
812                         pw->thumb_size = PAN_THUMB_SIZE_NONE;
813                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
814                         break;
815                 case PAN_IMAGE_SIZE_THUMB_SMALL:
816                         pw->thumb_size = PAN_THUMB_SIZE_SMALL;
817                         pw->thumb_gap = PAN_THUMB_GAP_SMALL;
818                         break;
819                 case PAN_IMAGE_SIZE_THUMB_NORMAL:
820                 default:
821                         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
822                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
823                         break;
824                 case PAN_IMAGE_SIZE_THUMB_LARGE:
825                         pw->thumb_size = PAN_THUMB_SIZE_LARGE;
826                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
827                         break;
828                 case PAN_IMAGE_SIZE_10:
829                         pw->image_size = 10;
830                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
831                         break;
832                 case PAN_IMAGE_SIZE_25:
833                         pw->image_size = 25;
834                         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
835                         break;
836                 case PAN_IMAGE_SIZE_33:
837                         pw->image_size = 33;
838                         pw->thumb_gap = PAN_THUMB_GAP_LARGE;
839                         break;
840                 case PAN_IMAGE_SIZE_50:
841                         pw->image_size = 50;
842                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
843                         break;
844                 case PAN_IMAGE_SIZE_100:
845                         pw->image_size = 100;
846                         pw->thumb_gap = PAN_THUMB_GAP_HUGE;
847                         break;
848                 }
849
850         *width = 0;
851         *height = 0;
852         *scroll_x = 0;
853         *scroll_y = 0;
854
855         switch (pw->layout)
856                 {
857                 case PAN_LAYOUT_GRID:
858                 default:
859                         pan_grid_compute(pw, dir_fd, width, height);
860                         break;
861                 case PAN_LAYOUT_FOLDERS_LINEAR:
862                         pan_folder_tree_compute(pw, dir_fd, width, height);
863                         break;
864                 case PAN_LAYOUT_FOLDERS_FLOWER:
865                         pan_flower_compute(pw, dir_fd, width, height, scroll_x, scroll_y);
866                         break;
867                 case PAN_LAYOUT_CALENDAR:
868                         pan_calendar_compute(pw, dir_fd, width, height);
869                         break;
870                 case PAN_LAYOUT_TIMELINE:
871                         pan_timeline_compute(pw, dir_fd, width, height);
872                         break;
873                 }
874
875         pan_cache_free(pw);
876
877         DEBUG_1("computed %d objects", g_list_length(pw->list));
878 }
879
880 static GList *pan_layout_intersect_l(GList *list, GList *item_list,
881                                      gint x, gint y, gint width, gint height)
882 {
883         GList *work;
884
885         work = item_list;
886         while (work)
887                 {
888                 PanItem *pi;
889                 gint rx, ry, rw, rh;
890
891                 pi = work->data;
892                 work = work->next;
893
894                 if (util_clip_region(x, y, width, height,
895                                      pi->x, pi->y, pi->width, pi->height,
896                                      &rx, &ry, &rw, &rh))
897                         {
898                         list = g_list_prepend(list, pi);
899                         }
900                 }
901
902         return list;
903 }
904
905 GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height)
906 {
907         GList *list = NULL;
908         GList *grid;
909         PanGrid *pg = NULL;
910
911         grid = pw->list_grid;
912         while (grid && !pg)
913                 {
914                 pg = grid->data;
915                 grid = grid->next;
916
917                 if (x < pg->x || x + width > pg->x + pg->w ||
918                     y < pg->y || y + height > pg->y + pg->h)
919                         {
920                         pg = NULL;
921                         }
922                 }
923
924         list = pan_layout_intersect_l(list, pw->list, x, y, width, height);
925
926         if (pg)
927                 {
928                 list = pan_layout_intersect_l(list, pg->list, x, y, width, height);
929                 }
930         else
931                 {
932                 list = pan_layout_intersect_l(list, pw->list_static, x, y, width, height);
933                 }
934
935         return list;
936 }
937
938 void pan_layout_resize(PanWindow *pw)
939 {
940         gint width = 0;
941         gint height = 0;
942         GList *work;
943         PixbufRenderer *pr;
944
945         work = pw->list;
946         while (work)
947                 {
948                 PanItem *pi;
949
950                 pi = work->data;
951                 work = work->next;
952
953                 if (width < pi->x + pi->width) width = pi->x + pi->width;
954                 if (height < pi->y + pi->height) height = pi->y + pi->height;
955                 }
956         work = pw->list_static;
957         while (work)
958                 {
959                 PanItem *pi;
960
961                 pi = work->data;
962                 work = work->next;
963
964                 if (width < pi->x + pi->width) width = pi->x + pi->width;
965                 if (height < pi->y + pi->height) height = pi->y + pi->height;
966                 }
967
968         width += PAN_BOX_BORDER * 2;
969         height += PAN_BOX_BORDER * 2;
970
971         pr = PIXBUF_RENDERER(pw->imd->pr);
972         if (width < pr->window_width) width = pr->window_width;
973         if (height < pr->window_width) height = pr->window_height;
974
975         pixbuf_renderer_set_tiles_size(PIXBUF_RENDERER(pw->imd->pr), width, height);
976 }
977
978 static gint pan_layout_update_idle_cb(gpointer data)
979 {
980         PanWindow *pw = data;
981         gint width;
982         gint height;
983         gint scroll_x;
984         gint scroll_y;
985
986         if (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE ||
987             (pw->exif_date_enable && (pw->layout == PAN_LAYOUT_TIMELINE || pw->layout == PAN_LAYOUT_CALENDAR)))
988                 {
989                 if (!pw->cache_list && !pw->cache_todo)
990                         {
991                         pan_cache_fill(pw, pw->dir_fd);
992                         if (pw->cache_todo)
993                                 {
994                                 pan_window_message(pw, _("Reading image data..."));
995                                 return TRUE;
996                                 }
997                         }
998                 if (pw->cache_todo)
999                         {
1000                         pw->cache_count++;
1001                         pw->cache_tick++;
1002                         if (pw->cache_count == pw->cache_total)
1003                                 {
1004                                 pan_window_message(pw, _("Sorting..."));
1005                                 }
1006                         else if (pw->cache_tick > 9)
1007                                 {
1008                                 gchar *buf;
1009
1010                                 buf = g_strdup_printf("%s %d / %d", _("Reading image data..."),
1011                                                       pw->cache_count, pw->cache_total);
1012                                 pan_window_message(pw, buf);
1013                                 g_free(buf);
1014
1015                                 pw->cache_tick = 0;
1016                                 }
1017
1018                         if (pan_cache_step(pw)) return TRUE;
1019
1020                         pw->idle_id = 0;
1021                         return FALSE;
1022                         }
1023                 }
1024
1025         pan_layout_compute(pw, pw->dir_fd, &width, &height, &scroll_x, &scroll_y);
1026
1027         pan_window_zoom_limit(pw);
1028
1029         if (width > 0 && height > 0)
1030                 {
1031                 gdouble align;
1032
1033                 DEBUG_1("Canvas size is %d x %d", width, height);
1034
1035                 pan_grid_build(pw, width, height, 1000);
1036
1037                 pixbuf_renderer_set_tiles(PIXBUF_RENDERER(pw->imd->pr), width, height,
1038                                           PAN_TILE_SIZE, PAN_TILE_SIZE, 10,
1039                                           pan_window_request_tile_cb,
1040                                           pan_window_dispose_tile_cb, pw, 1.0);
1041
1042                 if (scroll_x == 0 && scroll_y == 0)
1043                         {
1044                         align = 0.0;
1045                         }
1046                 else
1047                         {
1048                         align = 0.5;
1049                         }
1050                 pixbuf_renderer_scroll_to_point(PIXBUF_RENDERER(pw->imd->pr), scroll_x, scroll_y, align, align);
1051                 }
1052
1053         pan_window_message(pw, NULL);
1054
1055         pw->idle_id = 0;
1056         return FALSE;
1057 }
1058
1059 static void pan_layout_update_idle(PanWindow *pw)
1060 {
1061         if (!pw->idle_id)
1062                 {
1063                 pw->idle_id = g_idle_add(pan_layout_update_idle_cb, pw);
1064                 }
1065 }
1066
1067 static void pan_layout_update(PanWindow *pw)
1068 {
1069         pan_window_message(pw, _("Sorting images..."));
1070         pan_layout_update_idle(pw);
1071 }
1072
1073 static void pan_layout_set_fd(PanWindow *pw, FileData *dir_fd)
1074 {
1075         if (!dir_fd) return;
1076
1077         if (strcmp(dir_fd->path, G_DIR_SEPARATOR_S) == 0)
1078                 {
1079                 pan_warning_folder(dir_fd->path, pw->window);
1080                 return;
1081                 }
1082
1083         file_data_unref(pw->dir_fd);
1084         pw->dir_fd = file_data_ref(dir_fd);
1085
1086         pan_layout_update(pw);
1087 }
1088
1089
1090 /*
1091  *-----------------------------------------------------------------------------
1092  * keyboard handlers
1093  *-----------------------------------------------------------------------------
1094  */
1095
1096 FileData *pan_menu_click_fd(PanWindow *pw)
1097 {
1098         if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd;
1099         return NULL;
1100 }
1101
1102 static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
1103 {
1104         PanWindow *pw = data;
1105
1106         gdk_window_get_origin(gtk_widget_get_window(pw->imd->pr), x, y);
1107         popup_menu_position_clamp(menu, x, y, 0);
1108 }
1109
1110 static gboolean pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
1111 {
1112         PanWindow *pw = data;
1113         PixbufRenderer *pr;
1114         FileData *fd;
1115         gboolean stop_signal = FALSE;
1116         GtkWidget *menu;
1117         GtkWidget *imd_widget;
1118         gint x = 0;
1119         gint y = 0;
1120         gint focused;
1121         gint on_entry;
1122
1123         pr = PIXBUF_RENDERER(pw->imd->pr);
1124         fd = pan_menu_click_fd(pw);
1125
1126         imd_widget = gtk_container_get_focus_child(GTK_CONTAINER(pw->imd->widget));
1127         focused = (pw->fs || (imd_widget && gtk_widget_has_focus(imd_widget)));
1128         on_entry = (gtk_widget_has_focus(pw->path_entry) ||
1129                     gtk_widget_has_focus(pw->search_entry));
1130
1131         if (focused)
1132                 {
1133                 stop_signal = TRUE;
1134                 switch (event->keyval)
1135                         {
1136                         case GDK_KEY_Left: case GDK_KEY_KP_Left:
1137                                 x -= 1;
1138                                 break;
1139                         case GDK_KEY_Right: case GDK_KEY_KP_Right:
1140                                 x += 1;
1141                                 break;
1142                         case GDK_KEY_Up: case GDK_KEY_KP_Up:
1143                                 y -= 1;
1144                                 break;
1145                         case GDK_KEY_Down: case GDK_KEY_KP_Down:
1146                                 y += 1;
1147                                 break;
1148                         case GDK_KEY_Page_Up: case GDK_KEY_KP_Page_Up:
1149                                 pixbuf_renderer_scroll(pr, 0, 0 - pr->vis_height / 2);
1150                                 break;
1151                         case GDK_KEY_Page_Down: case GDK_KEY_KP_Page_Down:
1152                                 pixbuf_renderer_scroll(pr, 0, pr->vis_height / 2);
1153                                 break;
1154                         case GDK_KEY_Home: case GDK_KEY_KP_Home:
1155                                 pixbuf_renderer_scroll(pr, 0 - pr->vis_width / 2, 0);
1156                                 break;
1157                         case GDK_KEY_End: case GDK_KEY_KP_End:
1158                                 pixbuf_renderer_scroll(pr, pr->vis_width / 2, 0);
1159                                 break;
1160                         default:
1161                                 stop_signal = FALSE;
1162                                 break;
1163                         }
1164
1165                 if (x != 0 || y!= 0)
1166                         {
1167                         if (event->state & GDK_SHIFT_MASK)
1168                                 {
1169                                 x *= 3;
1170                                 y *= 3;
1171                                 }
1172                         keyboard_scroll_calc(&x, &y, event);
1173                         pixbuf_renderer_scroll(pr, x, y);
1174                         }
1175                 }
1176
1177         if (stop_signal) return stop_signal;
1178
1179         if (event->state & GDK_CONTROL_MASK)
1180                 {
1181                 stop_signal = TRUE;
1182                 switch (event->keyval)
1183                         {
1184                         case '1':
1185                         case '2':
1186                         case '3':
1187                         case '4':
1188                         case '5':
1189                         case '6':
1190                         case '7':
1191                         case '8':
1192                         case '9':
1193                         case '0':
1194                                 break;
1195                         case 'C': case 'c':
1196                                 if (fd) file_util_copy(fd, NULL, NULL, GTK_WIDGET(pr));
1197                                 break;
1198                         case 'M': case 'm':
1199                                 if (fd) file_util_move(fd, NULL, NULL, GTK_WIDGET(pr));
1200                                 break;
1201                         case 'R': case 'r':
1202                                 if (fd) file_util_rename(fd, NULL, GTK_WIDGET(pr));
1203                                 break;
1204                         case 'D': case 'd':
1205                                 if (fd) file_util_delete(fd, NULL, GTK_WIDGET(pr));
1206                                 break;
1207                         case 'F': case 'f':
1208                                 pan_search_toggle_visible(pw, TRUE);
1209                                 break;
1210                         case 'G': case 'g':
1211                                 pan_search_activate(pw);
1212                                 break;
1213                         case 'W': case 'w':
1214                                 pan_window_close(pw);
1215                                 break;
1216                         default:
1217                                 stop_signal = FALSE;
1218                                 break;
1219                         }
1220                 }
1221         else
1222                 {
1223                 stop_signal = TRUE;
1224                 switch (event->keyval)
1225                         {
1226                         case GDK_KEY_Escape:
1227                                 if (pw->fs)
1228                                         {
1229                                         pan_fullscreen_toggle(pw, TRUE);
1230                                         }
1231                                 else
1232                                         {
1233                                         pan_search_toggle_visible(pw, FALSE);
1234                                         }
1235                                 break;
1236                         default:
1237                                 stop_signal = FALSE;
1238                                 break;
1239                         }
1240
1241                 if (stop_signal) return stop_signal;
1242
1243                 if (!on_entry)
1244                         {
1245                         stop_signal = TRUE;
1246                         switch (event->keyval)
1247                                 {
1248                                 case '+': case '=': case GDK_KEY_KP_Add:
1249                                         pixbuf_renderer_zoom_adjust(pr, ZOOM_INCREMENT);
1250                                         break;
1251                                 case '-': case GDK_KEY_KP_Subtract:
1252                                         pixbuf_renderer_zoom_adjust(pr, -ZOOM_INCREMENT);
1253                                         break;
1254                                 case 'Z': case 'z': case GDK_KEY_KP_Divide: case '1':
1255                                         pixbuf_renderer_zoom_set(pr, 1.0);
1256                                         break;
1257                                 case '2':
1258                                         pixbuf_renderer_zoom_set(pr, 2.0);
1259                                         break;
1260                                 case '3':
1261                                         pixbuf_renderer_zoom_set(pr, 3.0);
1262                                         break;
1263                                 case '4':
1264                                         pixbuf_renderer_zoom_set(pr, 4.0);
1265                                         break;
1266                                 case '7':
1267                                         pixbuf_renderer_zoom_set(pr, -4.0);
1268                                         break;
1269                                 case '8':
1270                                         pixbuf_renderer_zoom_set(pr, -3.0);
1271                                         break;
1272                                 case '9':
1273                                         pixbuf_renderer_zoom_set(pr, -2.0);
1274                                         break;
1275                                 case 'F': case 'f':
1276                                 case 'V': case 'v':
1277                                 case GDK_KEY_F11:
1278                                         pan_fullscreen_toggle(pw, FALSE);
1279                                         break;
1280                                 case 'I': case 'i':
1281                                         break;
1282                                 case GDK_KEY_Delete: case GDK_KEY_KP_Delete:
1283                                         break;
1284                                 case GDK_KEY_Menu:
1285                                 case GDK_KEY_F10:
1286                                         menu = pan_popup_menu(pw);
1287                                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1288                                                        pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
1289                                         break;
1290                                 case '/':
1291                                         pan_search_toggle_visible(pw, TRUE);
1292                                         break;
1293                                 default:
1294                                         stop_signal = FALSE;
1295                                         break;
1296                                 }
1297                         }
1298                 }
1299
1300         return stop_signal;
1301 }
1302
1303 /*
1304  *-----------------------------------------------------------------------------
1305  * info popup
1306  *-----------------------------------------------------------------------------
1307  */
1308
1309 static void pan_info_add_exif(PanTextAlignment *ta, FileData *fd)
1310 {
1311
1312         if (!fd) return;
1313
1314         pan_text_alignment_add(ta, NULL, NULL);
1315 }
1316
1317
1318 static void pan_info_update(PanWindow *pw, PanItem *pi)
1319 {
1320         PanTextAlignment *ta;
1321         PanItem *pbox;
1322         PanItem *p;
1323         gchar *buf;
1324         gint x1, y1, x2, y2, x3, y3;
1325         gint x, y, w, h;
1326
1327         if (pw->click_pi == pi) return;
1328         if (pi && !pi->fd) pi = NULL;
1329
1330         while ((p = pan_item_find_by_key(pw, PAN_ITEM_NONE, "info"))) pan_item_remove(pw, p);
1331         pw->click_pi = pi;
1332
1333         if (!pi) return;
1334
1335         DEBUG_1("info set to %s", pi->fd->path);
1336
1337         pbox = pan_item_box_new(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
1338                                 PAN_POPUP_BORDER,
1339                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
1340                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1341         pan_item_set_key(pbox, "info");
1342
1343         if (pi->type == PAN_ITEM_THUMB && pi->pixbuf)
1344                 {
1345                 w = gdk_pixbuf_get_width(pi->pixbuf);
1346                 h = gdk_pixbuf_get_height(pi->pixbuf);
1347
1348                 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
1349                 y1 = pi->y + (pi->height - h) / 2 + 8;
1350                 }
1351         else
1352                 {
1353                 x1 = pi->x + pi->width - 8;
1354                 y1 = pi->y + 8;
1355                 }
1356
1357         x2 = pbox->x + 1;
1358         y2 = pbox->y + 36;
1359         x3 = pbox->x + 1;
1360         y3 = pbox->y + 12;
1361         util_clip_triangle(x1, y1, x2, y2, x3, y3,
1362                            &x, &y, &w, &h);
1363
1364         p = pan_item_tri_new(pw, NULL, x, y, w, h,
1365                              x1, y1, x2, y2, x3, y3,
1366                              PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
1367         pan_item_tri_border(p, PAN_BORDER_1 | PAN_BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1368         pan_item_set_key(p, "info");
1369         pan_item_added(pw, p);
1370
1371         ta = pan_text_alignment_new(pw, pbox->x + PREF_PAD_BORDER, pbox->y + PREF_PAD_BORDER, "info");
1372
1373         pan_text_alignment_add(ta, _("Filename:"), pi->fd->name);
1374         buf = remove_level_from_path(pi->fd->path);
1375         pan_text_alignment_add(ta, _("Location:"), buf);
1376         g_free(buf);
1377         pan_text_alignment_add(ta, _("Date:"), text_from_time(pi->fd->date));
1378         buf = text_from_size(pi->fd->size);
1379         pan_text_alignment_add(ta, _("Size:"), buf);
1380         g_free(buf);
1381
1382         if (pw->info_includes_exif)
1383                 {
1384                 pan_info_add_exif(ta, pi->fd);
1385                 }
1386
1387         pan_text_alignment_calc(ta, pbox);
1388         pan_text_alignment_free(ta);
1389
1390         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1391         pan_item_added(pw, pbox);
1392
1393         if (pw->info_image_size > PAN_IMAGE_SIZE_THUMB_NONE)
1394                 {
1395                 gint iw, ih;
1396                 if (image_load_dimensions(pi->fd, &iw, &ih))
1397                         {
1398                         gint scale = 25;
1399
1400                         switch (pw->info_image_size)
1401                                 {
1402                                 case PAN_IMAGE_SIZE_10:
1403                                         scale = 10;
1404                                         break;
1405                                 case PAN_IMAGE_SIZE_25:
1406                                         scale = 25;
1407                                         break;
1408                                 case PAN_IMAGE_SIZE_33:
1409                                         scale = 33;
1410                                         break;
1411                                 case PAN_IMAGE_SIZE_50:
1412                                         scale = 50;
1413                                         break;
1414                                 case PAN_IMAGE_SIZE_100:
1415                                         scale = 100;
1416                                         break;
1417                                 }
1418
1419                         iw = MAX(1, iw * scale / 100);
1420                         ih = MAX(1, ih * scale / 100);
1421
1422                         pbox = pan_item_box_new(pw, NULL, pbox->x, pbox->y + pbox->height + 8, 10, 10,
1423                                                 PAN_POPUP_BORDER,
1424                                                 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
1425                                                 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
1426                         pan_item_set_key(pbox, "info");
1427
1428                         p = pan_item_image_new(pw, file_data_new_group(pi->fd->path),
1429                                                pbox->x + PREF_PAD_BORDER, pbox->y + PREF_PAD_BORDER, iw, ih);
1430                         pan_item_set_key(p, "info");
1431                         pan_item_size_by_item(pbox, p, PREF_PAD_BORDER);
1432
1433                         pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1434                         pan_item_added(pw, pbox);
1435                         }
1436                 }
1437
1438         pan_layout_resize(pw);
1439 }
1440
1441
1442 /*
1443  *-----------------------------------------------------------------------------
1444  * search
1445  *-----------------------------------------------------------------------------
1446  */
1447
1448 static void pan_search_status(PanWindow *pw, const gchar *text)
1449 {
1450         gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
1451 }
1452
1453 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
1454 {
1455         PanItem *pi;
1456         GList *list;
1457         GList *found;
1458         PanItemType type;
1459         gchar *buf;
1460
1461         type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
1462
1463         list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
1464         if (!list) return FALSE;
1465
1466         found = g_list_find(list, pw->click_pi);
1467         if (found && found->next)
1468                 {
1469                 found = found->next;
1470                 pi = found->data;
1471                 }
1472         else
1473                 {
1474                 pi = list->data;
1475                 }
1476
1477         pan_info_update(pw, pi);
1478         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
1479
1480         buf = g_strdup_printf("%s ( %d / %d )",
1481                               (path[0] == G_DIR_SEPARATOR) ? _("path found") : _("filename found"),
1482                               g_list_index(list, pi) + 1,
1483                               g_list_length(list));
1484         pan_search_status(pw, buf);
1485         g_free(buf);
1486
1487         g_list_free(list);
1488
1489         return TRUE;
1490 }
1491
1492 static gboolean pan_search_by_partial(PanWindow *pw, const gchar *text)
1493 {
1494         PanItem *pi;
1495         GList *list;
1496         GList *found;
1497         PanItemType type;
1498         gchar *buf;
1499
1500         type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
1501
1502         list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
1503         if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
1504         if (!list)
1505                 {
1506                 gchar *needle;
1507
1508                 needle = g_utf8_strdown(text, -1);
1509                 list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
1510                 g_free(needle);
1511                 }
1512         if (!list) return FALSE;
1513
1514         found = g_list_find(list, pw->click_pi);
1515         if (found && found->next)
1516                 {
1517                 found = found->next;
1518                 pi = found->data;
1519                 }
1520         else
1521                 {
1522                 pi = list->data;
1523                 }
1524
1525         pan_info_update(pw, pi);
1526         image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
1527
1528         buf = g_strdup_printf("%s ( %d / %d )",
1529                               _("partial match"),
1530                               g_list_index(list, pi) + 1,
1531                               g_list_length(list));
1532         pan_search_status(pw, buf);
1533         g_free(buf);
1534
1535         g_list_free(list);
1536
1537         return TRUE;
1538 }
1539
1540 static gboolean valid_date_separator(gchar c)
1541 {
1542         return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
1543 }
1544
1545 static GList *pan_search_by_date_val(PanWindow *pw, PanItemType type,
1546                                      gint year, gint month, gint day,
1547                                      const gchar *key)
1548 {
1549         GList *list = NULL;
1550         GList *work;
1551
1552         work = g_list_last(pw->list_static);
1553         while (work)
1554                 {
1555                 PanItem *pi;
1556
1557                 pi = work->data;
1558                 work = work->prev;
1559
1560                 if (pi->fd && (pi->type == type || type == PAN_ITEM_NONE) &&
1561                     ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
1562                         {
1563                         struct tm *tl;
1564
1565                         tl = localtime(&pi->fd->date);
1566                         if (tl)
1567                                 {
1568                                 gint match;
1569
1570                                 match = (tl->tm_year == year - 1900);
1571                                 if (match && month >= 0) match = (tl->tm_mon == month - 1);
1572                                 if (match && day > 0) match = (tl->tm_mday == day);
1573
1574                                 if (match) list = g_list_prepend(list, pi);
1575                                 }
1576                         }
1577                 }
1578
1579         return g_list_reverse(list);
1580 }
1581
1582 static gboolean pan_search_by_date(PanWindow *pw, const gchar *text)
1583 {
1584         PanItem *pi = NULL;
1585         GList *list = NULL;
1586         GList *found;
1587         gint year;
1588         gint month = -1;
1589         gint day = -1;
1590         gchar *ptr;
1591         gchar *mptr;
1592         struct tm *lt;
1593         time_t t;
1594         gchar *message;
1595         gchar *buf;
1596         gchar *buf_count;
1597
1598         if (!text) return FALSE;
1599
1600         ptr = (gchar *)text;
1601         while (*ptr != '\0')
1602                 {
1603                 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
1604                 ptr++;
1605                 }
1606
1607         t = time(NULL);
1608         if (t == -1) return FALSE;
1609         lt = localtime(&t);
1610         if (!lt) return FALSE;
1611
1612         if (valid_date_separator(*text))
1613                 {
1614                 year = -1;
1615                 mptr = (gchar *)text;
1616                 }
1617         else
1618                 {
1619                 year = (gint)strtol(text, &mptr, 10);
1620                 if (mptr == text) return FALSE;
1621                 }
1622
1623         if (*mptr != '\0' && valid_date_separator(*mptr))
1624                 {
1625                 gchar *dptr;
1626
1627                 mptr++;
1628                 month = strtol(mptr, &dptr, 10);
1629                 if (dptr == mptr)
1630                         {
1631                         if (valid_date_separator(*dptr))
1632                                 {
1633                                 month = lt->tm_mon + 1;
1634                                 dptr++;
1635                                 }
1636                         else
1637                                 {
1638                                 month = -1;
1639                                 }
1640                         }
1641                 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
1642                         {
1643                         gchar *eptr;
1644                         dptr++;
1645                         day = strtol(dptr, &eptr, 10);
1646                         if (dptr == eptr)
1647                                 {
1648                                 day = lt->tm_mday;
1649                                 }
1650                         }
1651                 }
1652
1653         if (year == -1)
1654                 {
1655                 year = lt->tm_year + 1900;
1656                 }
1657         else if (year < 100)
1658                 {
1659                 if (year > 70)
1660                         year+= 1900;
1661                 else
1662                         year+= 2000;
1663                 }
1664
1665         if (year < 1970 ||
1666             month < -1 || month == 0 || month > 12 ||
1667             day < -1 || day == 0 || day > 31) return FALSE;
1668
1669         t = pan_date_to_time(year, month, day);
1670         if (t < 0) return FALSE;
1671
1672         if (pw->layout == PAN_LAYOUT_CALENDAR)
1673                 {
1674                 list = pan_search_by_date_val(pw, PAN_ITEM_BOX, year, month, day, "day");
1675                 }
1676         else
1677                 {
1678                 PanItemType type;
1679
1680                 type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
1681                 list = pan_search_by_date_val(pw, type, year, month, day, NULL);
1682                 }
1683
1684         if (list)
1685                 {
1686                 found = g_list_find(list, pw->search_pi);
1687                 if (found && found->next)
1688                         {
1689                         found = found->next;
1690                         pi = found->data;
1691                         }
1692                 else
1693                         {
1694                         pi = list->data;
1695                         }
1696                 }
1697
1698         pw->search_pi = pi;
1699
1700         if (pw->layout == PAN_LAYOUT_CALENDAR && pi && pi->type == PAN_ITEM_BOX)
1701                 {
1702                 pan_info_update(pw, NULL);
1703                 pan_calendar_update(pw, pi);
1704                 image_scroll_to_point(pw->imd,
1705                                       pi->x + pi->width / 2,
1706                                       pi->y + pi->height / 2, 0.5, 0.5);
1707                 }
1708         else if (pi)
1709                 {
1710                 pan_info_update(pw, pi);
1711                 image_scroll_to_point(pw->imd,
1712                                       pi->x - PAN_BOX_BORDER * 5 / 2,
1713                                       pi->y, 0.0, 0.5);
1714                 }
1715
1716         if (month > 0)
1717                 {
1718                 buf = pan_date_value_string(t, PAN_DATE_LENGTH_MONTH);
1719                 if (day > 0)
1720                         {
1721                         gchar *tmp;
1722                         tmp = buf;
1723                         buf = g_strdup_printf("%d %s", day, tmp);
1724                         g_free(tmp);
1725                         }
1726                 }
1727         else
1728                 {
1729                 buf = pan_date_value_string(t, PAN_DATE_LENGTH_YEAR);
1730                 }
1731
1732         if (pi)
1733                 {
1734                 buf_count = g_strdup_printf("( %d / %d )",
1735                                             g_list_index(list, pi) + 1,
1736                                             g_list_length(list));
1737                 }
1738         else
1739                 {
1740                 buf_count = g_strdup_printf("(%s)", _("no match"));
1741                 }
1742
1743         message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
1744         g_free(buf);
1745         g_free(buf_count);
1746         pan_search_status(pw, message);
1747         g_free(message);
1748
1749         g_list_free(list);
1750
1751         return TRUE;
1752 }
1753
1754 static void pan_search_activate_cb(const gchar *text, gpointer data)
1755 {
1756         PanWindow *pw = data;
1757
1758         if (!text) return;
1759
1760         tab_completion_append_to_history(pw->search_entry, text);
1761
1762         if (pan_search_by_path(pw, text)) return;
1763
1764         if ((pw->layout == PAN_LAYOUT_TIMELINE ||
1765              pw->layout == PAN_LAYOUT_CALENDAR) &&
1766             pan_search_by_date(pw, text))
1767                 {
1768                 return;
1769                 }
1770
1771         if (pan_search_by_partial(pw, text)) return;
1772
1773         pan_search_status(pw, _("no match"));
1774 }
1775
1776 static void pan_search_activate(PanWindow *pw)
1777 {
1778         gchar *text;
1779
1780         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->search_entry)));
1781         pan_search_activate_cb(text, pw);
1782         g_free(text);
1783 }
1784
1785 static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
1786 {
1787         PanWindow *pw = data;
1788         gboolean visible;
1789
1790         visible = gtk_widget_get_visible(pw->search_box);
1791         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
1792
1793         if (visible)
1794                 {
1795                 gtk_widget_hide(pw->search_box);
1796                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
1797                 }
1798         else
1799                 {
1800                 gtk_widget_show(pw->search_box);
1801                 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
1802                 gtk_widget_grab_focus(pw->search_entry);
1803                 }
1804 }
1805
1806 static void pan_search_toggle_visible(PanWindow *pw, gboolean enable)
1807 {
1808         if (pw->fs) return;
1809
1810         if (enable)
1811                 {
1812                 if (gtk_widget_get_visible(pw->search_box))
1813                         {
1814                         gtk_widget_grab_focus(pw->search_entry);
1815                         }
1816                 else
1817                         {
1818                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
1819                         }
1820                 }
1821         else
1822                 {
1823                 if (gtk_widget_get_visible(pw->search_entry))
1824                         {
1825                         if (gtk_widget_has_focus(pw->search_entry))
1826                                 {
1827                                 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
1828                                 }
1829                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
1830                         }
1831                 }
1832 }
1833
1834
1835 /*
1836  *-----------------------------------------------------------------------------
1837  * main window
1838  *-----------------------------------------------------------------------------
1839  */
1840
1841 static void button_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
1842 {
1843         PanWindow *pw = data;
1844         PanItem *pi = NULL;
1845         GtkWidget *menu;
1846         gint rx, ry;
1847
1848         rx = ry = 0;
1849         if (pr->scale)
1850                 {
1851                 rx = (gdouble)(pr->x_scroll + event->x - pr->x_offset) / pr->scale;
1852                 ry = (gdouble)(pr->y_scroll + event->y - pr->y_offset) / pr->scale;
1853                 }
1854
1855         pi = pan_item_find_by_coord(pw, PAN_ITEM_BOX, rx, ry, "info");
1856         if (pi && event->button == MOUSE_BUTTON_LEFT)
1857                 {
1858                 pan_info_update(pw, NULL);
1859                 return;
1860                 }
1861
1862         pi = pan_item_find_by_coord(pw, (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB,
1863                                     rx, ry, NULL);
1864
1865         switch (event->button)
1866                 {
1867                 case MOUSE_BUTTON_LEFT:
1868                         pan_info_update(pw, pi);
1869
1870                         if (!pi && pw->layout == PAN_LAYOUT_CALENDAR)
1871                                 {
1872                                 pi = pan_item_find_by_coord(pw, PAN_ITEM_BOX, rx, ry, "day");
1873                                 pan_calendar_update(pw, pi);
1874                                 }
1875                         break;
1876                 case MOUSE_BUTTON_MIDDLE:
1877                         break;
1878                 case MOUSE_BUTTON_RIGHT:
1879                         pan_info_update(pw, pi);
1880                         menu = pan_popup_menu(pw);
1881                         gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
1882                         break;
1883                 default:
1884                         break;
1885                 }
1886 }
1887
1888 static void scroll_cb(PixbufRenderer *pr, GdkEventScroll *event, gpointer data)
1889 {
1890         gint w, h;
1891
1892         w = pr->vis_width;
1893         h = pr->vis_height;
1894
1895         if (!(event->state & GDK_SHIFT_MASK))
1896                 {
1897                 w /= 3;
1898                 h /= 3;
1899                 }
1900
1901         if (event->state & GDK_CONTROL_MASK)
1902                 {
1903                 switch (event->direction)
1904                         {
1905                         case GDK_SCROLL_UP:
1906                                 pixbuf_renderer_zoom_adjust_at_point(pr, ZOOM_INCREMENT,
1907                                                                      (gint)event->x, (gint)event->y);
1908                                 break;
1909                         case GDK_SCROLL_DOWN:
1910                                 pixbuf_renderer_zoom_adjust_at_point(pr, -ZOOM_INCREMENT,
1911                                                                      (gint)event->x, (gint)event->y);
1912                                 break;
1913                         default:
1914                                 break;
1915                         }
1916                 }
1917         else
1918                 {
1919                 switch (event->direction)
1920                         {
1921                         case GDK_SCROLL_UP:
1922                                 pixbuf_renderer_scroll(pr, 0, -h);
1923                                 break;
1924                         case GDK_SCROLL_DOWN:
1925                                 pixbuf_renderer_scroll(pr, 0, h);
1926                                 break;
1927                         case GDK_SCROLL_LEFT:
1928                                 pixbuf_renderer_scroll(pr, -w, 0);
1929                                 break;
1930                         case GDK_SCROLL_RIGHT:
1931                                 pixbuf_renderer_scroll(pr, w, 0);
1932                                 break;
1933                         default:
1934                                 break;
1935                         }
1936                 }
1937 }
1938
1939 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
1940 {
1941         g_signal_connect(G_OBJECT(imd->pr), "clicked",
1942                          G_CALLBACK(button_cb), pw);
1943         g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
1944                          G_CALLBACK(scroll_cb), pw);
1945 }
1946
1947 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
1948 {
1949         PanWindow *pw = data;
1950
1951         pw->fs = NULL;
1952         pw->imd = pw->imd_normal;
1953 }
1954
1955 static void pan_fullscreen_toggle(PanWindow *pw, gboolean force_off)
1956 {
1957         if (force_off && !pw->fs) return;
1958
1959         if (pw->fs)
1960                 {
1961                 fullscreen_stop(pw->fs);
1962                 }
1963         else
1964                 {
1965                 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
1966                 pan_image_set_buttons(pw, pw->fs->imd);
1967                 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
1968                                  G_CALLBACK(pan_window_key_press_cb), pw);
1969
1970                 pw->imd = pw->fs->imd;
1971                 }
1972 }
1973
1974 static void pan_window_image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
1975 {
1976         PanWindow *pw = data;
1977         gchar *text;
1978
1979         text = image_zoom_get_as_text(pw->imd);
1980         gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
1981         g_free(text);
1982 }
1983
1984 static void pan_window_image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
1985 {
1986         PanWindow *pw = data;
1987         GtkAdjustment *adj;
1988         GdkRectangle rect;
1989         gint width, height;
1990
1991         if (pr->scale == 0.0) return;
1992
1993         pixbuf_renderer_get_visible_rect(pr, &rect);
1994         pixbuf_renderer_get_image_size(pr, &width, &height);
1995
1996         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
1997         gtk_adjustment_set_page_size(adj, rect.width);
1998         gtk_adjustment_set_page_increment(adj, gtk_adjustment_get_page_size(adj) / 2.0);
1999         gtk_adjustment_set_step_increment(adj, 48.0 / pr->scale);
2000         gtk_adjustment_set_lower(adj, 0.0);
2001         gtk_adjustment_set_upper(adj, MAX((gdouble)width, 1.0));
2002         gtk_adjustment_set_value(adj, (gdouble)rect.x);
2003
2004         pref_signal_block_data(pw->scrollbar_h, pw);
2005         gtk_adjustment_changed(adj);
2006         gtk_adjustment_value_changed(adj);
2007         pref_signal_unblock_data(pw->scrollbar_h, pw);
2008
2009         adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
2010         gtk_adjustment_set_page_size(adj, rect.height);
2011         gtk_adjustment_set_page_increment(adj, gtk_adjustment_get_page_size(adj) / 2.0);
2012         gtk_adjustment_set_step_increment(adj, 48.0 / pr->scale);
2013         gtk_adjustment_set_lower(adj, 0.0);
2014         gtk_adjustment_set_upper(adj, MAX((gdouble)height, 1.0));
2015         gtk_adjustment_set_value(adj, (gdouble)rect.y);
2016
2017         pref_signal_block_data(pw->scrollbar_v, pw);
2018         gtk_adjustment_changed(adj);
2019         gtk_adjustment_value_changed(adj);
2020         pref_signal_unblock_data(pw->scrollbar_v, pw);
2021 }
2022
2023 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
2024 {
2025         PanWindow *pw = data;
2026         PixbufRenderer *pr;
2027         gint x;
2028
2029         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
2030
2031         if (!pr->scale) return;
2032
2033         x = (gint)gtk_range_get_value(range);
2034
2035         pixbuf_renderer_scroll_to_point(pr, x, (gint)((gdouble)pr->y_scroll / pr->scale), 0.0, 0.0);
2036 }
2037
2038 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
2039 {
2040         PanWindow *pw = data;
2041         PixbufRenderer *pr;
2042         gint y;
2043
2044         pr = PIXBUF_RENDERER(pw->imd_normal->pr);
2045
2046         if (!pr->scale) return;
2047
2048         y = (gint)gtk_range_get_value(range);
2049
2050         pixbuf_renderer_scroll_to_point(pr, (gint)((gdouble)pr->x_scroll / pr->scale), y, 0.0, 0.0);
2051 }
2052
2053 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
2054 {
2055         PanWindow *pw = data;
2056
2057         pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
2058         pan_layout_update(pw);
2059 }
2060
2061 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
2062 {
2063         PanWindow *pw = data;
2064
2065         pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
2066         pan_layout_update(pw);
2067 }
2068
2069 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
2070 {
2071         PanWindow *pw = data;
2072         gchar *path;
2073
2074         path = remove_trailing_slash(new_text);
2075         parse_out_relatives(path);
2076
2077         if (!isdir(path))
2078                 {
2079                 warning_dialog(_("Folder not found"),
2080                                _("The entered path is not a folder"),
2081                                GTK_STOCK_DIALOG_WARNING, pw->path_entry);
2082                 }
2083         else
2084                 {
2085                 FileData *dir_fd = file_data_new_dir(path);
2086                 tab_completion_append_to_history(pw->path_entry, path);
2087
2088                 pan_layout_set_fd(pw, dir_fd);
2089                 file_data_unref(dir_fd);
2090                 }
2091
2092         g_free(path);
2093 }
2094
2095 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
2096 {
2097         PanWindow *pw = data;
2098         gchar *text;
2099
2100         if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
2101
2102         text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
2103         pan_window_entry_activate_cb(text, pw);
2104         g_free(text);
2105 }
2106
2107 static void pan_window_close(PanWindow *pw)
2108 {
2109         pan_window_list = g_list_remove(pan_window_list, pw);
2110
2111         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_EXIF_PAN_DATE, pw->exif_date_enable);
2112         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_INFO_IMAGE, pw->info_image_size);
2113         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_INFO_EXIF, pw->info_includes_exif);
2114
2115         if (pw->idle_id)
2116                 {
2117                 g_source_remove(pw->idle_id);
2118                 }
2119
2120         pan_fullscreen_toggle(pw, TRUE);
2121         gtk_widget_destroy(pw->window);
2122
2123         pan_window_items_free(pw);
2124         pan_cache_free(pw);
2125
2126         file_data_unref(pw->dir_fd);
2127
2128         g_free(pw);
2129 }
2130
2131 static gboolean pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
2132 {
2133         PanWindow *pw = data;
2134
2135         pan_window_close(pw);
2136         return TRUE;
2137 }
2138
2139 static void pan_window_new_real(FileData *dir_fd)
2140 {
2141         PanWindow *pw;
2142         GtkWidget *vbox;
2143         GtkWidget *box;
2144         GtkWidget *combo;
2145         GtkWidget *hbox;
2146         GtkWidget *frame;
2147         GtkWidget *table;
2148         GdkGeometry geometry;
2149
2150         pw = g_new0(PanWindow, 1);
2151
2152         pw->dir_fd = file_data_ref(dir_fd);
2153         pw->layout = PAN_LAYOUT_TIMELINE;
2154         pw->size = PAN_IMAGE_SIZE_THUMB_NORMAL;
2155         pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
2156         pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2157
2158         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_EXIF_PAN_DATE, &pw->exif_date_enable))
2159                 {
2160                 pw->exif_date_enable = FALSE;
2161                 }
2162         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_INFO_IMAGE, &pw->info_image_size))
2163                 {
2164                 pw->info_image_size = PAN_IMAGE_SIZE_THUMB_NONE;
2165                 }
2166         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_INFO_EXIF, &pw->info_includes_exif))
2167                 {
2168                 pw->info_includes_exif = TRUE;
2169                 }
2170
2171         pw->ignore_symlinks = TRUE;
2172
2173         pw->idle_id = 0;
2174
2175         pw->window = window_new(GTK_WINDOW_TOPLEVEL, "panview", NULL, NULL, _("Pan View"));
2176
2177         geometry.min_width = DEFAULT_MINIMAL_WINDOW_SIZE;
2178         geometry.min_height = DEFAULT_MINIMAL_WINDOW_SIZE;
2179         gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
2180
2181         gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
2182         gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
2183
2184         vbox = gtk_vbox_new(FALSE, 0);
2185         gtk_container_add(GTK_CONTAINER(pw->window), vbox);
2186         gtk_widget_show(vbox);
2187
2188         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
2189
2190         pref_spacer(box, 0);
2191         pref_label_new(box, _("Location:"));
2192         combo = tab_completion_new_with_history(&pw->path_entry, dir_fd->path, "pan_view_path", -1,
2193                                                 pan_window_entry_activate_cb, pw);
2194         g_signal_connect(G_OBJECT(gtk_widget_get_parent(pw->path_entry)), "changed",
2195                          G_CALLBACK(pan_window_entry_change_cb), pw);
2196         gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
2197         gtk_widget_show(combo);
2198
2199         combo = gtk_combo_box_text_new();
2200         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Timeline"));
2201         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Calendar"));
2202         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Folders"));
2203         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Folders (flower)"));
2204         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Grid"));
2205
2206         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
2207         g_signal_connect(G_OBJECT(combo), "changed",
2208                          G_CALLBACK(pan_window_layout_change_cb), pw);
2209         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
2210         gtk_widget_show(combo);
2211
2212         combo = gtk_combo_box_text_new();
2213         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Dots"));
2214         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("No Images"));
2215         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Small Thumbnails"));
2216         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Normal Thumbnails"));
2217         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("Large Thumbnails"));
2218         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:10 (10%)"));
2219         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:4 (25%)"));
2220         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:3 (33%)"));
2221         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:2 (50%)"));
2222         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), _("1:1 (100%)"));
2223
2224         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
2225         g_signal_connect(G_OBJECT(combo), "changed",
2226                          G_CALLBACK(pan_window_layout_size_cb), pw);
2227         gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
2228         gtk_widget_show(combo);
2229
2230         table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
2231         gtk_table_set_row_spacings(GTK_TABLE(table), 2);
2232         gtk_table_set_col_spacings(GTK_TABLE(table), 2);
2233
2234         pw->imd = image_new(TRUE);
2235         pw->imd_normal = pw->imd;
2236
2237         g_signal_connect(G_OBJECT(pw->imd->pr), "zoom",
2238                          G_CALLBACK(pan_window_image_zoom_cb), pw);
2239         g_signal_connect(G_OBJECT(pw->imd->pr), "scroll_notify",
2240                          G_CALLBACK(pan_window_image_scroll_notify_cb), pw);
2241
2242         gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
2243                          GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
2244         gtk_widget_show(GTK_WIDGET(pw->imd->widget));
2245
2246         pan_window_dnd_init(pw);
2247
2248         pan_image_set_buttons(pw, pw->imd);
2249
2250         pw->scrollbar_h = gtk_hscrollbar_new(NULL);
2251         g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
2252                          G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
2253         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
2254                          GTK_FILL | GTK_EXPAND, 0, 0, 0);
2255         gtk_widget_show(pw->scrollbar_h);
2256
2257         pw->scrollbar_v = gtk_vscrollbar_new(NULL);
2258         g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
2259                          G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
2260         gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
2261                          0, GTK_FILL | GTK_EXPAND, 0, 0);
2262         gtk_widget_show(pw->scrollbar_v);
2263
2264         /* find bar */
2265
2266         pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2267         gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
2268
2269         pref_spacer(pw->search_box, 0);
2270         pref_label_new(pw->search_box, _("Find:"));
2271
2272         hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
2273         gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
2274         gtk_widget_show(hbox);
2275
2276         combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
2277                                                 pan_search_activate_cb, pw);
2278         gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
2279         gtk_widget_show(combo);
2280
2281         pw->search_label = gtk_label_new("");
2282         gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
2283         gtk_widget_show(pw->search_label);
2284
2285         /* status bar */
2286
2287         box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
2288
2289         frame = gtk_frame_new(NULL);
2290         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
2291         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
2292         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
2293         gtk_widget_show(frame);
2294
2295         hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
2296         gtk_container_add(GTK_CONTAINER(frame), hbox);
2297         gtk_widget_show(hbox);
2298
2299         pref_spacer(hbox, 0);
2300         pw->label_message = pref_label_new(hbox, "");
2301
2302         frame = gtk_frame_new(NULL);
2303         gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
2304         gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
2305         gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
2306         gtk_widget_show(frame);
2307
2308         pw->label_zoom = gtk_label_new("");
2309         gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
2310         gtk_widget_show(pw->label_zoom);
2311
2312         pw->search_button = gtk_toggle_button_new();
2313         gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
2314         gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
2315         hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
2316         gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
2317         gtk_widget_show(hbox);
2318         pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
2319         gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
2320         gtk_widget_show(pw->search_button_arrow);
2321         pref_label_new(hbox, _("Find"));
2322
2323         gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
2324         gtk_widget_show(pw->search_button);
2325         g_signal_connect(G_OBJECT(pw->search_button), "clicked",
2326                          G_CALLBACK(pan_search_toggle_cb), pw);
2327
2328         g_signal_connect(G_OBJECT(pw->window), "delete_event",
2329                          G_CALLBACK(pan_window_delete_cb), pw);
2330         g_signal_connect(G_OBJECT(pw->window), "key_press_event",
2331                          G_CALLBACK(pan_window_key_press_cb), pw);
2332
2333         gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
2334
2335         pan_layout_update(pw);
2336
2337         gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
2338         gtk_widget_show(pw->window);
2339
2340         pan_window_list = g_list_append(pan_window_list, pw);
2341 }
2342
2343 /*
2344  *-----------------------------------------------------------------------------
2345  * peformance warnings
2346  *-----------------------------------------------------------------------------
2347  */
2348
2349 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
2350 {
2351         FileData *dir_fd = data;
2352
2353         generic_dialog_close(gd);
2354
2355         pan_window_new_real(dir_fd);
2356         file_data_unref(dir_fd);
2357 }
2358
2359 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
2360 {
2361         gboolean hide_dlg;
2362
2363         hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
2364         pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
2365 }
2366
2367 static gboolean pan_warning(FileData *dir_fd)
2368 {
2369         GenericDialog *gd;
2370         GtkWidget *box;
2371         GtkWidget *group;
2372         GtkWidget *button;
2373         GtkWidget *ct_button;
2374         gboolean hide_dlg;
2375
2376         if (dir_fd && strcmp(dir_fd->path, G_DIR_SEPARATOR_S) == 0)
2377                 {
2378                 pan_warning_folder(dir_fd->path, NULL);
2379                 return TRUE;
2380                 }
2381
2382         if (options->thumbnails.enable_caching &&
2383             options->thumbnails.spec_standard) return FALSE;
2384
2385         if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
2386         if (hide_dlg) return FALSE;
2387
2388         gd = generic_dialog_new(_("Pan View Performance"), "pan_view_warning", NULL, FALSE,
2389                                 NULL, NULL);
2390         gd->data = file_data_ref(dir_fd);
2391         generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
2392                                   pan_warning_ok_cb, TRUE);
2393
2394         box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
2395                                          _("Pan view performance may be poor."),
2396                                          _("To improve performance of thumbnails in the pan view the"
2397                                            " following options can be enabled. Note that both options"
2398                                            " must be enabled to notice a change in performance."));
2399
2400         group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
2401         pref_spacer(group, PREF_PAD_INDENT);
2402         group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
2403
2404         ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
2405                                           options->thumbnails.enable_caching, &options->thumbnails.enable_caching);
2406         button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
2407                                        options->thumbnails.spec_standard, &options->thumbnails.spec_standard);
2408         pref_checkbox_link_sensitivity(ct_button, button);
2409
2410         pref_line(box, 0);
2411
2412         pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
2413                           G_CALLBACK(pan_warning_hide_cb), NULL);
2414
2415         gtk_widget_show(gd->dialog);
2416
2417         return TRUE;
2418 }
2419
2420
2421 /*
2422  *-----------------------------------------------------------------------------
2423  * entry point
2424  *-----------------------------------------------------------------------------
2425  */
2426
2427 void pan_window_new(FileData *dir_fd)
2428 {
2429         if (pan_warning(dir_fd)) return;
2430
2431         pan_window_new_real(dir_fd);
2432 }
2433
2434
2435 /*
2436  *-----------------------------------------------------------------------------
2437  * menus
2438  *-----------------------------------------------------------------------------
2439  */
2440
2441 #define INFO_IMAGE_SIZE_KEY "image_size_data"
2442
2443
2444 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
2445 {
2446         PanWindow *pw = data;
2447         FileData *fd;
2448
2449         fd = pan_menu_click_fd(pw);
2450         if (fd)
2451                 {
2452                 pan_fullscreen_toggle(pw, TRUE);
2453                 view_window_new(fd);
2454                 }
2455 }
2456
2457 static void pan_edit_cb(GtkWidget *widget, gpointer data)
2458 {
2459         PanWindow *pw;
2460         FileData *fd;
2461         const gchar *key = data;
2462
2463         pw = submenu_item_get_data(widget);
2464         if (!pw) return;
2465
2466         fd = pan_menu_click_fd(pw);
2467         if (fd)
2468                 {
2469                 if (!editor_window_flag_set(key))
2470                         {
2471                         pan_fullscreen_toggle(pw, TRUE);
2472                         }
2473                 file_util_start_editor_from_file(key, fd, pw->imd->widget);
2474                 }
2475 }
2476
2477 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
2478 {
2479         PanWindow *pw = data;
2480
2481         image_zoom_adjust(pw->imd, ZOOM_INCREMENT);
2482 }
2483
2484 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
2485 {
2486         PanWindow *pw = data;
2487
2488         image_zoom_adjust(pw->imd, -ZOOM_INCREMENT);
2489 }
2490
2491 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
2492 {
2493         PanWindow *pw = data;
2494
2495         image_zoom_set(pw->imd, 1.0);
2496 }
2497
2498 static void pan_copy_cb(GtkWidget *widget, gpointer data)
2499 {
2500         PanWindow *pw = data;
2501         FileData *fd;
2502
2503         fd = pan_menu_click_fd(pw);
2504         if (fd) file_util_copy(fd, NULL, NULL, pw->imd->widget);
2505 }
2506
2507 static void pan_move_cb(GtkWidget *widget, gpointer data)
2508 {
2509         PanWindow *pw = data;
2510         FileData *fd;
2511
2512         fd = pan_menu_click_fd(pw);
2513         if (fd) file_util_move(fd, NULL, NULL, pw->imd->widget);
2514 }
2515
2516 static void pan_rename_cb(GtkWidget *widget, gpointer data)
2517 {
2518         PanWindow *pw = data;
2519         FileData *fd;
2520
2521         fd = pan_menu_click_fd(pw);
2522         if (fd) file_util_rename(fd, NULL, pw->imd->widget);
2523 }
2524
2525 static void pan_delete_cb(GtkWidget *widget, gpointer data)
2526 {
2527         PanWindow *pw = data;
2528         FileData *fd;
2529
2530         fd = pan_menu_click_fd(pw);
2531         if (fd) file_util_delete(fd, NULL, pw->imd->widget);
2532 }
2533
2534 static void pan_copy_path_cb(GtkWidget *widget, gpointer data)
2535 {
2536         PanWindow *pw = data;
2537         FileData *fd;
2538
2539         fd = pan_menu_click_fd(pw);
2540         if (fd) file_util_copy_path_to_clipboard(fd);
2541 }
2542
2543 static void pan_exif_date_toggle_cb(GtkWidget *widget, gpointer data)
2544 {
2545         PanWindow *pw = data;
2546
2547         pw->exif_date_enable = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2548         pan_layout_update(pw);
2549 }
2550
2551 static void pan_info_toggle_exif_cb(GtkWidget *widget, gpointer data)
2552 {
2553         PanWindow *pw = data;
2554
2555         pw->info_includes_exif = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
2556         /* fixme: sync info now */
2557 }
2558
2559 static void pan_info_toggle_image_cb(GtkWidget *widget, gpointer data)
2560 {
2561         PanWindow *pw = data;
2562
2563         pw->info_image_size = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), INFO_IMAGE_SIZE_KEY));
2564         /* fixme: sync info now */
2565 }
2566
2567 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
2568 {
2569         PanWindow *pw = data;
2570
2571         pan_fullscreen_toggle(pw, FALSE);
2572 }
2573
2574 static void pan_close_cb(GtkWidget *widget, gpointer data)
2575 {
2576         PanWindow *pw = data;
2577
2578         pan_window_close(pw);
2579 }
2580
2581 static void pan_popup_menu_destroy_cb(GtkWidget *widget, gpointer data)
2582 {
2583         GList *editmenu_fd_list = data;
2584
2585         filelist_free(editmenu_fd_list);
2586 }
2587
2588 static GList *pan_view_get_fd_list(PanWindow *pw)
2589 {
2590         GList *list = NULL;
2591         FileData *fd = pan_menu_click_fd(pw);
2592
2593         if (fd) list = g_list_prepend(filelist_copy(fd->sidecar_files), file_data_ref(fd));
2594
2595         return list;
2596 }
2597
2598 static GtkWidget *pan_popup_menu(PanWindow *pw)
2599 {
2600         GtkWidget *menu;
2601         GtkWidget *submenu;
2602         GtkWidget *item;
2603         gboolean active;
2604         GList *editmenu_fd_list;
2605
2606         active = (pw->click_pi != NULL);
2607
2608         menu = popup_menu_short_lived();
2609
2610         menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
2611                             G_CALLBACK(pan_zoom_in_cb), pw);
2612         menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
2613                             G_CALLBACK(pan_zoom_out_cb), pw);
2614         menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
2615                             G_CALLBACK(pan_zoom_1_1_cb), pw);
2616         menu_item_add_divider(menu);
2617
2618         editmenu_fd_list = pan_view_get_fd_list(pw);
2619         g_signal_connect(G_OBJECT(menu), "destroy",
2620                          G_CALLBACK(pan_popup_menu_destroy_cb), editmenu_fd_list);
2621
2622         submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw, editmenu_fd_list);
2623         gtk_widget_set_sensitive(item, active);
2624
2625         menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
2626                                       G_CALLBACK(pan_new_window_cb), pw);
2627
2628         menu_item_add_divider(menu);
2629         menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
2630                                       G_CALLBACK(pan_copy_cb), pw);
2631         menu_item_add_sensitive(menu, _("_Move..."), active,
2632                                 G_CALLBACK(pan_move_cb), pw);
2633         menu_item_add_sensitive(menu, _("_Rename..."), active,
2634                                 G_CALLBACK(pan_rename_cb), pw);
2635         menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
2636                                       G_CALLBACK(pan_delete_cb), pw);
2637         menu_item_add_sensitive(menu, _("_Copy path"), active,
2638                                 G_CALLBACK(pan_copy_path_cb), pw);
2639
2640         menu_item_add_divider(menu);
2641         item = menu_item_add_check(menu, _("Sort by E_xif date"), pw->exif_date_enable,
2642                                    G_CALLBACK(pan_exif_date_toggle_cb), pw);
2643         gtk_widget_set_sensitive(item, (pw->layout == PAN_LAYOUT_TIMELINE || pw->layout == PAN_LAYOUT_CALENDAR));
2644
2645         menu_item_add_divider(menu);
2646
2647         menu_item_add_check(menu, _("_Show Exif information"), pw->info_includes_exif,
2648                             G_CALLBACK(pan_info_toggle_exif_cb), pw);
2649         item = menu_item_add(menu, _("Show im_age"), NULL, NULL);
2650         submenu = gtk_menu_new();
2651         gtk_menu_item_set_submenu(GTK_MENU_ITEM(item), submenu);
2652
2653         item = menu_item_add_check(submenu, _("_None"), (pw->info_image_size == PAN_IMAGE_SIZE_THUMB_NONE),
2654                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2655         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_THUMB_NONE));
2656
2657         item = menu_item_add_check(submenu, _("_Full size"), (pw->info_image_size == PAN_IMAGE_SIZE_100),
2658                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2659         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_100));
2660
2661         item = menu_item_add_check(submenu, _("1:2 (50%)"), (pw->info_image_size == PAN_IMAGE_SIZE_50),
2662                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2663         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_50));
2664
2665         item = menu_item_add_check(submenu, _("1:3 (33%)"), (pw->info_image_size == PAN_IMAGE_SIZE_33),
2666                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2667         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_33));
2668
2669         item = menu_item_add_check(submenu, _("1:4 (25%)"), (pw->info_image_size == PAN_IMAGE_SIZE_25),
2670                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2671         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_25));
2672
2673         item = menu_item_add_check(submenu, _("1:10 (10%)"), (pw->info_image_size == PAN_IMAGE_SIZE_10),
2674                                    G_CALLBACK(pan_info_toggle_image_cb), pw);
2675         g_object_set_data(G_OBJECT(item), INFO_IMAGE_SIZE_KEY, GINT_TO_POINTER(PAN_IMAGE_SIZE_10));
2676
2677
2678
2679         menu_item_add_divider(menu);
2680
2681         if (pw->fs)
2682                 {
2683                 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
2684                 }
2685         else
2686                 {
2687                 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
2688                 }
2689
2690         menu_item_add_divider(menu);
2691         menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
2692
2693         return menu;
2694 }
2695
2696
2697 /*
2698  *-----------------------------------------------------------------------------
2699  * drag and drop
2700  *-----------------------------------------------------------------------------
2701  */
2702
2703 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
2704                                     gint x, gint y,
2705                                     GtkSelectionData *selection_data, guint info,
2706                                     guint time, gpointer data)
2707 {
2708         PanWindow *pw = data;
2709
2710         if (gtk_drag_get_source_widget(context) == pw->imd->pr) return;
2711
2712         if (info == TARGET_URI_LIST)
2713                 {
2714                 GList *list;
2715
2716                 list = uri_filelist_from_gtk_selection_data(selection_data);
2717                 if (list && isdir(((FileData *)list->data)->path))
2718                         {
2719                         FileData *fd = list->data;
2720
2721                         pan_layout_set_fd(pw, fd);
2722                         }
2723
2724                 filelist_free(list);
2725                 }
2726 }
2727
2728 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
2729                                     GtkSelectionData *selection_data, guint info,
2730                                     guint time, gpointer data)
2731 {
2732         PanWindow *pw = data;
2733         FileData *fd;
2734
2735         fd = pan_menu_click_fd(pw);
2736         if (fd)
2737                 {
2738                 GList *list;
2739
2740                 list = g_list_append(NULL, fd);
2741                 uri_selection_data_set_uris_from_filelist(selection_data, list);
2742                 g_list_free(list);
2743                 }
2744         else
2745                 {
2746                 gtk_selection_data_set(selection_data, gtk_selection_data_get_target(selection_data),
2747                                        8, NULL, 0);
2748                 }
2749 }
2750
2751 static void pan_window_dnd_init(PanWindow *pw)
2752 {
2753         GtkWidget *widget;
2754
2755         widget = pw->imd->pr;
2756
2757         gtk_drag_source_set(widget, GDK_BUTTON2_MASK,
2758                             dnd_file_drag_types, dnd_file_drag_types_count,
2759                             GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2760         g_signal_connect(G_OBJECT(widget), "drag_data_get",
2761                          G_CALLBACK(pan_window_set_dnd_data), pw);
2762
2763         gtk_drag_dest_set(widget,
2764                           GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
2765                           dnd_file_drop_types, dnd_file_drop_types_count,
2766                           GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
2767         g_signal_connect(G_OBJECT(widget), "drag_data_received",
2768                          G_CALLBACK(pan_window_get_dnd_data), pw);
2769 }
2770
2771 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */