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