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!
17 #include "cache-loader.h"
21 #include "fullscreen.h"
23 #include "image-load.h"
27 #include "pixbuf-renderer.h"
28 #include "pixbuf_util.h"
31 #include "ui_bookmark.h"
32 #include "ui_fileops.h"
35 #include "ui_tabcomp.h"
37 #include <gdk/gdkkeysyms.h> /* for keyboard values */
41 #define PAN_WINDOW_DEFAULT_WIDTH 720
42 #define PAN_WINDOW_DEFAULT_HEIGHT 500
44 #define PAN_TILE_SIZE 512
46 #define PAN_THUMB_SIZE_DOTS 4
47 #define PAN_THUMB_SIZE_NONE 24
48 #define PAN_THUMB_SIZE_SMALL 64
49 #define PAN_THUMB_SIZE_NORMAL 128
50 #define PAN_THUMB_SIZE_LARGE 256
51 #define PAN_THUMB_SIZE pw->thumb_size
53 #define PAN_THUMB_GAP_DOTS 2
54 #define PAN_THUMB_GAP_SMALL 14
55 #define PAN_THUMB_GAP_NORMAL 30
56 #define PAN_THUMB_GAP_LARGE 40
57 #define PAN_THUMB_GAP_HUGE 50
58 #define PAN_THUMB_GAP pw->thumb_gap
60 #define PAN_SHADOW_OFFSET 6
61 #define PAN_SHADOW_FADE 5
62 #define PAN_SHADOW_COLOR 0, 0, 0
63 #define PAN_SHADOW_ALPHA 64
65 #define PAN_OUTLINE_THICKNESS 1
66 #define PAN_OUTLINE_COLOR_1 255, 255, 255
67 #define PAN_OUTLINE_COLOR_2 64, 64, 64
68 #define PAN_OUTLINE_ALPHA 180
70 #define PAN_BACKGROUND_COLOR 150, 150, 150
72 #define PAN_GRID_SIZE 60
73 #define PAN_GRID_COLOR 0, 0, 0
74 #define PAN_GRID_ALPHA 20
76 #define PAN_FOLDER_BOX_COLOR 255, 255, 255
77 #define PAN_FOLDER_BOX_ALPHA 100
78 #define PAN_FOLDER_BOX_BORDER 20
80 #define PAN_FOLDER_BOX_OUTLINE_THICKNESS 4
81 #define PAN_FOLDER_BOX_OUTLINE_COLOR 0, 0, 0
82 #define PAN_FOLDER_BOX_OUTLINE_ALPHA 128
84 #define PAN_TEXT_BORDER_SIZE 4
85 #define PAN_TEXT_COLOR 0, 0, 0
87 #define PAN_POPUP_COLOR 255, 255, 225
88 #define PAN_POPUP_ALPHA 255
89 #define PAN_POPUP_BORDER 1
90 #define PAN_POPUP_BORDER_COLOR 0, 0, 0
91 #define PAN_POPUP_TEXT_COLOR 0, 0, 0
93 #define PAN_CAL_POPUP_COLOR 220, 220, 220
94 #define PAN_CAL_POPUP_ALPHA 255
95 #define PAN_CAL_POPUP_BORDER 1
96 #define PAN_CAL_POPUP_BORDER_COLOR 0, 0, 0
97 #define PAN_CAL_POPUP_TEXT_COLOR 0, 0, 0
99 #define PAN_CAL_DAY_WIDTH 100
100 #define PAN_CAL_DAY_HEIGHT 80
102 #define PAN_CAL_DAY_COLOR 255, 255, 255
103 #define PAN_CAL_DAY_ALPHA 220
104 #define PAN_CAL_DAY_BORDER 2
105 #define PAN_CAL_DAY_BORDER_COLOR 0, 0, 0
106 #define PAN_CAL_DAY_TEXT_COLOR 0, 0, 0
108 #define PAN_CAL_MONTH_COLOR 255, 255, 255
109 #define PAN_CAL_MONTH_ALPHA 200
110 #define PAN_CAL_MONTH_BORDER 4
111 #define PAN_CAL_MONTH_BORDER_COLOR 0, 0, 0
112 #define PAN_CAL_MONTH_TEXT_COLOR 0, 0, 0
114 #define PAN_CAL_DOT_SIZE 3
115 #define PAN_CAL_DOT_GAP 2
116 #define PAN_CAL_DOT_COLOR 128, 128, 128
117 #define PAN_CAL_DOT_ALPHA 128
120 #define PAN_GROUP_MAX 16
122 #define ZOOM_INCREMENT 1.0
123 #define ZOOM_LABEL_WIDTH 64
126 #define PAN_PREF_GROUP "pan_view_options"
127 #define PAN_PREF_HIDE_WARNING "hide_performance_warning"
128 #define PAN_PREF_EXIF_DATE "use_exif_date"
134 LAYOUT_FOLDERS_LINEAR,
135 LAYOUT_FOLDERS_FLOWER,
140 LAYOUT_SIZE_THUMB_DOTS = 0,
141 LAYOUT_SIZE_THUMB_NONE,
142 LAYOUT_SIZE_THUMB_SMALL,
143 LAYOUT_SIZE_THUMB_NORMAL,
144 LAYOUT_SIZE_THUMB_LARGE,
163 TEXT_ATTR_BOLD = 1 << 0,
164 TEXT_ATTR_HEADING = 1 << 1,
165 TEXT_ATTR_MARKUP = 1 << 2
176 typedef struct _PanItem PanItem;
191 TextAttrType text_attr;
209 typedef struct _PanWindow PanWindow;
214 ImageWindow *imd_normal;
217 GtkWidget *path_entry;
219 GtkWidget *label_message;
220 GtkWidget *label_zoom;
222 GtkWidget *search_box;
223 GtkWidget *search_entry;
224 GtkWidget *search_label;
225 GtkWidget *search_button;
226 GtkWidget *search_button_arrow;
228 GtkWidget *date_button;
230 GtkWidget *scrollbar_h;
231 GtkWidget *scrollbar_v;
241 gint exif_date_enable;
243 gint ignore_symlinks;
254 CacheLoader *cache_cl;
267 typedef struct _PanGrid PanGrid;
276 typedef struct _PanCacheData PanCacheData;
277 struct _PanCacheData {
283 static GList *pan_window_list = NULL;
286 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend,
287 gint ignore_symlinks);
289 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height);
291 static void pan_window_layout_update_idle(PanWindow *pw);
293 static GtkWidget *pan_popup_menu(PanWindow *pw);
294 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off);
296 static void pan_window_close(PanWindow *pw);
298 static void pan_window_dnd_init(PanWindow *pw);
310 static gint date_compare(time_t a, time_t b, DateLengthType length)
315 if (length == DATE_LENGTH_EXACT) return (a == b);
317 if (!localtime_r(&a, &ta) ||
318 !localtime_r(&b, &tb)) return FALSE;
320 if (ta.tm_year != tb.tm_year) return FALSE;
321 if (length == DATE_LENGTH_YEAR) return TRUE;
323 if (ta.tm_mon != tb.tm_mon) return FALSE;
324 if (length == DATE_LENGTH_MONTH) return TRUE;
326 if (length == DATE_LENGTH_WEEK) return (ta.tm_yday / 7 == tb.tm_yday / 7);
328 if (ta.tm_mday != tb.tm_mday) return FALSE;
329 if (length == DATE_LENGTH_DAY) return TRUE;
331 return (ta.tm_hour == tb.tm_hour);
334 static gint date_value(time_t d, DateLengthType length)
338 if (!localtime_r(&d, &td)) return -1;
342 case DATE_LENGTH_DAY:
345 case DATE_LENGTH_WEEK:
348 case DATE_LENGTH_MONTH:
349 return td.tm_mon + 1;
351 case DATE_LENGTH_YEAR:
352 return td.tm_year + 1900;
354 case DATE_LENGTH_EXACT:
362 static gchar *date_value_string(time_t d, DateLengthType length)
366 gchar *format = NULL;
368 if (!localtime_r(&d, &td)) return g_strdup("");
372 case DATE_LENGTH_DAY:
373 return g_strdup_printf("%d", td.tm_mday);
375 case DATE_LENGTH_WEEK:
378 case DATE_LENGTH_MONTH:
381 case DATE_LENGTH_YEAR:
382 return g_strdup_printf("%d", td.tm_year + 1900);
384 case DATE_LENGTH_EXACT:
386 return g_strdup(text_from_time(d));
391 if (format && strftime(buf, sizeof(buf), format, &td) > 0)
393 gchar *ret = g_locale_to_utf8(buf, -1, NULL, NULL, NULL);
400 static time_t date_to_time(gint year, gint month, gint day)
407 lt.tm_mday = (day >= 1 && day <= 31) ? day : 1;
408 lt.tm_mon = (month >= 1 && month <= 12) ? month - 1 : 0;
409 lt.tm_year = year - 1900;
416 *-----------------------------------------------------------------------------
418 *-----------------------------------------------------------------------------
421 static void pan_cache_free(PanWindow *pw)
425 work = pw->cache_list;
433 cache_sim_data_free(pc->cd);
434 file_data_free((FileData *)pc);
437 g_list_free(pw->cache_list);
438 pw->cache_list = NULL;
440 filelist_free(pw->cache_todo);
441 pw->cache_todo = NULL;
447 cache_loader_free(pw->cache_cl);
451 static void pan_cache_fill(PanWindow *pw, const gchar *path)
457 list = pan_window_layout_list(path, SORT_NAME, TRUE, pw->ignore_symlinks);
458 pw->cache_todo = g_list_reverse(list);
460 pw->cache_total = g_list_length(pw->cache_todo);
463 static void pan_cache_step_done_cb(CacheLoader *cl, gint error, gpointer data)
465 PanWindow *pw = data;
470 pc = pw->cache_list->data;
479 cache_loader_free(cl);
482 pan_window_layout_update_idle(pw);
485 static gint pan_cache_step(PanWindow *pw)
489 CacheDataType load_mask;
491 if (!pw->cache_todo) return TRUE;
493 fd = pw->cache_todo->data;
494 pw->cache_todo = g_list_remove(pw->cache_todo, fd);
497 if (enable_thumb_caching)
501 found = cache_find_location(CACHE_TYPE_SIM, fd->path);
502 if (found && filetime(found) == fd->date)
504 cd = cache_sim_data_load(found);
509 if (!cd) cd = cache_sim_data_new();
513 cd->dimensions = image_load_dimensions(fd->path, &cd->width, &cd->height);
514 if (enable_thumb_caching &&
520 base = cache_get_location(CACHE_TYPE_SIM, fd->path, FALSE, &mode);
521 if (cache_ensure_dir_exists(base, mode))
524 cd->path = cache_get_location(CACHE_TYPE_SIM, fd->path, TRUE, NULL);
525 if (cache_sim_data_save(cd))
527 filetime_set(cd->path, filetime(fd->path));
536 pc = g_new0(PanCacheData, 1);
537 memcpy(pc, fd, sizeof(FileData));
542 pw->cache_list = g_list_prepend(pw->cache_list, pc);
544 cache_loader_free(pw->cache_cl);
546 load_mask = CACHE_LOADER_NONE;
547 if (pw->size > LAYOUT_SIZE_THUMB_LARGE) load_mask |= CACHE_LOADER_DIMENSIONS;
548 if (pw->exif_date_enable) load_mask |= CACHE_LOADER_DATE;
549 pw->cache_cl = cache_loader_new(((FileData *)pc)->path, load_mask,
550 pan_cache_step_done_cb, pw);
551 return (pw->cache_cl == NULL);
554 /* This sync date function is optimized for lists with a common sort */
555 static void pan_cache_sync_date(PanWindow *pw, GList *list)
560 haystack = g_list_copy(pw->cache_list);
578 path = ((FileData *)pc)->path;
579 if (path && strcmp(path, fd->path) == 0)
581 if (pc->cd && pc->cd->have_date && pc->cd->date >= 0)
583 fd->date = pc->cd->date;
586 haystack = g_list_delete_link(haystack, needle);
591 needle = needle->next;
596 g_list_free(haystack);
600 *-----------------------------------------------------------------------------
602 *-----------------------------------------------------------------------------
605 static void pan_grid_clear(PanWindow *pw)
609 work = pw->list_grid;
617 g_list_free(pg->list);
621 g_list_free(pw->list_grid);
622 pw->list_grid = NULL;
624 pw->list = g_list_concat(pw->list, pw->list_static);
625 pw->list_static = NULL;
628 static void pan_grid_build(PanWindow *pw, gint width, gint height, gint grid_size)
641 l = g_list_length(pw->list);
645 total = (gdouble)width * (gdouble)height / (gdouble)l;
648 aw = (gdouble)width / s;
649 ah = (gdouble)height / s;
651 col = (gint)(sqrt((gdouble)l / grid_size) * width / height + 0.999);
652 col = CLAMP(col, 1, l / grid_size + 1);
653 row = (gint)((gdouble)l / grid_size / col);
654 if (row < 1) row = 1;
656 /* limit minimum size of grid so that a tile will always fit regardless of position */
657 cw = MAX((gint)ceil((gdouble)width / col), PAN_TILE_SIZE * 2);
658 ch = MAX((gint)ceil((gdouble)height / row), PAN_TILE_SIZE * 2);
663 printf("intersect speedup grid is %dx%d, based on %d average per grid\n", col, row, grid_size);
665 for (j = 0; j < row; j++)
666 for (i = 0; i < col; i++)
668 if ((i + 1) * cw / 2 < width && (j + 1) * ch / 2 < height)
672 pg = g_new0(PanGrid, 1);
679 pw->list_grid = g_list_prepend(pw->list_grid, pg);
681 if (debug) printf("grid section: %d,%d (%dx%d)\n", pg->x, pg->y, pg->w, pg->h);
694 grid = pw->list_grid;
703 if (util_clip_region(pi->x, pi->y, pi->width, pi->height,
704 pg->x, pg->y, pg->w, pg->h,
707 pg->list = g_list_prepend(pg->list, pi);
712 work = pw->list_grid;
720 pg->list = g_list_reverse(pg->list);
723 pw->list_static = pw->list;
730 *-----------------------------------------------------------------------------
732 *-----------------------------------------------------------------------------
735 static void pan_item_free(PanItem *pi)
739 if (pi->pixbuf) g_object_unref(pi->pixbuf);
740 if (pi->fd) file_data_free(pi->fd);
748 static void pan_window_items_free(PanWindow *pw)
757 PanItem *pi = work->data;
763 g_list_free(pw->list);
766 g_list_free(pw->queue);
770 image_loader_free(pw->il);
773 thumb_loader_free(pw->tl);
777 pw->search_pi = NULL;
780 static PanItem *pan_item_new_thumb(PanWindow *pw, FileData *fd, gint x, gint y)
784 pi = g_new0(PanItem, 1);
785 pi->type = ITEM_THUMB;
789 pi->width = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2;
790 pi->height = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2;
796 pw->list = g_list_prepend(pw->list, pi);
801 static PanItem *pan_item_new_box(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height,
803 guint8 base_r, guint8 base_g, guint8 base_b, guint8 base_a,
804 guint8 bord_r, guint8 bord_g, guint8 bord_b, guint8 bord_a)
808 pi = g_new0(PanItem, 1);
816 pi->color_r = base_r;
817 pi->color_g = base_g;
818 pi->color_b = base_b;
819 pi->color_a = base_a;
821 pi->color2_r = bord_r;
822 pi->color2_g = bord_g;
823 pi->color2_b = bord_b;
824 pi->color2_a = bord_a;
825 pi->border = border_size;
827 pw->list = g_list_prepend(pw->list, pi);
832 static void pan_item_box_shadow(PanItem *pi, gint offset, gint fade)
836 if (!pi || pi->type != ITEM_BOX) return;
841 pi->width -= shadow[0];
842 pi->height -= shadow[0];
845 shadow = g_new0(gint, 2);
850 pi->height += offset;
856 static PanItem *pan_item_new_tri(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height,
857 gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
858 guint8 r, guint8 g, guint8 b, guint8 a)
863 pi = g_new0(PanItem, 1);
864 pi->type = ITEM_TRIANGLE;
875 coord = g_new0(gint, 6);
885 pi->border = BORDER_NONE;
887 pw->list = g_list_prepend(pw->list, pi);
892 static void pan_item_tri_border(PanItem *pi, gint borders,
893 guint8 r, guint8 g, guint8 b, guint8 a)
895 if (!pi || pi->type != ITEM_TRIANGLE) return;
897 pi->border = borders;
905 static PangoLayout *pan_item_text_layout(PanItem *pi, GtkWidget *widget)
909 layout = gtk_widget_create_pango_layout(widget, NULL);
911 if (pi->text_attr & TEXT_ATTR_MARKUP)
913 pango_layout_set_markup(layout, pi->text, -1);
917 if (pi->text_attr & TEXT_ATTR_BOLD ||
918 pi->text_attr & TEXT_ATTR_HEADING)
923 pal = pango_attr_list_new();
924 if (pi->text_attr & TEXT_ATTR_BOLD)
926 pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
928 pa->end_index = G_MAXINT;
929 pango_attr_list_insert(pal, pa);
931 if (pi->text_attr & TEXT_ATTR_HEADING)
933 pa = pango_attr_scale_new(PANGO_SCALE_LARGE);
935 pa->end_index = G_MAXINT;
936 pango_attr_list_insert(pal, pa);
938 pango_layout_set_attributes(layout, pal);
939 pango_attr_list_unref(pal);
942 pango_layout_set_text(layout, pi->text, -1);
946 static void pan_item_text_compute_size(PanItem *pi, GtkWidget *widget)
950 if (!pi || !pi->text || !widget) return;
952 layout = pan_item_text_layout(pi, widget);
953 pango_layout_get_pixel_size(layout, &pi->width, &pi->height);
954 g_object_unref(G_OBJECT(layout));
956 pi->width += PAN_TEXT_BORDER_SIZE * 2;
957 pi->height += PAN_TEXT_BORDER_SIZE * 2;
960 static PanItem *pan_item_new_text(PanWindow *pw, gint x, gint y, const gchar *text, TextAttrType attr,
961 guint8 r, guint8 g, guint8 b, guint8 a)
965 pi = g_new0(PanItem, 1);
966 pi->type = ITEM_TEXT;
969 pi->text = g_strdup(text);
970 pi->text_attr = attr;
977 pan_item_text_compute_size(pi, pw->imd->pr);
979 pw->list = g_list_prepend(pw->list, pi);
984 static void pan_item_set_key(PanItem *pi, const gchar *key)
991 pi->key = g_strdup(key);
995 static void pan_item_added(PanWindow *pw, PanItem *pi)
998 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
1001 static void pan_item_remove(PanWindow *pw, PanItem *pi)
1005 if (pw->click_pi == pi) pw->click_pi = NULL;
1006 if (pw->queue_pi == pi) pw->queue_pi = NULL;
1007 if (pw->search_pi == pi) pw->search_pi = NULL;
1008 pw->queue = g_list_remove(pw->queue, pi);
1010 pw->list = g_list_remove(pw->list, pi);
1011 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
1015 static void pan_item_size_by_item(PanItem *pi, PanItem *child, gint border)
1017 if (!pi || !child) return;
1019 if (pi->x + pi->width < child->x + child->width + border)
1020 pi->width = child->x + child->width + border - pi->x;
1022 if (pi->y + pi->height < child->y + child->height + border)
1023 pi->height = child->y + child->height + border - pi->y;
1026 static void pan_item_size_coordinates(PanItem *pi, gint border, gint *w, gint *h)
1030 if (*w < pi->x + pi->width + border) *w = pi->x + pi->width + border;
1031 if (*h < pi->y + pi->height + border) *h = pi->y + pi->height + border;
1034 static void pan_item_image_find_size(PanWindow *pw, PanItem *pi, gint w, gint h)
1041 if (!pi->fd) return;
1043 work = pw->cache_list;
1052 path = ((FileData *)pc)->path;
1054 if (pc->cd && pc->cd->dimensions &&
1055 path && strcmp(path, pi->fd->path) == 0)
1057 pi->width = MAX(1, pc->cd->width * pw->image_size / 100);
1058 pi->height = MAX(1, pc->cd->height * pw->image_size / 100);
1060 pw->cache_list = g_list_remove(pw->cache_list, pc);
1061 cache_sim_data_free(pc->cd);
1062 file_data_free((FileData *)pc);
1068 static PanItem *pan_item_new_image(PanWindow *pw, FileData *fd, gint x, gint y, gint w, gint h)
1072 pi = g_new0(PanItem, 1);
1073 pi->type = ITEM_IMAGE;
1078 pan_item_image_find_size(pw, pi, w, h);
1080 pw->list = g_list_prepend(pw->list, pi);
1085 static PanItem *pan_item_find_by_key(PanWindow *pw, ItemType type, const gchar *key)
1089 if (!key) return NULL;
1091 work = g_list_last(pw->list);
1097 if ((pi->type == type || type == ITEM_NONE) &&
1098 pi->key && strcmp(pi->key, key) == 0)
1104 work = g_list_last(pw->list_static);
1110 if ((pi->type == type || type == ITEM_NONE) &&
1111 pi->key && strcmp(pi->key, key) == 0)
1121 /* when ignore_case and partial are TRUE, path should be converted to lower case */
1122 static GList *pan_item_find_by_path_l(GList *list, GList *search_list,
1123 ItemType type, const gchar *path,
1124 gint ignore_case, gint partial)
1128 work = g_list_last(search_list);
1134 if ((pi->type == type || type == ITEM_NONE) && pi->fd)
1140 if (pi->fd->path && strcmp(path, pi->fd->path) == 0) match = TRUE;
1142 else if (pi->fd->name)
1150 haystack = g_utf8_strdown(pi->fd->name, -1);
1151 match = (strstr(haystack, path) != NULL);
1156 if (strstr(pi->fd->name, path)) match = TRUE;
1159 else if (ignore_case)
1161 if (strcasecmp(path, pi->fd->name) == 0) match = TRUE;
1165 if (strcmp(path, pi->fd->name) == 0) match = TRUE;
1169 if (match) list = g_list_prepend(list, pi);
1177 /* when ignore_case and partial are TRUE, path should be converted to lower case */
1178 static GList *pan_item_find_by_path(PanWindow *pw, ItemType type, const gchar *path,
1179 gint ignore_case, gint partial)
1183 if (!path) return NULL;
1184 if (partial && path[0] == '/') return NULL;
1186 list = pan_item_find_by_path_l(list, pw->list_static, type, path, ignore_case, partial);
1187 list = pan_item_find_by_path_l(list, pw->list, type, path, ignore_case, partial);
1189 return g_list_reverse(list);
1192 static PanItem *pan_item_find_by_coord_l(GList *list, ItemType type, gint x, gint y, const gchar *key)
1202 if ((pi->type == type || type == ITEM_NONE) &&
1203 x >= pi->x && x < pi->x + pi->width &&
1204 y >= pi->y && y < pi->y + pi->height &&
1205 (!key || (pi->key && strcmp(pi->key, key) == 0)))
1215 static PanItem *pan_item_find_by_coord(PanWindow *pw, ItemType type, gint x, gint y, const gchar *key)
1219 pi = pan_item_find_by_coord_l(pw->list, type, x, y, key);
1222 return pan_item_find_by_coord_l(pw->list_static, type, x, y, key);
1226 *-----------------------------------------------------------------------------
1228 *-----------------------------------------------------------------------------
1231 static gint islink_loop(const gchar *s)
1237 sl = path_from_utf8(s);
1239 if (lstat(sl, &st) == 0 && S_ISLNK(st.st_mode))
1244 buf = g_malloc(st.st_size + 1);
1245 l = readlink(sl, buf, st.st_size);
1246 if (l == st.st_size)
1250 parse_out_relatives(buf);
1253 parse_out_relatives(sl);
1257 if (strncmp(sl, buf, l) == 0 &&
1258 (sl[l] == '\0' || sl[l] == '/' || l == 1)) ret = TRUE;
1264 link_path = concat_dir_and_file(sl, buf);
1265 parse_out_relatives(link_path);
1267 if (strncmp(sl, link_path, l) == 0 &&
1268 (sl[l] == '\0' || sl[l] == '/' || l == 1)) ret = TRUE;
1282 static gint is_ignored(const gchar *s, gint ignore_symlinks)
1287 if (!lstat_utf8(s, &st)) return TRUE;
1290 /* normal filesystems have directories with some size or block allocation,
1291 * special filesystems (like linux /proc) set both to zero.
1292 * enable this check if you enable listing the root "/" folder
1294 if (st.st_size == 0 && st.st_blocks == 0) return TRUE;
1297 if (S_ISLNK(st.st_mode) && (ignore_symlinks || islink_loop(s))) return TRUE;
1299 n = filename_from_path(s);
1300 if (n && strcmp(n, GQVIEW_RC_DIR) == 0) return TRUE;
1305 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend,
1306 gint ignore_symlinks)
1308 GList *flist = NULL;
1309 GList *dlist = NULL;
1313 filelist_read(path, &flist, &dlist);
1314 if (sort != SORT_NONE)
1316 flist = filelist_sort(flist, sort, ascend);
1317 dlist = filelist_sort(dlist, sort, ascend);
1327 folders = g_list_remove(folders, fd);
1329 if (!is_ignored(fd->path, ignore_symlinks) &&
1330 filelist_read(fd->path, &flist, &dlist))
1332 if (sort != SORT_NONE)
1334 flist = filelist_sort(flist, sort, ascend);
1335 dlist = filelist_sort(dlist, sort, ascend);
1338 result = g_list_concat(result, flist);
1339 folders = g_list_concat(dlist, folders);
1348 static void pan_window_layout_compute_grid(PanWindow *pw, const gchar *path, gint *width, gint *height)
1356 list = pan_window_layout_list(path, SORT_NAME, TRUE, pw->ignore_symlinks);
1358 grid_size = (gint)sqrt((double)g_list_length(list));
1359 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1361 grid_size = grid_size * (512 + PAN_THUMB_GAP) * pw->image_size / 100;
1365 grid_size = grid_size * (PAN_THUMB_SIZE + PAN_THUMB_GAP);
1370 *width = PAN_FOLDER_BOX_BORDER * 2;
1371 *height = PAN_FOLDER_BOX_BORDER * 2;
1384 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1386 pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1388 x += pi->width + PAN_THUMB_GAP;
1389 if (y + pi->height + PAN_THUMB_GAP > next_y) next_y = y + pi->height + PAN_THUMB_GAP;
1398 pi = pan_item_new_thumb(pw, fd, x, y);
1400 x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1404 y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1407 pan_item_size_coordinates(pi, PAN_THUMB_GAP, width, height);
1413 static void pan_window_Layout_compute_folders_flower_size(PanWindow *pw, gint *width, gint *height)
1416 gint x1, y1, x2, y2;
1431 if (x1 > pi->x) x1 = pi->x;
1432 if (y1 > pi->y) y1 = pi->y;
1433 if (x2 < pi->x + pi->width) x2 = pi->x + pi->width;
1434 if (y2 < pi->y + pi->height) y2 = pi->y + pi->height;
1437 x1 -= PAN_FOLDER_BOX_BORDER;
1438 y1 -= PAN_FOLDER_BOX_BORDER;
1439 x2 += PAN_FOLDER_BOX_BORDER;
1440 y2 += PAN_FOLDER_BOX_BORDER;
1453 if (pi->type == ITEM_TRIANGLE && pi->data)
1467 if (width) *width = x2 - x1;
1468 if (height) *height = y2 - y1;
1471 typedef struct _FlowerGroup FlowerGroup;
1472 struct _FlowerGroup {
1485 static void pan_window_layout_compute_folder_flower_move(FlowerGroup *group, gint x, gint y)
1489 work = group->items;
1507 static void pan_window_layout_compute_folder_flower_position(FlowerGroup *group, FlowerGroup *parent,
1508 gint *result_x, gint *result_y)
1514 radius = parent->circumference / (2*PI);
1515 radius = MAX(radius, parent->diameter / 2 + group->diameter / 2);
1517 a = 2*PI * group->diameter / parent->circumference;
1519 x = (gint)((double)radius * cos(parent->angle + a / 2));
1520 y = (gint)((double)radius * sin(parent->angle + a / 2));
1527 x += parent->width / 2;
1528 y += parent->height / 2;
1530 x -= group->width / 2;
1531 y -= group->height / 2;
1537 static void pan_window_layout_compute_folder_flower_build(PanWindow *pw, FlowerGroup *group, FlowerGroup *parent)
1544 if (parent && parent->children)
1546 pan_window_layout_compute_folder_flower_position(group, parent, &x, &y);
1554 pan_window_layout_compute_folder_flower_move(group, x, y);
1559 gint px, py, gx, gy;
1560 gint x1, y1, x2, y2;
1562 px = parent->x + parent->width / 2;
1563 py = parent->y + parent->height / 2;
1565 gx = group->x + group->width / 2;
1566 gy = group->y + group->height / 2;
1571 x2 = MAX(px, gx + 5);
1572 y2 = MAX(py, gy + 5);
1574 pi = pan_item_new_tri(pw, NULL, x1, y1, x2 - x1, y2 - y1,
1575 px, py, gx, gy, gx + 5, gy + 5,
1577 pan_item_tri_border(pi, BORDER_1 | BORDER_3,
1581 pw->list = g_list_concat(group->items, pw->list);
1582 group->items = NULL;
1584 group->circumference = 0;
1585 work = group->children;
1593 group->circumference += child->diameter;
1596 work = g_list_last(group->children);
1604 pan_window_layout_compute_folder_flower_build(pw, child, group);
1607 g_list_free(group->children);
1611 static FlowerGroup *pan_window_layout_compute_folders_flower_path(PanWindow *pw, const gchar *path,
1624 if (!filelist_read(path, &f, &d)) return NULL;
1625 if (!f && !d) return NULL;
1627 f = filelist_sort(f, SORT_NAME, TRUE);
1628 d = filelist_sort(d, SORT_NAME, TRUE);
1630 pi_box = pan_item_new_text(pw, x, y, path, TEXT_ATTR_NONE,
1631 PAN_TEXT_COLOR, 255);
1633 y += pi_box->height;
1635 pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1637 PAN_FOLDER_BOX_BORDER * 2, PAN_FOLDER_BOX_BORDER * 2,
1638 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1639 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1640 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1642 x += PAN_FOLDER_BOX_BORDER;
1643 y += PAN_FOLDER_BOX_BORDER;
1645 grid_size = (gint)(sqrt(g_list_length(f)) + 0.9);
1659 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1661 pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1662 x += pi->width + PAN_THUMB_GAP;
1663 if (pi->height > y_height) y_height = pi->height;
1667 pi = pan_item_new_thumb(pw, fd, x, y);
1668 x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1669 y_height = PAN_THUMB_SIZE;
1673 if (grid_count >= grid_size)
1677 y += y_height + PAN_THUMB_GAP;
1681 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1684 group = g_new0(FlowerGroup, 1);
1685 group->items = pw->list;
1688 group->width = pi_box->width;
1689 group->height = pi_box->y + pi_box->height;
1690 group->diameter = (int)sqrt(group->width * group->width + group->height * group->height);
1692 group->children = NULL;
1703 if (!is_ignored(fd->path, pw->ignore_symlinks))
1705 child = pan_window_layout_compute_folders_flower_path(pw, fd->path, 0, 0);
1706 if (child) group->children = g_list_prepend(group->children, child);
1710 if (!f && !group->children)
1712 work = group->items;
1723 g_list_free(group->items);
1734 static void pan_window_layout_compute_folders_flower(PanWindow *pw, const gchar *path,
1735 gint *width, gint *height,
1736 gint *scroll_x, gint *scroll_y)
1741 group = pan_window_layout_compute_folders_flower_path(pw, path, 0, 0);
1742 pan_window_layout_compute_folder_flower_build(pw, group, NULL);
1744 pan_window_Layout_compute_folders_flower_size(pw, width, height);
1746 list = pan_item_find_by_path(pw, ITEM_BOX, path, FALSE, FALSE);
1749 PanItem *pi = list->data;
1750 *scroll_x = pi->x + pi->width / 2;
1751 *scroll_y = pi->y + pi->height / 2;
1756 static void pan_window_layout_compute_folders_linear_path(PanWindow *pw, const gchar *path,
1757 gint *x, gint *y, gint *level,
1759 gint *width, gint *height)
1767 if (!filelist_read(path, &f, &d)) return;
1768 if (!f && !d) return;
1770 f = filelist_sort(f, SORT_NAME, TRUE);
1771 d = filelist_sort(d, SORT_NAME, TRUE);
1773 *x = PAN_FOLDER_BOX_BORDER + ((*level) * MAX(PAN_FOLDER_BOX_BORDER, PAN_THUMB_GAP));
1775 pi_box = pan_item_new_text(pw, *x, *y, path, TEXT_ATTR_NONE,
1776 PAN_TEXT_COLOR, 255);
1778 *y += pi_box->height;
1780 pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1782 PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER,
1783 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1784 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1785 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1787 *x += PAN_FOLDER_BOX_BORDER;
1788 *y += PAN_FOLDER_BOX_BORDER;
1799 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1801 pi = pan_item_new_image(pw, fd, *x, *y, 10, 10);
1802 *x += pi->width + PAN_THUMB_GAP;
1803 if (pi->height > y_height) y_height = pi->height;
1807 pi = pan_item_new_thumb(pw, fd, *x, *y);
1808 *x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1809 y_height = PAN_THUMB_SIZE;
1812 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1815 if (f) *y = pi_box->y + pi_box->height;
1827 if (!is_ignored(fd->path, pw->ignore_symlinks))
1829 *level = *level + 1;
1830 pan_window_layout_compute_folders_linear_path(pw, fd->path, x, y, level,
1831 pi_box, width, height);
1832 *level = *level - 1;
1838 pan_item_size_by_item(parent, pi_box, PAN_FOLDER_BOX_BORDER);
1840 if (*y < pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER)
1841 *y = pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER;
1843 pan_item_size_coordinates(pi_box, PAN_FOLDER_BOX_BORDER, width, height);
1846 static void pan_window_layout_compute_folders_linear(PanWindow *pw, const gchar *path, gint *width, gint *height)
1853 x = PAN_FOLDER_BOX_BORDER;
1854 y = PAN_FOLDER_BOX_BORDER;
1855 w = PAN_FOLDER_BOX_BORDER * 2;
1856 h = PAN_FOLDER_BOX_BORDER * 2;
1858 pan_window_layout_compute_folders_linear_path(pw, path, &x, &y, &level, NULL, &w, &h);
1860 if (width) *width = w;
1861 if (height) *height = h;
1865 *-----------------------------------------------------------------------------
1867 *-----------------------------------------------------------------------------
1870 static void pan_calendar_update(PanWindow *pw, PanItem *pi_day)
1876 gint x1, y1, x2, y2, x3, y3;
1881 while ((pi = pan_item_find_by_key(pw, ITEM_NONE, "day_bubble"))) pan_item_remove(pw, pi);
1883 if (!pi_day || pi_day->type != ITEM_BOX ||
1884 !pi_day->key || strcmp(pi_day->key, "day") != 0) return;
1886 list = pan_layout_intersect(pw, pi_day->x, pi_day->y, pi_day->width, pi_day->height);
1898 if (dot->type != ITEM_BOX || !dot->fd ||
1899 !dot->key || strcmp(dot->key, "dot") != 0)
1901 list = g_list_delete_link(list, node);
1909 grid = (gint)(sqrt(g_list_length(list)) + 0.5);
1911 x = pi_day->x + pi_day->width + 4;
1915 if (y + grid * (PAN_THUMB_SIZE + PAN_THUMB_GAP) + PAN_FOLDER_BOX_BORDER * 4 > pw->pr->image_height)
1917 y = pw->pr->image_height - (grid * (PAN_THUMB_SIZE + PAN_THUMB_GAP) + PAN_FOLDER_BOX_BORDER * 4);
1921 pbox = pan_item_new_box(pw, NULL, x, y, PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER,
1922 PAN_CAL_POPUP_BORDER,
1923 PAN_CAL_POPUP_COLOR, PAN_CAL_POPUP_ALPHA,
1924 PAN_CAL_POPUP_BORDER_COLOR, PAN_CAL_POPUP_ALPHA);
1925 pan_item_set_key(pbox, "day_bubble");
1932 buf = date_value_string(pi_day->fd->date, DATE_LENGTH_WEEK);
1933 plabel = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
1934 PAN_CAL_POPUP_TEXT_COLOR, 255);
1935 pan_item_set_key(plabel, "day_bubble");
1938 pan_item_size_by_item(pbox, plabel, 0);
1940 y += plabel->height;
1947 x += PAN_FOLDER_BOX_BORDER;
1948 y += PAN_FOLDER_BOX_BORDER;
1962 pimg = pan_item_new_thumb(pw, file_data_new_simple(dot->fd->path), x, y);
1963 pan_item_set_key(pimg, "day_bubble");
1965 pan_item_size_by_item(pbox, pimg, PAN_FOLDER_BOX_BORDER);
1970 x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1975 x = pbox->x + PAN_FOLDER_BOX_BORDER;
1976 y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1982 x1 = pi_day->x + pi_day->width - 8;
1985 y2 = pbox->y + MIN(42, pbox->height);
1987 y3 = MAX(pbox->y, y2 - 30);
1988 util_clip_triangle(x1, y1, x2, y2, x3, y3,
1991 pi = pan_item_new_tri(pw, NULL, x, y, w, h,
1992 x1, y1, x2, y2, x3, y3,
1993 PAN_CAL_POPUP_COLOR, PAN_CAL_POPUP_ALPHA);
1994 pan_item_tri_border(pi, BORDER_1 | BORDER_3, PAN_CAL_POPUP_BORDER_COLOR, PAN_CAL_POPUP_ALPHA);
1995 pan_item_set_key(pi, "day_bubble");
1996 pan_item_added(pw, pi);
1998 pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1999 pan_item_added(pw, pbox);
2002 static void pan_window_layout_compute_calendar(PanWindow *pw, const gchar *path, gint *width, gint *height)
2018 list = pan_window_layout_list(path, SORT_NONE, TRUE, pw->ignore_symlinks);
2020 if (pw->cache_list && pw->exif_date_enable)
2022 pw->cache_list = filelist_sort(pw->cache_list, SORT_NAME, TRUE);
2023 list = filelist_sort(list, SORT_NAME, TRUE);
2024 pan_cache_sync_date(pw, list);
2027 pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE);
2028 list = filelist_sort(list, SORT_TIME, TRUE);
2041 if (!date_compare(fd->date, tc, DATE_LENGTH_DAY))
2049 if (day_max < count) day_max = count;
2053 printf("biggest day contains %d images\n", day_max);
2055 grid = (gint)(sqrt((double)day_max) + 0.5) * (PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2 + PAN_THUMB_GAP);
2056 day_width = MAX(PAN_CAL_DAY_WIDTH, grid);
2057 day_height = MAX(PAN_CAL_DAY_HEIGHT, grid);
2061 FileData *fd = list->data;
2063 year = date_value(fd->date, DATE_LENGTH_YEAR);
2064 month = date_value(fd->date, DATE_LENGTH_MONTH);
2067 work = g_list_last(list);
2070 FileData *fd = work->data;
2071 end_year = date_value(fd->date, DATE_LENGTH_YEAR);
2072 end_month = date_value(fd->date, DATE_LENGTH_MONTH);
2075 *width = PAN_FOLDER_BOX_BORDER * 2;
2076 *height = PAN_FOLDER_BOX_BORDER * 2;
2078 x = PAN_FOLDER_BOX_BORDER;
2079 y = PAN_FOLDER_BOX_BORDER;
2082 while (work && (year < end_year || (year == end_year && month <= end_month)))
2093 dt = date_to_time((month == 12) ? year + 1 : year, (month == 12) ? 1 : month + 1, 1);
2095 days = date_value(dt, DATE_LENGTH_DAY);
2096 dt = date_to_time(year, month, 1);
2097 col = date_value(dt, DATE_LENGTH_WEEK);
2100 x = PAN_FOLDER_BOX_BORDER;
2102 pi_month = pan_item_new_box(pw, NULL, x, y, PAN_CAL_DAY_WIDTH * 7, PAN_CAL_DAY_HEIGHT / 4,
2103 PAN_CAL_MONTH_BORDER,
2104 PAN_CAL_MONTH_COLOR, PAN_CAL_MONTH_ALPHA,
2105 PAN_CAL_MONTH_BORDER_COLOR, PAN_CAL_MONTH_ALPHA);
2106 buf = date_value_string(dt, DATE_LENGTH_MONTH);
2107 pi_text = pan_item_new_text(pw, x, y, buf,
2108 TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
2109 PAN_CAL_MONTH_TEXT_COLOR, 255);
2111 pi_text->x = pi_month->x + (pi_month->width - pi_text->width) / 2;
2113 pi_month->height = pi_text->y + pi_text->height - pi_month->y;
2115 x = PAN_FOLDER_BOX_BORDER + col * PAN_CAL_DAY_WIDTH;
2116 y = pi_month->y + pi_month->height + PAN_FOLDER_BOX_BORDER;
2118 for (day = 1; day <= days; day++)
2125 dt = date_to_time(year, month, day);
2127 fd = g_new0(FileData, 1);
2128 /* path and name must be non NULL, so make them an invalid filename */
2129 fd->path = g_strdup("//");
2132 pi_day = pan_item_new_box(pw, fd, x, y, PAN_CAL_DAY_WIDTH, PAN_CAL_DAY_HEIGHT,
2134 PAN_CAL_DAY_COLOR, PAN_CAL_DAY_ALPHA,
2135 PAN_CAL_DAY_BORDER_COLOR, PAN_CAL_DAY_ALPHA);
2136 pan_item_set_key(pi_day, "day");
2138 dx = x + PAN_CAL_DOT_GAP * 2;
2139 dy = y + PAN_CAL_DOT_GAP * 2;
2141 fd = (work) ? work->data : NULL;
2142 while (fd && date_compare(fd->date, dt, DATE_LENGTH_DAY))
2146 pi = pan_item_new_box(pw, fd, dx, dy, PAN_CAL_DOT_SIZE, PAN_CAL_DOT_SIZE,
2148 PAN_CAL_DOT_COLOR, PAN_CAL_DOT_ALPHA,
2150 pan_item_set_key(pi, "dot");
2152 dx += PAN_CAL_DOT_SIZE + PAN_CAL_DOT_GAP;
2153 if (dx + PAN_CAL_DOT_SIZE > pi_day->x + pi_day->width - PAN_CAL_DOT_GAP * 2)
2155 dx = x + PAN_CAL_DOT_GAP * 2;
2156 dy += PAN_CAL_DOT_SIZE + PAN_CAL_DOT_GAP;
2158 if (dy + PAN_CAL_DOT_SIZE > pi_day->y + pi_day->height - PAN_CAL_DOT_GAP * 2)
2160 /* must keep all dots within respective day even if it gets ugly */
2161 dy = y + PAN_CAL_DOT_GAP * 2;
2167 fd = (work) ? work->data : NULL;
2174 pi_day->color_r = MAX(pi_day->color_r - 61 - n * 3, 80);
2175 pi_day->color_g = pi_day->color_r;
2177 buf = g_strdup_printf("( %d )", n);
2178 pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE,
2179 PAN_CAL_DAY_TEXT_COLOR, 255);
2182 pi->x = pi_day->x + (pi_day->width - pi->width) / 2;
2183 pi->y = pi_day->y + (pi_day->height - pi->height) / 2;
2186 buf = g_strdup_printf("%d", day);
2187 pan_item_new_text(pw, x + 4, y + 4, buf, TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
2188 PAN_CAL_DAY_TEXT_COLOR, 255);
2192 pan_item_size_coordinates(pi_day, PAN_FOLDER_BOX_BORDER, width, height);
2199 x = PAN_FOLDER_BOX_BORDER;
2200 y += PAN_CAL_DAY_HEIGHT;
2204 x += PAN_CAL_DAY_WIDTH;
2208 if (col > 0) y += PAN_CAL_DAY_HEIGHT;
2209 y += PAN_FOLDER_BOX_BORDER * 2;
2220 *height = MAX(*height, grid + PAN_FOLDER_BOX_BORDER * 2 * 2);
2225 static void pan_window_layout_compute_timeline(PanWindow *pw, const gchar *path, gint *width, gint *height)
2233 PanItem *pi_month = NULL;
2234 PanItem *pi_day = NULL;
2240 list = pan_window_layout_list(path, SORT_NONE, TRUE, pw->ignore_symlinks);
2242 if (pw->cache_list && pw->exif_date_enable)
2244 pw->cache_list = filelist_sort(pw->cache_list, SORT_NAME, TRUE);
2245 list = filelist_sort(list, SORT_NAME, TRUE);
2246 pan_cache_sync_date(pw, list);
2249 pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE);
2250 list = filelist_sort(list, SORT_TIME, TRUE);
2252 *width = PAN_FOLDER_BOX_BORDER * 2;
2253 *height = PAN_FOLDER_BOX_BORDER * 2;
2258 day_start = month_start;
2273 if (!date_compare(fd->date, tc, DATE_LENGTH_DAY))
2278 if (!date_compare(fd->date, tc, DATE_LENGTH_MONTH))
2284 x = pi_month->x + pi_month->width + PAN_FOLDER_BOX_BORDER;
2288 x = PAN_FOLDER_BOX_BORDER;
2291 y = PAN_FOLDER_BOX_BORDER;
2293 buf = date_value_string(fd->date, DATE_LENGTH_MONTH);
2294 pi = pan_item_new_text(pw, x, y, buf,
2295 TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
2296 PAN_TEXT_COLOR, 255);
2300 pi_month = pan_item_new_box(pw, file_data_new_simple(fd->path),
2302 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
2303 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
2304 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
2306 x += PAN_FOLDER_BOX_BORDER;
2307 y += PAN_FOLDER_BOX_BORDER;
2311 if (pi_day) x = pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER;
2323 if (date_compare(nfd->date, tc, DATE_LENGTH_DAY))
2325 needle = needle->next;
2334 buf = date_value_string(fd->date, DATE_LENGTH_WEEK);
2335 pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE,
2336 PAN_TEXT_COLOR, 255);
2341 pi_day = pan_item_new_box(pw, file_data_new_simple(fd->path), x, y, 0, 0,
2342 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
2343 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
2344 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
2346 x += PAN_FOLDER_BOX_BORDER;
2347 y += PAN_FOLDER_BOX_BORDER;
2351 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
2353 pi = pan_item_new_image(pw, fd, x, y, 10, 10);
2354 if (pi->width > x_width) x_width = pi->width;
2355 y_height = pi->height;
2359 pi = pan_item_new_thumb(pw, fd, x, y);
2360 x_width = PAN_THUMB_SIZE;
2361 y_height = PAN_THUMB_SIZE;
2364 pan_item_size_by_item(pi_day, pi, PAN_FOLDER_BOX_BORDER);
2365 pan_item_size_by_item(pi_month, pi_day, PAN_FOLDER_BOX_BORDER);
2370 if (total > 0 && count < PAN_GROUP_MAX)
2372 y += y_height + PAN_THUMB_GAP;
2376 x += x_width + PAN_THUMB_GAP;
2386 pan_item_size_coordinates(pi_month, PAN_FOLDER_BOX_BORDER, width, height);
2392 static void pan_window_layout_compute(PanWindow *pw, const gchar *path,
2393 gint *width, gint *height,
2394 gint *scroll_x, gint *scroll_y)
2396 pan_window_items_free(pw);
2400 case LAYOUT_SIZE_THUMB_DOTS:
2401 pw->thumb_size = PAN_THUMB_SIZE_DOTS;
2402 pw->thumb_gap = PAN_THUMB_GAP_DOTS;
2404 case LAYOUT_SIZE_THUMB_NONE:
2405 pw->thumb_size = PAN_THUMB_SIZE_NONE;
2406 pw->thumb_gap = PAN_THUMB_GAP_SMALL;
2408 case LAYOUT_SIZE_THUMB_SMALL:
2409 pw->thumb_size = PAN_THUMB_SIZE_SMALL;
2410 pw->thumb_gap = PAN_THUMB_GAP_SMALL;
2412 case LAYOUT_SIZE_THUMB_NORMAL:
2414 pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
2415 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2417 case LAYOUT_SIZE_THUMB_LARGE:
2418 pw->thumb_size = PAN_THUMB_SIZE_LARGE;
2419 pw->thumb_gap = PAN_THUMB_GAP_LARGE;
2421 case LAYOUT_SIZE_10:
2422 pw->image_size = 10;
2423 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2425 case LAYOUT_SIZE_25:
2426 pw->image_size = 25;
2427 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2429 case LAYOUT_SIZE_33:
2430 pw->image_size = 33;
2431 pw->thumb_gap = PAN_THUMB_GAP_LARGE;
2433 case LAYOUT_SIZE_50:
2434 pw->image_size = 50;
2435 pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2437 case LAYOUT_SIZE_100:
2438 pw->image_size = 100;
2439 pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2452 pan_window_layout_compute_grid(pw, path, width, height);
2454 case LAYOUT_FOLDERS_LINEAR:
2455 pan_window_layout_compute_folders_linear(pw, path, width, height);
2457 case LAYOUT_FOLDERS_FLOWER:
2458 pan_window_layout_compute_folders_flower(pw, path, width, height, scroll_x, scroll_y);
2460 case LAYOUT_CALENDAR:
2461 pan_window_layout_compute_calendar(pw, path, width, height);
2463 case LAYOUT_TIMELINE:
2464 pan_window_layout_compute_timeline(pw, path, width, height);
2470 printf("computed %d objects\n", g_list_length(pw->list));
2473 static GList *pan_layout_intersect_l(GList *list, GList *item_list,
2474 gint x, gint y, gint width, gint height)
2482 gint rx, ry, rw, rh;
2487 if (util_clip_region(x, y, width, height,
2488 pi->x, pi->y, pi->width, pi->height,
2489 &rx, &ry, &rw, &rh))
2491 list = g_list_prepend(list, pi);
2498 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height)
2504 grid = pw->list_grid;
2510 if (x < pg->x || x + width > pg->x + pg->w ||
2511 y < pg->y || y + height > pg->y + pg->h)
2517 list = pan_layout_intersect_l(list, pw->list, x, y, width, height);
2521 list = pan_layout_intersect_l(list, pg->list, x, y, width, height);
2525 list = pan_layout_intersect_l(list, pw->list_static, x, y, width, height);
2532 *-----------------------------------------------------------------------------
2534 *-----------------------------------------------------------------------------
2537 static gint pan_layout_queue_step(PanWindow *pw);
2540 static void pan_layout_queue_thumb_done_cb(ThumbLoader *tl, gpointer data)
2542 PanWindow *pw = data;
2550 pw->queue_pi = NULL;
2554 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2555 pi->pixbuf = thumb_loader_get_pixbuf(tl, TRUE);
2558 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2562 thumb_loader_free(pw->tl);
2565 while (pan_layout_queue_step(pw));
2568 static void pan_layout_queue_image_done_cb(ImageLoader *il, gpointer data)
2570 PanWindow *pw = data;
2578 pw->queue_pi = NULL;
2582 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2583 pi->pixbuf = image_loader_get_pixbuf(pw->il);
2584 if (pi->pixbuf) g_object_ref(pi->pixbuf);
2586 if (pi->pixbuf && pw->size != LAYOUT_SIZE_100 &&
2587 (gdk_pixbuf_get_width(pi->pixbuf) > pi->width ||
2588 gdk_pixbuf_get_height(pi->pixbuf) > pi->height))
2593 pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height,
2594 (GdkInterpType)zoom_quality);
2595 g_object_unref(tmp);
2599 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2603 image_loader_free(pw->il);
2606 while (pan_layout_queue_step(pw));
2610 static void pan_layout_queue_image_area_cb(ImageLoader *il, guint x, guint y,
2611 guint width, guint height, gpointer data)
2613 PanWindow *pw = data;
2624 pi->pixbuf = image_loader_get_pixbuf(pw->il);
2625 if (pi->pixbuf) g_object_ref(pi->pixbuf);
2629 image_area_changed(pw->imd, pi->x + x, pi->y + y, width, height);
2635 static gint pan_layout_queue_step(PanWindow *pw)
2639 if (!pw->queue) return FALSE;
2641 pi = pw->queue->data;
2642 pw->queue = g_list_remove(pw->queue, pi);
2645 if (!pw->queue_pi->fd)
2647 pw->queue_pi->queued = FALSE;
2648 pw->queue_pi = NULL;
2652 image_loader_free(pw->il);
2654 thumb_loader_free(pw->tl);
2657 if (pi->type == ITEM_IMAGE)
2659 pw->il = image_loader_new(pi->fd->path);
2661 if (pw->size != LAYOUT_SIZE_100)
2663 image_loader_set_requested_size(pw->il, pi->width, pi->height);
2667 image_loader_set_area_ready_func(pw->il, pan_layout_queue_image_area_cb, pw);
2669 image_loader_set_error_func(pw->il, pan_layout_queue_image_done_cb, pw);
2671 if (image_loader_start(pw->il, pan_layout_queue_image_done_cb, pw)) return FALSE;
2673 image_loader_free(pw->il);
2676 else if (pi->type == ITEM_THUMB)
2678 pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE);
2680 if (!pw->tl->standard_loader)
2682 /* The classic loader will recreate a thumbnail any time we
2683 * request a different size than what exists. This view will
2684 * almost never use the user configured sizes so disable cache.
2686 thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE);
2689 thumb_loader_set_callbacks(pw->tl,
2690 pan_layout_queue_thumb_done_cb,
2691 pan_layout_queue_thumb_done_cb,
2694 if (thumb_loader_start(pw->tl, pi->fd->path)) return FALSE;
2696 thumb_loader_free(pw->tl);
2700 pw->queue_pi->queued = FALSE;
2701 pw->queue_pi = NULL;
2705 static void pan_layout_queue(PanWindow *pw, PanItem *pi)
2707 if (!pi || pi->queued || pi->pixbuf) return;
2708 if (pw->size <= LAYOUT_SIZE_THUMB_NONE) return;
2711 pw->queue = g_list_prepend(pw->queue, pi);
2713 if (!pw->tl && !pw->il) while(pan_layout_queue_step(pw));
2716 static gint pan_window_request_tile_cb(PixbufRenderer *pr, gint x, gint y,
2717 gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
2719 PanWindow *pw = data;
2724 pixbuf_set_rect_fill(pixbuf,
2725 0, 0, width, height,
2726 PAN_BACKGROUND_COLOR, 255);
2728 for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE)
2730 gint rx, ry, rw, rh;
2732 if (util_clip_region(x, y, width, height,
2734 &rx, &ry, &rw, &rh))
2736 pixbuf_draw_rect_fill(pixbuf,
2737 rx - x, ry - y, rw, rh,
2738 PAN_GRID_COLOR, PAN_GRID_ALPHA);
2741 for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE)
2743 gint rx, ry, rw, rh;
2745 if (util_clip_region(x, y, width, height,
2747 &rx, &ry, &rw, &rh))
2749 pixbuf_draw_rect_fill(pixbuf,
2750 rx - x, ry - y, rw, rh,
2751 PAN_GRID_COLOR, PAN_GRID_ALPHA);
2755 list = pan_layout_intersect(pw, x, y, width, height);
2760 gint tx, ty, tw, th;
2761 gint rx, ry, rw, rh;
2768 if (pi->type == ITEM_THUMB && pi->pixbuf)
2770 tw = gdk_pixbuf_get_width(pi->pixbuf);
2771 th = gdk_pixbuf_get_height(pi->pixbuf);
2773 tx = pi->x + (pi->width - tw) / 2;
2774 ty = pi->y + (pi->height - th) / 2;
2776 if (gdk_pixbuf_get_has_alpha(pi->pixbuf))
2778 if (util_clip_region(x, y, width, height,
2779 tx + PAN_SHADOW_OFFSET, ty + PAN_SHADOW_OFFSET, tw, th,
2780 &rx, &ry, &rw, &rh))
2782 pixbuf_draw_shadow(pixbuf,
2783 rx - x, ry - y, rw, rh,
2784 tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2786 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2791 if (util_clip_region(x, y, width, height,
2792 tx + tw, ty + PAN_SHADOW_OFFSET,
2793 PAN_SHADOW_OFFSET, th - PAN_SHADOW_OFFSET,
2794 &rx, &ry, &rw, &rh))
2796 pixbuf_draw_shadow(pixbuf,
2797 rx - x, ry - y, rw, rh,
2798 tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2800 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2802 if (util_clip_region(x, y, width, height,
2803 tx + PAN_SHADOW_OFFSET, ty + th, tw, PAN_SHADOW_OFFSET,
2804 &rx, &ry, &rw, &rh))
2806 pixbuf_draw_shadow(pixbuf,
2807 rx - x, ry - y, rw, rh,
2808 tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2810 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2814 if (util_clip_region(x, y, width, height,
2816 &rx, &ry, &rw, &rh))
2818 gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2821 1.0, 1.0, GDK_INTERP_NEAREST,
2825 if (util_clip_region(x, y, width, height,
2826 tx, ty, tw, PAN_OUTLINE_THICKNESS,
2827 &rx, &ry, &rw, &rh))
2829 pixbuf_draw_rect_fill(pixbuf,
2830 rx - x, ry - y, rw, rh,
2831 PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2833 if (util_clip_region(x, y, width, height,
2834 tx, ty, PAN_OUTLINE_THICKNESS, th,
2835 &rx, &ry, &rw, &rh))
2837 pixbuf_draw_rect_fill(pixbuf,
2838 rx - x, ry - y, rw, rh,
2839 PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2841 if (util_clip_region(x, y, width, height,
2842 tx + tw - PAN_OUTLINE_THICKNESS, ty + PAN_OUTLINE_THICKNESS,
2843 PAN_OUTLINE_THICKNESS, th - PAN_OUTLINE_THICKNESS,
2844 &rx, &ry, &rw, &rh))
2846 pixbuf_draw_rect_fill(pixbuf,
2847 rx - x, ry - y, rw, rh,
2848 PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2850 if (util_clip_region(x, y, width, height,
2851 tx + PAN_OUTLINE_THICKNESS, ty + th - PAN_OUTLINE_THICKNESS,
2852 tw - PAN_OUTLINE_THICKNESS * 2, PAN_OUTLINE_THICKNESS,
2853 &rx, &ry, &rw, &rh))
2855 pixbuf_draw_rect_fill(pixbuf,
2856 rx - x, ry - y, rw, rh,
2857 PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2860 else if (pi->type == ITEM_THUMB)
2862 tw = pi->width - PAN_SHADOW_OFFSET * 2;
2863 th = pi->height - PAN_SHADOW_OFFSET * 2;
2864 tx = pi->x + PAN_SHADOW_OFFSET;
2865 ty = pi->y + PAN_SHADOW_OFFSET;
2867 if (util_clip_region(x, y, width, height,
2869 &rx, &ry, &rw, &rh))
2873 d = (pw->size <= LAYOUT_SIZE_THUMB_NONE) ? 2 : 8;
2874 pixbuf_draw_rect_fill(pixbuf,
2875 rx - x, ry - y, rw, rh,
2877 PAN_SHADOW_ALPHA / d);
2880 pan_layout_queue(pw, pi);
2882 else if (pi->type == ITEM_IMAGE)
2884 if (util_clip_region(x, y, width, height,
2885 pi->x, pi->y, pi->width, pi->height,
2886 &rx, &ry, &rw, &rh))
2890 gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2893 1.0, 1.0, GDK_INTERP_NEAREST,
2898 pixbuf_draw_rect_fill(pixbuf,
2899 rx - x, ry - y, rw, rh,
2900 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA / 2);
2901 pan_layout_queue(pw, pi);
2905 else if (pi->type == ITEM_BOX)
2919 if (pi->color_a > 254)
2921 pixbuf_draw_shadow(pixbuf, pi->x - x + bw, pi->y - y + shadow[0],
2922 shadow[0], bh - shadow[0],
2923 pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2925 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2926 pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + bh,
2928 pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2930 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2935 a = pi->color_a * PAN_SHADOW_ALPHA >> 8;
2936 pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + shadow[0],
2938 pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2940 PAN_SHADOW_COLOR, a);
2944 if (util_clip_region(x, y, width, height,
2945 pi->x, pi->y, bw, bh,
2946 &rx, &ry, &rw, &rh))
2948 pixbuf_draw_rect_fill(pixbuf,
2949 rx - x, ry - y, rw, rh,
2950 pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2952 if (util_clip_region(x, y, width, height,
2953 pi->x, pi->y, bw, pi->border,
2954 &rx, &ry, &rw, &rh))
2956 pixbuf_draw_rect_fill(pixbuf,
2957 rx - x, ry - y, rw, rh,
2958 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2960 if (util_clip_region(x, y, width, height,
2961 pi->x, pi->y + pi->border, pi->border, bh - pi->border * 2,
2962 &rx, &ry, &rw, &rh))
2964 pixbuf_draw_rect_fill(pixbuf,
2965 rx - x, ry - y, rw, rh,
2966 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2968 if (util_clip_region(x, y, width, height,
2969 pi->x + bw - pi->border, pi->y + pi->border,
2970 pi->border, bh - pi->border * 2,
2971 &rx, &ry, &rw, &rh))
2973 pixbuf_draw_rect_fill(pixbuf,
2974 rx - x, ry - y, rw, rh,
2975 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2977 if (util_clip_region(x, y, width, height,
2978 pi->x, pi->y + bh - pi->border,
2980 &rx, &ry, &rw, &rh))
2982 pixbuf_draw_rect_fill(pixbuf,
2983 rx - x, ry - y, rw, rh,
2984 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2987 else if (pi->type == ITEM_TRIANGLE)
2989 if (util_clip_region(x, y, width, height,
2990 pi->x, pi->y, pi->width, pi->height,
2991 &rx, &ry, &rw, &rh) && pi->data)
2993 gint *coord = pi->data;
2994 pixbuf_draw_triangle(pixbuf,
2995 rx - x, ry - y, rw, rh,
2996 coord[0] - x, coord[1] - y,
2997 coord[2] - x, coord[3] - y,
2998 coord[4] - x, coord[5] - y,
2999 pi->color_r, pi->color_g, pi->color_b, pi->color_a);
3001 if (pi->border & BORDER_1)
3003 pixbuf_draw_line(pixbuf,
3004 rx - x, ry - y, rw, rh,
3005 coord[0] - x, coord[1] - y,
3006 coord[2] - x, coord[3] - y,
3007 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
3009 if (pi->border & BORDER_2)
3011 pixbuf_draw_line(pixbuf,
3012 rx - x, ry - y, rw, rh,
3013 coord[2] - x, coord[3] - y,
3014 coord[4] - x, coord[5] - y,
3015 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
3017 if (pi->border & BORDER_3)
3019 pixbuf_draw_line(pixbuf,
3020 rx - x, ry - y, rw, rh,
3021 coord[4] - x, coord[5] - y,
3022 coord[0] - x, coord[1] - y,
3023 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
3027 else if (pi->type == ITEM_TEXT && pi->text)
3029 PangoLayout *layout;
3031 layout = pan_item_text_layout(pi, (GtkWidget *)pr);
3032 pixbuf_draw_layout(pixbuf, layout, (GtkWidget *)pr,
3033 pi->x - x + PAN_TEXT_BORDER_SIZE, pi->y - y + PAN_TEXT_BORDER_SIZE,
3034 pi->color_r, pi->color_g, pi->color_b, pi->color_a);
3035 g_object_unref(G_OBJECT(layout));
3041 if (x%512 == 0 && y%512 == 0)
3043 PangoLayout *layout;
3046 layout = gtk_widget_create_pango_layout((GtkWidget *)pr, NULL);
3048 buf = g_strdup_printf("%d,%d\n(#%d)", x, y,
3049 (x / pr->source_tile_width) +
3050 (y / pr->source_tile_height * (pr->image_width/pr->source_tile_width + 1)));
3051 pango_layout_set_text(layout, buf, -1);
3054 pixbuf_draw_layout(pixbuf, layout, (GtkWidget *)pr, 0, 0, 0, 0, 0, 255);
3056 g_object_unref(G_OBJECT(layout));
3063 static void pan_window_dispose_tile_cb(PixbufRenderer *pr, gint x, gint y,
3064 gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
3066 PanWindow *pw = data;
3070 list = pan_layout_intersect(pw, x, y, width, height);
3079 if (pi->refcount > 0)
3083 if ((pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE) &&
3088 pw->queue = g_list_remove(pw->queue, pi);
3091 if (pw->queue_pi == pi) pw->queue_pi = NULL;
3094 g_object_unref(pi->pixbuf);
3106 *-----------------------------------------------------------------------------
3108 *-----------------------------------------------------------------------------
3111 static void pan_window_message(PanWindow *pw, const gchar *text)
3121 gtk_label_set_text(GTK_LABEL(pw->label_message), text);
3125 work = pw->list_static;
3126 if (pw->layout == LAYOUT_CALENDAR)
3136 pi->type == ITEM_BOX &&
3137 pi->key && strcmp(pi->key, "dot") == 0)
3139 size += pi->fd->size;
3154 (pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE))
3156 size += pi->fd->size;
3162 ss = text_from_size_abrev(size);
3163 buf = g_strdup_printf(_("%d images, %s"), count, ss);
3165 gtk_label_set_text(GTK_LABEL(pw->label_message), buf);
3169 static void pan_warning_folder(const gchar *path, GtkWidget *parent)
3173 message = g_strdup_printf(_("The pan view does not support the folder \"%s\"."), path);
3174 warning_dialog(_("Folder not supported"), message,
3175 GTK_STOCK_DIALOG_INFO, parent);
3179 static void pan_window_zoom_limit(PanWindow *pw)
3185 case LAYOUT_SIZE_THUMB_DOTS:
3186 case LAYOUT_SIZE_THUMB_NONE:
3187 case LAYOUT_SIZE_THUMB_SMALL:
3188 case LAYOUT_SIZE_THUMB_NORMAL:
3190 /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */
3194 case LAYOUT_SIZE_THUMB_LARGE:
3197 case LAYOUT_SIZE_10:
3198 case LAYOUT_SIZE_25:
3201 case LAYOUT_SIZE_33:
3202 case LAYOUT_SIZE_50:
3203 case LAYOUT_SIZE_100:
3209 image_zoom_set_limits(pw->imd, min, 32.0);
3212 static gint pan_window_layout_update_idle_cb(gpointer data)
3214 PanWindow *pw = data;
3220 if (pw->size > LAYOUT_SIZE_THUMB_LARGE ||
3221 (pw->exif_date_enable && (pw->layout == LAYOUT_TIMELINE || pw->layout == LAYOUT_CALENDAR)))
3223 if (!pw->cache_list && !pw->cache_todo)
3225 pan_cache_fill(pw, pw->path);
3228 pan_window_message(pw, _("Reading image data..."));
3236 if (pw->cache_count == pw->cache_total)
3238 pan_window_message(pw, _("Sorting..."));
3240 else if (pw->cache_tick > 9)
3244 buf = g_strdup_printf("%s %d", _("Reading image data..."),
3245 pw->cache_total - pw->cache_count);
3246 pan_window_message(pw, buf);
3252 if (pan_cache_step(pw)) return TRUE;
3259 pan_window_layout_compute(pw, pw->path, &width, &height, &scroll_x, &scroll_y);
3261 pan_window_zoom_limit(pw);
3263 if (width > 0 && height > 0)
3267 printf("Canvas size is %d x %d\n", width, height);
3269 pan_grid_build(pw, width, height, 1000);
3271 pixbuf_renderer_set_tiles(PIXBUF_RENDERER(pw->imd->pr), width, height,
3272 PAN_TILE_SIZE, PAN_TILE_SIZE, 10,
3273 pan_window_request_tile_cb,
3274 pan_window_dispose_tile_cb, pw, 1.0);
3276 if (scroll_x == 0 && scroll_y == 0)
3284 pixbuf_renderer_scroll_to_point(PIXBUF_RENDERER(pw->imd->pr), scroll_x, scroll_y, align, align);
3287 pan_window_message(pw, NULL);
3293 static void pan_window_layout_update_idle(PanWindow *pw)
3295 if (pw->idle_id == -1)
3297 pw->idle_id = g_idle_add(pan_window_layout_update_idle_cb, pw);
3301 static void pan_window_layout_update(PanWindow *pw)
3303 pan_window_message(pw, _("Sorting images..."));
3304 pan_window_layout_update_idle(pw);
3307 static void pan_window_layout_set_path(PanWindow *pw, const gchar *path)
3311 if (strcmp(path, "/") == 0)
3313 pan_warning_folder(path, pw->window);
3318 pw->path = g_strdup(path);
3320 pan_window_layout_update(pw);
3324 *-----------------------------------------------------------------------------
3325 * pan window keyboard
3326 *-----------------------------------------------------------------------------
3329 static const gchar *pan_menu_click_path(PanWindow *pw)
3331 if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd->path;
3335 static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
3337 PanWindow *pw = data;
3339 gdk_window_get_origin(pw->imd->pr->window, x, y);
3340 popup_menu_position_clamp(menu, x, y, 0);
3343 static gint pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
3345 PanWindow *pw = data;
3348 gint stop_signal = FALSE;
3354 pr = PIXBUF_RENDERER(pw->imd->pr);
3355 path = pan_menu_click_path(pw);
3357 focused = (pw->fs || GTK_WIDGET_HAS_FOCUS(GTK_WIDGET(pw->imd->widget)));
3361 switch (event->keyval)
3363 case GDK_Left: case GDK_KP_Left:
3367 case GDK_Right: case GDK_KP_Right:
3371 case GDK_Up: case GDK_KP_Up:
3375 case GDK_Down: case GDK_KP_Down:
3379 case GDK_Page_Up: case GDK_KP_Page_Up:
3380 pixbuf_renderer_scroll(pr, 0, 0 - pr->vis_height / 2);
3382 case GDK_Page_Down: case GDK_KP_Page_Down:
3383 pixbuf_renderer_scroll(pr, 0, pr->vis_height / 2);
3385 case GDK_Home: case GDK_KP_Home:
3386 pixbuf_renderer_scroll(pr, 0 - pr->vis_width / 2, 0);
3388 case GDK_End: case GDK_KP_End:
3389 pixbuf_renderer_scroll(pr, pr->vis_width / 2, 0);
3394 if (focused && !(event->state & GDK_CONTROL_MASK) )
3395 switch (event->keyval)
3397 case '+': case '=': case GDK_KP_Add:
3398 pixbuf_renderer_zoom_adjust(pr, ZOOM_INCREMENT);
3400 case '-': case GDK_KP_Subtract:
3401 pixbuf_renderer_zoom_adjust(pr, -ZOOM_INCREMENT);
3403 case 'Z': case 'z': case GDK_KP_Divide: case '1':
3404 pixbuf_renderer_zoom_set(pr, 1.0);
3407 pixbuf_renderer_zoom_set(pr, 2.0);
3410 pixbuf_renderer_zoom_set(pr, 3.0);
3413 pixbuf_renderer_zoom_set(pr, 4.0);
3416 pixbuf_renderer_zoom_set(pr, -4.0);
3419 pixbuf_renderer_zoom_set(pr, -3.0);
3422 pixbuf_renderer_zoom_set(pr, -2.0);
3426 pan_fullscreen_toggle(pw, FALSE);
3431 pan_overlay_toggle(pw);
3434 case GDK_Delete: case GDK_KP_Delete:
3439 if (GTK_WIDGET_VISIBLE(pw->search_box))
3441 gtk_widget_grab_focus(pw->search_entry);
3445 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
3453 pan_fullscreen_toggle(pw, TRUE);
3456 else if (GTK_WIDGET_VISIBLE(pw->search_entry))
3458 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3464 menu = pan_popup_menu(pw);
3465 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
3470 if (event->state & GDK_CONTROL_MASK)
3473 switch (event->keyval)
3506 if (path) file_util_copy(path, NULL, NULL, GTK_WIDGET(pr));
3509 if (path) file_util_move(path, NULL, NULL, GTK_WIDGET(pr));
3512 if (path) file_util_rename(path, NULL, GTK_WIDGET(pr));
3515 if (path) file_util_delete(path, NULL, GTK_WIDGET(pr));
3518 if (path) info_window_new(path, NULL);
3521 pan_window_close(pw);
3524 if (n != -1 && path)
3526 pan_fullscreen_toggle(pw, TRUE);
3527 start_editor_from_file(n, path);
3531 else if (event->state & GDK_SHIFT_MASK)
3538 switch (event->keyval)
3543 pan_fullscreen_toggle(pw, TRUE);
3546 else if (GTK_WIDGET_HAS_FOCUS(pw->search_entry))
3548 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
3549 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3558 if (x != 0 || y!= 0)
3560 keyboard_scroll_calc(&x, &y, event);
3561 pixbuf_renderer_scroll(pr, x, y);
3568 *-----------------------------------------------------------------------------
3570 *-----------------------------------------------------------------------------
3573 static void pan_info_update(PanWindow *pw, PanItem *pi)
3579 gint x1, y1, x2, y2, x3, y3;
3582 if (pw->click_pi == pi) return;
3583 if (pi && !pi->fd) pi = NULL;
3585 while ((p = pan_item_find_by_key(pw, ITEM_NONE, "info"))) pan_item_remove(pw, p);
3590 if (debug) printf("info set to %s\n", pi->fd->path);
3592 pbox = pan_item_new_box(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
3594 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
3595 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3596 pan_item_set_key(pbox, "info");
3598 if (pi->type == ITEM_THUMB && pi->pixbuf)
3600 w = gdk_pixbuf_get_width(pi->pixbuf);
3601 h = gdk_pixbuf_get_height(pi->pixbuf);
3603 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
3604 y1 = pi->y + (pi->height - h) / 2 + 8;
3608 x1 = pi->x + pi->width - 8;
3616 util_clip_triangle(x1, y1, x2, y2, x3, y3,
3619 p = pan_item_new_tri(pw, NULL, x, y, w, h,
3620 x1, y1, x2, y2, x3, y3,
3621 PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
3622 pan_item_tri_border(p, BORDER_1 | BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3623 pan_item_set_key(p, "info");
3624 pan_item_added(pw, p);
3626 plabel = pan_item_new_text(pw, pbox->x, pbox->y,
3627 _("Filename:"), TEXT_ATTR_BOLD,
3628 PAN_POPUP_TEXT_COLOR, 255);
3629 pan_item_set_key(plabel, "info");
3630 p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3631 pi->fd->name, TEXT_ATTR_NONE,
3632 PAN_POPUP_TEXT_COLOR, 255);
3633 pan_item_set_key(p, "info");
3634 pan_item_size_by_item(pbox, p, 0);
3636 plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3637 _("Date:"), TEXT_ATTR_BOLD,
3638 PAN_POPUP_TEXT_COLOR, 255);
3639 pan_item_set_key(plabel, "info");
3640 p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3641 text_from_time(pi->fd->date), TEXT_ATTR_NONE,
3642 PAN_POPUP_TEXT_COLOR, 255);
3643 pan_item_set_key(p, "info");
3644 pan_item_size_by_item(pbox, p, 0);
3646 plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3647 _("Size:"), TEXT_ATTR_BOLD,
3648 PAN_POPUP_TEXT_COLOR, 255);
3649 pan_item_set_key(plabel, "info");
3650 buf = text_from_size(pi->fd->size);
3651 p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3652 buf, TEXT_ATTR_NONE,
3653 PAN_POPUP_TEXT_COLOR, 255);
3655 pan_item_set_key(p, "info");
3656 pan_item_size_by_item(pbox, p, 0);
3658 pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
3659 pan_item_added(pw, pbox);
3664 *-----------------------------------------------------------------------------
3666 *-----------------------------------------------------------------------------
3669 static void pan_search_status(PanWindow *pw, const gchar *text)
3671 gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
3674 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
3682 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3684 list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
3685 if (!list) return FALSE;
3687 found = g_list_find(list, pw->click_pi);
3688 if (found && found->next)
3690 found = found->next;
3698 pan_info_update(pw, pi);
3699 image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3701 buf = g_strdup_printf("%s ( %d / %d )",
3702 (path[0] == '/') ? _("path found") : _("filename found"),
3703 g_list_index(list, pi) + 1,
3704 g_list_length(list));
3705 pan_search_status(pw, buf);
3713 static gint pan_search_by_partial(PanWindow *pw, const gchar *text)
3721 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3723 list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
3724 if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
3729 needle = g_utf8_strdown(text, -1);
3730 list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
3733 if (!list) return FALSE;
3735 found = g_list_find(list, pw->click_pi);
3736 if (found && found->next)
3738 found = found->next;
3746 pan_info_update(pw, pi);
3747 image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3749 buf = g_strdup_printf("%s ( %d / %d )",
3751 g_list_index(list, pi) + 1,
3752 g_list_length(list));
3753 pan_search_status(pw, buf);
3761 static gint valid_date_separator(gchar c)
3763 return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
3766 static GList *pan_search_by_date_val(PanWindow *pw, ItemType type,
3767 gint year, gint month, gint day,
3773 work = g_list_last(pw->list_static);
3781 if (pi->fd && (pi->type == type || type == ITEM_NONE) &&
3782 ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
3786 tl = localtime(&pi->fd->date);
3791 match = (tl->tm_year == year - 1900);
3792 if (match && month >= 0) match = (tl->tm_mon == month - 1);
3793 if (match && day > 0) match = (tl->tm_mday == day);
3795 if (match) list = g_list_prepend(list, pi);
3800 return g_list_reverse(list);
3803 static gint pan_search_by_date(PanWindow *pw, const gchar *text)
3819 if (!text) return FALSE;
3821 ptr = (gchar *)text;
3822 while (*ptr != '\0')
3824 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
3829 if (t == -1) return FALSE;
3831 if (!lt) return FALSE;
3833 if (valid_date_separator(*text))
3836 mptr = (gchar *)text;
3840 year = (gint)strtol(text, &mptr, 10);
3841 if (mptr == text) return FALSE;
3844 if (*mptr != '\0' && valid_date_separator(*mptr))
3849 month = strtol(mptr, &dptr, 10);
3852 if (valid_date_separator(*dptr))
3854 month = lt->tm_mon + 1;
3862 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
3866 day = strtol(dptr, &eptr, 10);
3876 year = lt->tm_year + 1900;
3878 else if (year < 100)
3887 month < -1 || month == 0 || month > 12 ||
3888 day < -1 || day == 0 || day > 31) return FALSE;
3890 t = date_to_time(year, month, day);
3891 if (t < 0) return FALSE;
3893 if (pw->layout == LAYOUT_CALENDAR)
3895 list = pan_search_by_date_val(pw, ITEM_BOX, year, month, day, "day");
3901 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3902 list = pan_search_by_date_val(pw, type, year, month, day, NULL);
3907 found = g_list_find(list, pw->search_pi);
3908 if (found && found->next)
3910 found = found->next;
3921 if (pw->layout == LAYOUT_CALENDAR && pi && pi->type == ITEM_BOX)
3923 pan_info_update(pw, NULL);
3924 pan_calendar_update(pw, pi);
3925 image_scroll_to_point(pw->imd,
3926 pi->x + pi->width / 2,
3927 pi->y + pi->height / 2, 0.5, 0.5);
3931 pan_info_update(pw, pi);
3932 image_scroll_to_point(pw->imd,
3933 pi->x - PAN_FOLDER_BOX_BORDER * 5 / 2,
3939 buf = date_value_string(t, DATE_LENGTH_MONTH);
3944 buf = g_strdup_printf("%d %s", day, tmp);
3950 buf = date_value_string(t, DATE_LENGTH_YEAR);
3955 buf_count = g_strdup_printf("( %d / %d )",
3956 g_list_index(list, pi) + 1,
3957 g_list_length(list));
3961 buf_count = g_strdup_printf("(%s)", _("no match"));
3964 message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
3967 pan_search_status(pw, message);
3975 static void pan_search_activate_cb(const gchar *text, gpointer data)
3977 PanWindow *pw = data;
3981 tab_completion_append_to_history(pw->search_entry, text);
3983 if (pan_search_by_path(pw, text)) return;
3985 if ((pw->layout == LAYOUT_TIMELINE ||
3986 pw->layout == LAYOUT_CALENDAR) &&
3987 pan_search_by_date(pw, text))
3992 if (pan_search_by_partial(pw, text)) return;
3994 pan_search_status(pw, _("no match"));
3997 static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
3999 PanWindow *pw = data;
4002 visible = GTK_WIDGET_VISIBLE(pw->search_box);
4003 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
4007 gtk_widget_hide(pw->search_box);
4008 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
4012 gtk_widget_show(pw->search_box);
4013 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
4014 gtk_widget_grab_focus(pw->search_entry);
4020 *-----------------------------------------------------------------------------
4021 * view window main routines
4022 *-----------------------------------------------------------------------------
4025 static void button_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
4027 PanWindow *pw = data;
4035 rx = (double)(pr->x_scroll + event->x - pr->x_offset) / pr->scale;
4036 ry = (double)(pr->y_scroll + event->y - pr->y_offset) / pr->scale;
4039 pi = pan_item_find_by_coord(pw, (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB,
4042 switch (event->button)
4045 pan_info_update(pw, pi);
4047 if (!pi && pw->layout == LAYOUT_CALENDAR)
4049 pi = pan_item_find_by_coord(pw, ITEM_BOX, rx, ry, "day");
4050 pan_calendar_update(pw, pi);
4056 pan_info_update(pw, pi);
4057 menu = pan_popup_menu(pw);
4058 gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
4065 static void scroll_cb(PixbufRenderer *pr, GdkEventScroll *event, gpointer data)
4068 PanWindow *pw = data;
4075 if (!(event->state & GDK_SHIFT_MASK))
4081 if (event->state & GDK_CONTROL_MASK)
4083 switch (event->direction)
4086 pixbuf_renderer_zoom_adjust_at_point(pr, ZOOM_INCREMENT,
4087 (gint)event->x, (gint)event->y);
4089 case GDK_SCROLL_DOWN:
4090 pixbuf_renderer_zoom_adjust_at_point(pr, -ZOOM_INCREMENT,
4091 (gint)event->x, (gint)event->y);
4099 switch (event->direction)
4102 pixbuf_renderer_scroll(pr, 0, -h);
4104 case GDK_SCROLL_DOWN:
4105 pixbuf_renderer_scroll(pr, 0, h);
4107 case GDK_SCROLL_LEFT:
4108 pixbuf_renderer_scroll(pr, -w, 0);
4110 case GDK_SCROLL_RIGHT:
4111 pixbuf_renderer_scroll(pr, w, 0);
4119 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
4121 g_signal_connect(G_OBJECT(imd->pr), "clicked",
4122 G_CALLBACK(button_cb), pw);
4123 g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
4124 G_CALLBACK(scroll_cb), pw);
4127 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
4129 PanWindow *pw = data;
4132 pw->imd = pw->imd_normal;
4135 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off)
4137 if (force_off && !pw->fs) return;
4141 fullscreen_stop(pw->fs);
4145 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
4146 pan_image_set_buttons(pw, pw->fs->imd);
4147 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
4148 G_CALLBACK(pan_window_key_press_cb), pw);
4150 pw->imd = pw->fs->imd;
4154 static void pan_window_image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
4156 PanWindow *pw = data;
4159 text = image_zoom_get_as_text(pw->imd);
4160 gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
4164 static void pan_window_image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
4166 PanWindow *pw = data;
4171 if (pr->scale == 0.0) return;
4173 pixbuf_renderer_get_visible_rect(pr, &rect);
4174 pixbuf_renderer_get_image_size(pr, &width, &height);
4176 adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
4177 adj->page_size = (gdouble)rect.width;
4178 adj->page_increment = adj->page_size / 2.0;
4179 adj->step_increment = 48.0 / pr->scale;
4181 adj->upper = MAX((gdouble)width, 1.0);
4182 adj->value = (gdouble)rect.x;
4184 pref_signal_block_data(pw->scrollbar_h, pw);
4185 gtk_adjustment_changed(adj);
4186 gtk_adjustment_value_changed(adj);
4187 pref_signal_unblock_data(pw->scrollbar_h, pw);
4189 adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
4190 adj->page_size = (gdouble)rect.height;
4191 adj->page_increment = adj->page_size / 2.0;
4192 adj->step_increment = 48.0 / pr->scale;
4194 adj->upper = MAX((gdouble)height, 1.0);
4195 adj->value = (gdouble)rect.y;
4197 pref_signal_block_data(pw->scrollbar_v, pw);
4198 gtk_adjustment_changed(adj);
4199 gtk_adjustment_value_changed(adj);
4200 pref_signal_unblock_data(pw->scrollbar_v, pw);
4203 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
4205 PanWindow *pw = data;
4209 pr = PIXBUF_RENDERER(pw->imd_normal->pr);
4211 if (!pr->scale) return;
4213 x = (gint)gtk_range_get_value(range);
4215 pixbuf_renderer_scroll_to_point(pr, x, (gint)((gdouble)pr->y_scroll / pr->scale), 0.0, 0.0);
4218 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
4220 PanWindow *pw = data;
4224 pr = PIXBUF_RENDERER(pw->imd_normal->pr);
4226 if (!pr->scale) return;
4228 y = (gint)gtk_range_get_value(range);
4230 pixbuf_renderer_scroll_to_point(pr, (gint)((gdouble)pr->x_scroll / pr->scale), y, 0.0, 0.0);
4233 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
4235 PanWindow *pw = data;
4237 pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
4238 pan_window_layout_update(pw);
4241 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
4243 PanWindow *pw = data;
4245 pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
4246 pan_window_layout_update(pw);
4250 static void pan_window_date_toggle_cb(GtkWidget *button, gpointer data)
4252 PanWindow *pw = data;
4254 pw->exif_date_enable = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
4255 pan_window_layout_update(pw);
4259 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
4261 PanWindow *pw = data;
4264 path = remove_trailing_slash(new_text);
4265 parse_out_relatives(path);
4269 warning_dialog(_("Folder not found"),
4270 _("The entered path is not a folder"),
4271 GTK_STOCK_DIALOG_WARNING, pw->path_entry);
4275 tab_completion_append_to_history(pw->path_entry, path);
4277 pan_window_layout_set_path(pw, path);
4283 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
4285 PanWindow *pw = data;
4288 if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
4290 text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
4291 pan_window_entry_activate_cb(text, pw);
4295 static void pan_window_close(PanWindow *pw)
4297 pan_window_list = g_list_remove(pan_window_list, pw);
4299 pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_EXIF_DATE, pw->exif_date_enable);
4301 if (pw->idle_id != -1)
4303 g_source_remove(pw->idle_id);
4306 pan_fullscreen_toggle(pw, TRUE);
4307 gtk_widget_destroy(pw->window);
4309 pan_window_items_free(pw);
4317 static gint pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
4319 PanWindow *pw = data;
4321 pan_window_close(pw);
4325 static void pan_window_new_real(const gchar *path)
4334 GdkGeometry geometry;
4336 pw = g_new0(PanWindow, 1);
4338 pw->path = g_strdup(path);
4339 pw->layout = LAYOUT_TIMELINE;
4340 pw->size = LAYOUT_SIZE_THUMB_NORMAL;
4341 pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
4342 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
4344 if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_EXIF_DATE, &pw->exif_date_enable))
4346 pw->exif_date_enable = FALSE;
4349 pw->ignore_symlinks = TRUE;
4352 pw->list_static = NULL;
4353 pw->list_grid = NULL;
4356 pw->overlay_id = -1;
4359 pw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4361 geometry.min_width = 8;
4362 geometry.min_height = 8;
4363 gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
4365 gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
4366 gtk_window_set_title (GTK_WINDOW(pw->window), "Pan View - GQview");
4367 gtk_window_set_wmclass(GTK_WINDOW(pw->window), "view", "GQview");
4368 gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
4370 window_set_icon(pw->window, NULL, NULL);
4372 vbox = gtk_vbox_new(FALSE, 0);
4373 gtk_container_add(GTK_CONTAINER(pw->window), vbox);
4374 gtk_widget_show(vbox);
4376 box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
4378 pref_spacer(box, 0);
4379 pref_label_new(box, _("Location:"));
4380 combo = tab_completion_new_with_history(&pw->path_entry, path, "pan_view_path", -1,
4381 pan_window_entry_activate_cb, pw);
4382 g_signal_connect(G_OBJECT(pw->path_entry->parent), "changed",
4383 G_CALLBACK(pan_window_entry_change_cb), pw);
4384 gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
4385 gtk_widget_show(combo);
4387 combo = gtk_combo_box_new_text();
4388 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Timeline"));
4389 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Calendar"));
4390 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders"));
4391 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders (flower)"));
4392 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Grid"));
4394 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
4395 g_signal_connect(G_OBJECT(combo), "changed",
4396 G_CALLBACK(pan_window_layout_change_cb), pw);
4397 gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
4398 gtk_widget_show(combo);
4400 combo = gtk_combo_box_new_text();
4401 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Dots"));
4402 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("No Images"));
4403 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Small Thumbnails"));
4404 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Normal Thumbnails"));
4405 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Large Thumbnails"));
4406 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:10 (10%)"));
4407 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:4 (25%)"));
4408 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:3 (33%)"));
4409 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:2 (50%)"));
4410 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:1 (100%)"));
4412 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
4413 g_signal_connect(G_OBJECT(combo), "changed",
4414 G_CALLBACK(pan_window_layout_size_cb), pw);
4415 gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
4416 gtk_widget_show(combo);
4418 table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
4419 gtk_table_set_row_spacings(GTK_TABLE(table), 2);
4420 gtk_table_set_col_spacings(GTK_TABLE(table), 2);
4422 pw->imd = image_new(TRUE);
4423 pw->imd_normal = pw->imd;
4425 g_signal_connect(G_OBJECT(pw->imd->pr), "zoom",
4426 G_CALLBACK(pan_window_image_zoom_cb), pw);
4427 g_signal_connect(G_OBJECT(pw->imd->pr), "scroll_notify",
4428 G_CALLBACK(pan_window_image_scroll_notify_cb), pw);
4430 gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
4431 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
4432 gtk_widget_show(GTK_WIDGET(pw->imd->widget));
4434 pan_window_dnd_init(pw);
4436 pan_image_set_buttons(pw, pw->imd);
4438 pw->scrollbar_h = gtk_hscrollbar_new(NULL);
4439 g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
4440 G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
4441 gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
4442 GTK_FILL | GTK_EXPAND, 0, 0, 0);
4443 gtk_widget_show(pw->scrollbar_h);
4445 pw->scrollbar_v = gtk_vscrollbar_new(NULL);
4446 g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
4447 G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
4448 gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
4449 0, GTK_FILL | GTK_EXPAND, 0, 0);
4450 gtk_widget_show(pw->scrollbar_v);
4454 pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
4455 gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
4457 pref_spacer(pw->search_box, 0);
4458 pref_label_new(pw->search_box, _("Find:"));
4460 hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
4461 gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
4462 gtk_widget_show(hbox);
4464 combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
4465 pan_search_activate_cb, pw);
4466 gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
4467 gtk_widget_show(combo);
4469 pw->search_label = gtk_label_new("");
4470 gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
4471 gtk_widget_show(pw->search_label);
4475 box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4477 frame = gtk_frame_new(NULL);
4478 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
4479 gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
4480 gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
4481 gtk_widget_show(frame);
4483 hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
4484 gtk_container_add(GTK_CONTAINER(frame), hbox);
4485 gtk_widget_show(hbox);
4487 pref_spacer(hbox, 0);
4488 pw->label_message = pref_label_new(hbox, "");
4490 frame = gtk_frame_new(NULL);
4491 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
4492 gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
4493 gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
4494 gtk_widget_show(frame);
4496 pw->label_zoom = gtk_label_new("");
4497 gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
4498 gtk_widget_show(pw->label_zoom);
4501 pw->date_button = pref_checkbox_new(box, _("Use Exif date"), pw->exif_date_enable,
4502 G_CALLBACK(pan_window_date_toggle_cb), pw);
4505 pw->search_button = gtk_toggle_button_new();
4506 gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
4507 gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
4508 hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
4509 gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
4510 gtk_widget_show(hbox);
4511 pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
4512 gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
4513 gtk_widget_show(pw->search_button_arrow);
4514 pref_label_new(hbox, _("Find"));
4516 gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
4517 gtk_widget_show(pw->search_button);
4518 g_signal_connect(G_OBJECT(pw->search_button), "clicked",
4519 G_CALLBACK(pan_search_toggle_cb), pw);
4521 g_signal_connect(G_OBJECT(pw->window), "delete_event",
4522 G_CALLBACK(pan_window_delete_cb), pw);
4523 g_signal_connect(G_OBJECT(pw->window), "key_press_event",
4524 G_CALLBACK(pan_window_key_press_cb), pw);
4526 gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
4528 pan_window_layout_update(pw);
4530 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
4531 gtk_widget_show(pw->window);
4533 pan_window_list = g_list_append(pan_window_list, pw);
4537 *-----------------------------------------------------------------------------
4538 * peformance warnings
4539 *-----------------------------------------------------------------------------
4542 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
4546 generic_dialog_close(gd);
4548 pan_window_new_real(path);
4552 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
4556 hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
4557 pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
4560 static gint pan_warning(const gchar *path)
4566 GtkWidget *ct_button;
4569 if (path && strcmp(path, "/") == 0)
4571 pan_warning_folder(path, NULL);
4575 if (enable_thumb_caching &&
4576 thumbnail_spec_standard) return FALSE;
4578 if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
4579 if (hide_dlg) return FALSE;
4581 gd = generic_dialog_new(_("Pan View Performance"), "GQview", "pan_view_warning", NULL, FALSE,
4583 gd->data = g_strdup(path);
4584 generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
4585 pan_warning_ok_cb, TRUE);
4587 box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
4588 _("Pan view performance may be poor."),
4589 _("To improve performance of thumbnails in the pan view the"
4590 " following options can be enabled. Note that both options"
4591 " must be enabled to notice a change in performance."));
4593 group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4594 pref_spacer(group, PREF_PAD_INDENT);
4595 group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
4597 ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
4598 enable_thumb_caching, &enable_thumb_caching);
4599 button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
4600 thumbnail_spec_standard, &thumbnail_spec_standard);
4601 pref_checkbox_link_sensitivity(ct_button, button);
4605 pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
4606 G_CALLBACK(pan_warning_hide_cb), NULL);
4608 gtk_widget_show(gd->dialog);
4615 *-----------------------------------------------------------------------------
4617 *-----------------------------------------------------------------------------
4620 void pan_window_new(const gchar *path)
4622 if (pan_warning(path)) return;
4624 pan_window_new_real(path);
4628 *-----------------------------------------------------------------------------
4630 *-----------------------------------------------------------------------------
4633 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
4635 PanWindow *pw = data;
4638 path = pan_menu_click_path(pw);
4641 pan_fullscreen_toggle(pw, TRUE);
4642 view_window_new(path);
4646 static void pan_edit_cb(GtkWidget *widget, gpointer data)
4652 pw = submenu_item_get_data(widget);
4653 n = GPOINTER_TO_INT(data);
4656 path = pan_menu_click_path(pw);
4659 pan_fullscreen_toggle(pw, TRUE);
4660 start_editor_from_file(n, path);
4664 static void pan_info_cb(GtkWidget *widget, gpointer data)
4666 PanWindow *pw = data;
4669 path = pan_menu_click_path(pw);
4670 if (path) info_window_new(path, NULL);
4673 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
4675 PanWindow *pw = data;
4677 image_zoom_adjust(pw->imd, ZOOM_INCREMENT);
4680 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
4682 PanWindow *pw = data;
4684 image_zoom_adjust(pw->imd, -ZOOM_INCREMENT);
4687 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
4689 PanWindow *pw = data;
4691 image_zoom_set(pw->imd, 1.0);
4694 static void pan_copy_cb(GtkWidget *widget, gpointer data)
4696 PanWindow *pw = data;
4699 path = pan_menu_click_path(pw);
4700 if (path) file_util_copy(path, NULL, NULL, pw->imd->widget);
4703 static void pan_move_cb(GtkWidget *widget, gpointer data)
4705 PanWindow *pw = data;
4708 path = pan_menu_click_path(pw);
4709 if (path) file_util_move(path, NULL, NULL, pw->imd->widget);
4712 static void pan_rename_cb(GtkWidget *widget, gpointer data)
4714 PanWindow *pw = data;
4717 path = pan_menu_click_path(pw);
4718 if (path) file_util_rename(path, NULL, pw->imd->widget);
4721 static void pan_delete_cb(GtkWidget *widget, gpointer data)
4723 PanWindow *pw = data;
4726 path = pan_menu_click_path(pw);
4727 if (path) file_util_delete(path, NULL, pw->imd->widget);
4730 static void pan_exif_date_toggle_cb(GtkWidget *widget, gpointer data)
4732 PanWindow *pw = data;
4734 pw->exif_date_enable = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
4735 pan_window_layout_update(pw);
4738 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
4740 PanWindow *pw = data;
4742 pan_fullscreen_toggle(pw, FALSE);
4745 static void pan_close_cb(GtkWidget *widget, gpointer data)
4747 PanWindow *pw = data;
4749 pan_window_close(pw);
4752 static GtkWidget *pan_popup_menu(PanWindow *pw)
4758 active = (pw->click_pi != NULL);
4760 menu = popup_menu_short_lived();
4762 menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
4763 G_CALLBACK(pan_zoom_in_cb), pw);
4764 menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
4765 G_CALLBACK(pan_zoom_out_cb), pw);
4766 menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
4767 G_CALLBACK(pan_zoom_1_1_cb), pw);
4768 menu_item_add_divider(menu);
4770 submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw);
4771 gtk_widget_set_sensitive(item, active);
4773 menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active,
4774 G_CALLBACK(pan_info_cb), pw);
4776 menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
4777 G_CALLBACK(pan_new_window_cb), pw);
4779 menu_item_add_divider(menu);
4780 menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
4781 G_CALLBACK(pan_copy_cb), pw);
4782 menu_item_add_sensitive(menu, _("_Move..."), active,
4783 G_CALLBACK(pan_move_cb), pw);
4784 menu_item_add_sensitive(menu, _("_Rename..."), active,
4785 G_CALLBACK(pan_rename_cb), pw);
4786 menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
4787 G_CALLBACK(pan_delete_cb), pw);
4789 menu_item_add_divider(menu);
4790 item = menu_item_add_check(menu, _("Sort by E_xif date"), pw->exif_date_enable,
4791 G_CALLBACK(pan_exif_date_toggle_cb), pw);
4792 gtk_widget_set_sensitive(item, (pw->layout == LAYOUT_TIMELINE || pw->layout == LAYOUT_CALENDAR));
4794 menu_item_add_divider(menu);
4798 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4802 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4805 menu_item_add_divider(menu);
4806 menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
4812 *-----------------------------------------------------------------------------
4814 *-----------------------------------------------------------------------------
4817 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
4819 GtkSelectionData *selection_data, guint info,
4820 guint time, gpointer data)
4822 PanWindow *pw = data;
4824 if (gtk_drag_get_source_widget(context) == pw->imd->pr) return;
4826 if (info == TARGET_URI_LIST)
4830 list = uri_list_from_text(selection_data->data, TRUE);
4831 if (list && isdir((gchar *)list->data))
4833 gchar *path = list->data;
4835 pan_window_layout_set_path(pw, path);
4838 path_list_free(list);
4842 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
4843 GtkSelectionData *selection_data, guint info,
4844 guint time, gpointer data)
4846 PanWindow *pw = data;
4849 path = pan_menu_click_path(pw);
4859 case TARGET_URI_LIST:
4862 case TARGET_TEXT_PLAIN:
4867 list = g_list_append(NULL, (gchar *)path);
4868 text = uri_text_from_list(list, &len, plain_text);
4872 gtk_selection_data_set (selection_data, selection_data->target,
4879 gtk_selection_data_set (selection_data, selection_data->target,
4884 static void pan_window_dnd_init(PanWindow *pw)
4888 widget = pw->imd->pr;
4890 gtk_drag_source_set(widget, GDK_BUTTON2_MASK,
4891 dnd_file_drag_types, dnd_file_drag_types_count,
4892 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4893 g_signal_connect(G_OBJECT(widget), "drag_data_get",
4894 G_CALLBACK(pan_window_set_dnd_data), pw);
4896 gtk_drag_dest_set(widget,
4897 GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
4898 dnd_file_drop_types, dnd_file_drop_types_count,
4899 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4900 g_signal_connect(G_OBJECT(widget), "drag_data_received",
4901 G_CALLBACK(pan_window_get_dnd_data), pw);
4905 *-----------------------------------------------------------------------------
4906 * maintenance (for rename, move, remove)
4907 *-----------------------------------------------------------------------------