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