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"
129 #define SORT_BY_EXIF_DATE 1
135 LAYOUT_FOLDERS_LINEAR,
136 LAYOUT_FOLDERS_FLOWER,
141 LAYOUT_SIZE_THUMB_DOTS = 0,
142 LAYOUT_SIZE_THUMB_NONE,
143 LAYOUT_SIZE_THUMB_SMALL,
144 LAYOUT_SIZE_THUMB_NORMAL,
145 LAYOUT_SIZE_THUMB_LARGE,
164 TEXT_ATTR_BOLD = 1 << 0,
165 TEXT_ATTR_HEADING = 1 << 1,
166 TEXT_ATTR_MARKUP = 1 << 2
177 typedef struct _PanItem PanItem;
192 TextAttrType text_attr;
210 typedef struct _PanWindow PanWindow;
215 ImageWindow *imd_normal;
218 GtkWidget *path_entry;
220 GtkWidget *label_message;
221 GtkWidget *label_zoom;
223 GtkWidget *search_box;
224 GtkWidget *search_entry;
225 GtkWidget *search_label;
226 GtkWidget *search_button;
227 GtkWidget *search_button_arrow;
229 GtkWidget *scrollbar_h;
230 GtkWidget *scrollbar_v;
241 gint ignore_symlinks;
252 CacheLoader *cache_cl;
265 typedef struct _PanGrid PanGrid;
274 typedef struct _PanCacheData PanCacheData;
275 struct _PanCacheData {
281 static GList *pan_window_list = NULL;
284 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend,
285 gint ignore_symlinks);
287 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height);
289 static void pan_window_layout_update_idle(PanWindow *pw);
291 static GtkWidget *pan_popup_menu(PanWindow *pw);
292 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off);
294 static void pan_window_close(PanWindow *pw);
296 static void pan_window_dnd_init(PanWindow *pw);
308 static gint date_compare(time_t a, time_t b, DateLengthType length)
313 if (length == DATE_LENGTH_EXACT) return (a == b);
315 if (!localtime_r(&a, &ta) ||
316 !localtime_r(&b, &tb)) return FALSE;
318 if (ta.tm_year != tb.tm_year) return FALSE;
319 if (length == DATE_LENGTH_YEAR) return TRUE;
321 if (ta.tm_mon != tb.tm_mon) return FALSE;
322 if (length == DATE_LENGTH_MONTH) return TRUE;
324 if (length == DATE_LENGTH_WEEK) return (ta.tm_yday / 7 == tb.tm_yday / 7);
326 if (ta.tm_mday != tb.tm_mday) return FALSE;
327 if (length == DATE_LENGTH_DAY) return TRUE;
329 return (ta.tm_hour == tb.tm_hour);
332 static gint date_value(time_t d, DateLengthType length)
336 if (!localtime_r(&d, &td)) return -1;
340 case DATE_LENGTH_DAY:
343 case DATE_LENGTH_WEEK:
346 case DATE_LENGTH_MONTH:
347 return td.tm_mon + 1;
349 case DATE_LENGTH_YEAR:
350 return td.tm_year + 1900;
352 case DATE_LENGTH_EXACT:
360 static gchar *date_value_string(time_t d, DateLengthType length)
364 gchar *format = NULL;
366 if (!localtime_r(&d, &td)) return g_strdup("");
370 case DATE_LENGTH_DAY:
371 return g_strdup_printf("%d", td.tm_mday);
373 case DATE_LENGTH_WEEK:
376 case DATE_LENGTH_MONTH:
379 case DATE_LENGTH_YEAR:
380 return g_strdup_printf("%d", td.tm_year + 1900);
382 case DATE_LENGTH_EXACT:
384 return g_strdup(text_from_time(d));
389 if (format && strftime(buf, sizeof(buf), format, &td) > 0)
391 gchar *ret = g_locale_to_utf8(buf, -1, NULL, NULL, NULL);
398 static time_t date_to_time(gint year, gint month, gint day)
405 lt.tm_mday = (day >= 1 && day <= 31) ? day : 1;
406 lt.tm_mon = (month >= 1 && month <= 12) ? month - 1 : 0;
407 lt.tm_year = year - 1900;
414 *-----------------------------------------------------------------------------
416 *-----------------------------------------------------------------------------
419 static void pan_cache_free(PanWindow *pw)
423 work = pw->cache_list;
431 cache_sim_data_free(pc->cd);
432 file_data_free((FileData *)pc);
435 g_list_free(pw->cache_list);
436 pw->cache_list = NULL;
438 filelist_free(pw->cache_todo);
439 pw->cache_todo = NULL;
445 cache_loader_free(pw->cache_cl);
449 static void pan_cache_fill(PanWindow *pw, const gchar *path)
455 list = pan_window_layout_list(path, SORT_NAME, TRUE, pw->ignore_symlinks);
456 pw->cache_todo = g_list_reverse(list);
458 pw->cache_total = g_list_length(pw->cache_todo);
461 static void pan_cache_step_done_cb(CacheLoader *cl, gint error, gpointer data)
463 PanWindow *pw = data;
468 pc = pw->cache_list->data;
477 cache_loader_free(cl);
480 pan_window_layout_update_idle(pw);
483 static gint pan_cache_step(PanWindow *pw)
487 CacheDataType load_mask;
489 if (!pw->cache_todo) return TRUE;
491 fd = pw->cache_todo->data;
492 pw->cache_todo = g_list_remove(pw->cache_todo, fd);
495 if (enable_thumb_caching)
499 found = cache_find_location(CACHE_TYPE_SIM, fd->path);
500 if (found && filetime(found) == fd->date)
502 cd = cache_sim_data_load(found);
507 if (!cd) cd = cache_sim_data_new();
511 cd->dimensions = image_load_dimensions(fd->path, &cd->width, &cd->height);
512 if (enable_thumb_caching &&
518 base = cache_get_location(CACHE_TYPE_SIM, fd->path, FALSE, &mode);
519 if (cache_ensure_dir_exists(base, mode))
522 cd->path = cache_get_location(CACHE_TYPE_SIM, fd->path, TRUE, NULL);
523 if (cache_sim_data_save(cd))
525 filetime_set(cd->path, filetime(fd->path));
534 pc = g_new0(PanCacheData, 1);
535 memcpy(pc, fd, sizeof(FileData));
540 pw->cache_list = g_list_prepend(pw->cache_list, pc);
542 cache_loader_free(pw->cache_cl);
544 load_mask = CACHE_LOADER_NONE;
545 if (pw->size > LAYOUT_SIZE_THUMB_LARGE) load_mask |= CACHE_LOADER_DIMENSIONS;
546 if (SORT_BY_EXIF_DATE) load_mask |= CACHE_LOADER_DATE;
547 pw->cache_cl = cache_loader_new(((FileData *)pc)->path, load_mask,
548 pan_cache_step_done_cb, pw);
549 return (pw->cache_cl == NULL);
552 /* This sync date function is optimized for lists with a common sort */
553 static void pan_cache_sync_date(PanWindow *pw, GList *list)
558 haystack = g_list_copy(pw->cache_list);
576 path = ((FileData *)pc)->path;
577 if (path && strcmp(path, fd->path) == 0)
579 if (pc->cd && pc->cd->have_date && pc->cd->date >= 0)
581 fd->date = pc->cd->date;
584 haystack = g_list_delete_link(haystack, needle);
589 needle = needle->next;
594 g_list_free(haystack);
598 *-----------------------------------------------------------------------------
600 *-----------------------------------------------------------------------------
603 static void pan_grid_clear(PanWindow *pw)
607 work = pw->list_grid;
615 g_list_free(pg->list);
619 g_list_free(pw->list_grid);
620 pw->list_grid = NULL;
622 pw->list = g_list_concat(pw->list, pw->list_static);
623 pw->list_static = NULL;
626 static void pan_grid_build(PanWindow *pw, gint width, gint height, gint grid_size)
639 l = g_list_length(pw->list);
643 total = (gdouble)width * (gdouble)height / (gdouble)l;
646 aw = (gdouble)width / s;
647 ah = (gdouble)height / s;
649 col = (gint)(sqrt((gdouble)l / grid_size) * width / height + 0.999);
650 col = CLAMP(col, 1, l / grid_size + 1);
651 row = (gint)((gdouble)l / grid_size / col);
652 if (row < 1) row = 1;
654 /* limit minimum size of grid so that a tile will always fit regardless of position */
655 cw = MAX((gint)ceil((gdouble)width / col), PAN_TILE_SIZE * 2);
656 ch = MAX((gint)ceil((gdouble)height / row), PAN_TILE_SIZE * 2);
661 printf("intersect speedup grid is %dx%d, based on %d average per grid\n", col, row, grid_size);
663 for (j = 0; j < row; j++)
664 for (i = 0; i < col; i++)
666 if ((i + 1) * cw / 2 < width && (j + 1) * ch / 2 < height)
670 pg = g_new0(PanGrid, 1);
677 pw->list_grid = g_list_prepend(pw->list_grid, pg);
679 if (debug) printf("grid section: %d,%d (%dx%d)\n", pg->x, pg->y, pg->w, pg->h);
692 grid = pw->list_grid;
701 if (util_clip_region(pi->x, pi->y, pi->width, pi->height,
702 pg->x, pg->y, pg->w, pg->h,
705 pg->list = g_list_prepend(pg->list, pi);
710 work = pw->list_grid;
718 pg->list = g_list_reverse(pg->list);
721 pw->list_static = pw->list;
728 *-----------------------------------------------------------------------------
730 *-----------------------------------------------------------------------------
733 static void pan_item_free(PanItem *pi)
737 if (pi->pixbuf) g_object_unref(pi->pixbuf);
738 if (pi->fd) file_data_free(pi->fd);
746 static void pan_window_items_free(PanWindow *pw)
755 PanItem *pi = work->data;
761 g_list_free(pw->list);
764 g_list_free(pw->queue);
768 image_loader_free(pw->il);
771 thumb_loader_free(pw->tl);
775 pw->search_pi = NULL;
778 static PanItem *pan_item_new_thumb(PanWindow *pw, FileData *fd, gint x, gint y)
782 pi = g_new0(PanItem, 1);
783 pi->type = ITEM_THUMB;
787 pi->width = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2;
788 pi->height = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2;
794 pw->list = g_list_prepend(pw->list, pi);
799 static PanItem *pan_item_new_box(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height,
801 guint8 base_r, guint8 base_g, guint8 base_b, guint8 base_a,
802 guint8 bord_r, guint8 bord_g, guint8 bord_b, guint8 bord_a)
806 pi = g_new0(PanItem, 1);
814 pi->color_r = base_r;
815 pi->color_g = base_g;
816 pi->color_b = base_b;
817 pi->color_a = base_a;
819 pi->color2_r = bord_r;
820 pi->color2_g = bord_g;
821 pi->color2_b = bord_b;
822 pi->color2_a = bord_a;
823 pi->border = border_size;
825 pw->list = g_list_prepend(pw->list, pi);
830 static void pan_item_box_shadow(PanItem *pi, gint offset, gint fade)
834 if (!pi || pi->type != ITEM_BOX) return;
839 pi->width -= shadow[0];
840 pi->height -= shadow[0];
843 shadow = g_new0(gint, 2);
848 pi->height += offset;
854 static PanItem *pan_item_new_tri(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height,
855 gint x1, gint y1, gint x2, gint y2, gint x3, gint y3,
856 guint8 r, guint8 g, guint8 b, guint8 a)
861 pi = g_new0(PanItem, 1);
862 pi->type = ITEM_TRIANGLE;
873 coord = g_new0(gint, 6);
883 pi->border = BORDER_NONE;
885 pw->list = g_list_prepend(pw->list, pi);
890 static void pan_item_tri_border(PanItem *pi, gint borders,
891 guint8 r, guint8 g, guint8 b, guint8 a)
893 if (!pi || pi->type != ITEM_TRIANGLE) return;
895 pi->border = borders;
903 static PangoLayout *pan_item_text_layout(PanItem *pi, GtkWidget *widget)
907 layout = gtk_widget_create_pango_layout(widget, NULL);
909 if (pi->text_attr & TEXT_ATTR_MARKUP)
911 pango_layout_set_markup(layout, pi->text, -1);
915 if (pi->text_attr & TEXT_ATTR_BOLD ||
916 pi->text_attr & TEXT_ATTR_HEADING)
921 pal = pango_attr_list_new();
922 if (pi->text_attr & TEXT_ATTR_BOLD)
924 pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD);
926 pa->end_index = G_MAXINT;
927 pango_attr_list_insert(pal, pa);
929 if (pi->text_attr & TEXT_ATTR_HEADING)
931 pa = pango_attr_scale_new(PANGO_SCALE_LARGE);
933 pa->end_index = G_MAXINT;
934 pango_attr_list_insert(pal, pa);
936 pango_layout_set_attributes(layout, pal);
937 pango_attr_list_unref(pal);
940 pango_layout_set_text(layout, pi->text, -1);
944 static void pan_item_text_compute_size(PanItem *pi, GtkWidget *widget)
948 if (!pi || !pi->text || !widget) return;
950 layout = pan_item_text_layout(pi, widget);
951 pango_layout_get_pixel_size(layout, &pi->width, &pi->height);
952 g_object_unref(G_OBJECT(layout));
954 pi->width += PAN_TEXT_BORDER_SIZE * 2;
955 pi->height += PAN_TEXT_BORDER_SIZE * 2;
958 static PanItem *pan_item_new_text(PanWindow *pw, gint x, gint y, const gchar *text, TextAttrType attr,
959 guint8 r, guint8 g, guint8 b, guint8 a)
963 pi = g_new0(PanItem, 1);
964 pi->type = ITEM_TEXT;
967 pi->text = g_strdup(text);
968 pi->text_attr = attr;
975 pan_item_text_compute_size(pi, pw->imd->pr);
977 pw->list = g_list_prepend(pw->list, pi);
982 static void pan_item_set_key(PanItem *pi, const gchar *key)
989 pi->key = g_strdup(key);
993 static void pan_item_added(PanWindow *pw, PanItem *pi)
996 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
999 static void pan_item_remove(PanWindow *pw, PanItem *pi)
1003 if (pw->click_pi == pi) pw->click_pi = NULL;
1004 if (pw->queue_pi == pi) pw->queue_pi = NULL;
1005 if (pw->search_pi == pi) pw->search_pi = NULL;
1006 pw->queue = g_list_remove(pw->queue, pi);
1008 pw->list = g_list_remove(pw->list, pi);
1009 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
1013 static void pan_item_size_by_item(PanItem *pi, PanItem *child, gint border)
1015 if (!pi || !child) return;
1017 if (pi->x + pi->width < child->x + child->width + border)
1018 pi->width = child->x + child->width + border - pi->x;
1020 if (pi->y + pi->height < child->y + child->height + border)
1021 pi->height = child->y + child->height + border - pi->y;
1024 static void pan_item_size_coordinates(PanItem *pi, gint border, gint *w, gint *h)
1028 if (*w < pi->x + pi->width + border) *w = pi->x + pi->width + border;
1029 if (*h < pi->y + pi->height + border) *h = pi->y + pi->height + border;
1032 static void pan_item_image_find_size(PanWindow *pw, PanItem *pi, gint w, gint h)
1039 if (!pi->fd) return;
1041 work = pw->cache_list;
1050 path = ((FileData *)pc)->path;
1052 if (pc->cd && pc->cd->dimensions &&
1053 path && strcmp(path, pi->fd->path) == 0)
1055 pi->width = MAX(1, pc->cd->width * pw->image_size / 100);
1056 pi->height = MAX(1, pc->cd->height * pw->image_size / 100);
1058 pw->cache_list = g_list_remove(pw->cache_list, pc);
1059 cache_sim_data_free(pc->cd);
1060 file_data_free((FileData *)pc);
1066 static PanItem *pan_item_new_image(PanWindow *pw, FileData *fd, gint x, gint y, gint w, gint h)
1070 pi = g_new0(PanItem, 1);
1071 pi->type = ITEM_IMAGE;
1076 pan_item_image_find_size(pw, pi, w, h);
1078 pw->list = g_list_prepend(pw->list, pi);
1083 static PanItem *pan_item_find_by_key(PanWindow *pw, ItemType type, const gchar *key)
1087 if (!key) return NULL;
1089 work = g_list_last(pw->list);
1095 if ((pi->type == type || type == ITEM_NONE) &&
1096 pi->key && strcmp(pi->key, key) == 0)
1102 work = g_list_last(pw->list_static);
1108 if ((pi->type == type || type == ITEM_NONE) &&
1109 pi->key && strcmp(pi->key, key) == 0)
1119 /* when ignore_case and partial are TRUE, path should be converted to lower case */
1120 static GList *pan_item_find_by_path_l(GList *list, GList *search_list,
1121 ItemType type, const gchar *path,
1122 gint ignore_case, gint partial)
1126 work = g_list_last(search_list);
1132 if ((pi->type == type || type == ITEM_NONE) && pi->fd)
1138 if (pi->fd->path && strcmp(path, pi->fd->path) == 0) match = TRUE;
1140 else if (pi->fd->name)
1148 haystack = g_utf8_strdown(pi->fd->name, -1);
1149 match = (strstr(haystack, path) != NULL);
1154 if (strstr(pi->fd->name, path)) match = TRUE;
1157 else if (ignore_case)
1159 if (strcasecmp(path, pi->fd->name) == 0) match = TRUE;
1163 if (strcmp(path, pi->fd->name) == 0) match = TRUE;
1167 if (match) list = g_list_prepend(list, pi);
1175 /* when ignore_case and partial are TRUE, path should be converted to lower case */
1176 static GList *pan_item_find_by_path(PanWindow *pw, ItemType type, const gchar *path,
1177 gint ignore_case, gint partial)
1181 if (!path) return NULL;
1182 if (partial && path[0] == '/') return NULL;
1184 list = pan_item_find_by_path_l(list, pw->list_static, type, path, ignore_case, partial);
1185 list = pan_item_find_by_path_l(list, pw->list, type, path, ignore_case, partial);
1187 return g_list_reverse(list);
1190 static PanItem *pan_item_find_by_coord_l(GList *list, ItemType type, gint x, gint y, const gchar *key)
1200 if ((pi->type == type || type == ITEM_NONE) &&
1201 x >= pi->x && x < pi->x + pi->width &&
1202 y >= pi->y && y < pi->y + pi->height &&
1203 (!key || (pi->key && strcmp(pi->key, key) == 0)))
1213 static PanItem *pan_item_find_by_coord(PanWindow *pw, ItemType type, gint x, gint y, const gchar *key)
1217 pi = pan_item_find_by_coord_l(pw->list, type, x, y, key);
1220 return pan_item_find_by_coord_l(pw->list_static, type, x, y, key);
1224 *-----------------------------------------------------------------------------
1226 *-----------------------------------------------------------------------------
1229 static gint islink_loop(const gchar *s)
1235 sl = path_from_utf8(s);
1237 if (lstat(sl, &st) == 0 && S_ISLNK(st.st_mode))
1242 buf = g_malloc(st.st_size + 1);
1243 l = readlink(sl, buf, st.st_size);
1244 if (l == st.st_size)
1248 parse_out_relatives(buf);
1251 parse_out_relatives(sl);
1255 if (strncmp(sl, buf, l) == 0 &&
1256 (sl[l] == '\0' || sl[l] == '/' || l == 1)) ret = TRUE;
1262 link_path = concat_dir_and_file(sl, buf);
1263 parse_out_relatives(link_path);
1265 if (strncmp(sl, link_path, l) == 0 &&
1266 (sl[l] == '\0' || sl[l] == '/' || l == 1)) ret = TRUE;
1280 static gint is_ignored(const gchar *s, gint ignore_symlinks)
1285 if (!lstat_utf8(s, &st)) return TRUE;
1288 /* normal filesystems have directories with some size or block allocation,
1289 * special filesystems (like linux /proc) set both to zero.
1290 * enable this check if you enable listing the root "/" folder
1292 if (st.st_size == 0 && st.st_blocks == 0) return TRUE;
1295 if (S_ISLNK(st.st_mode) && (ignore_symlinks || islink_loop(s))) return TRUE;
1297 n = filename_from_path(s);
1298 if (n && strcmp(n, GQVIEW_RC_DIR) == 0) return TRUE;
1303 static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend,
1304 gint ignore_symlinks)
1306 GList *flist = NULL;
1307 GList *dlist = NULL;
1311 filelist_read(path, &flist, &dlist);
1312 if (sort != SORT_NONE)
1314 flist = filelist_sort(flist, sort, ascend);
1315 dlist = filelist_sort(dlist, sort, ascend);
1325 folders = g_list_remove(folders, fd);
1327 if (!is_ignored(fd->path, ignore_symlinks) &&
1328 filelist_read(fd->path, &flist, &dlist))
1330 if (sort != SORT_NONE)
1332 flist = filelist_sort(flist, sort, ascend);
1333 dlist = filelist_sort(dlist, sort, ascend);
1336 result = g_list_concat(result, flist);
1337 folders = g_list_concat(dlist, folders);
1346 static void pan_window_layout_compute_grid(PanWindow *pw, const gchar *path, gint *width, gint *height)
1354 list = pan_window_layout_list(path, SORT_NAME, TRUE, pw->ignore_symlinks);
1356 grid_size = (gint)sqrt((double)g_list_length(list));
1357 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1359 grid_size = grid_size * (512 + PAN_THUMB_GAP) * pw->image_size / 100;
1363 grid_size = grid_size * (PAN_THUMB_SIZE + PAN_THUMB_GAP);
1368 *width = PAN_FOLDER_BOX_BORDER * 2;
1369 *height = PAN_FOLDER_BOX_BORDER * 2;
1382 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1384 pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1386 x += pi->width + PAN_THUMB_GAP;
1387 if (y + pi->height + PAN_THUMB_GAP > next_y) next_y = y + pi->height + PAN_THUMB_GAP;
1396 pi = pan_item_new_thumb(pw, fd, x, y);
1398 x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1402 y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1405 pan_item_size_coordinates(pi, PAN_THUMB_GAP, width, height);
1411 static void pan_window_Layout_compute_folders_flower_size(PanWindow *pw, gint *width, gint *height)
1414 gint x1, y1, x2, y2;
1429 if (x1 > pi->x) x1 = pi->x;
1430 if (y1 > pi->y) y1 = pi->y;
1431 if (x2 < pi->x + pi->width) x2 = pi->x + pi->width;
1432 if (y2 < pi->y + pi->height) y2 = pi->y + pi->height;
1435 x1 -= PAN_FOLDER_BOX_BORDER;
1436 y1 -= PAN_FOLDER_BOX_BORDER;
1437 x2 += PAN_FOLDER_BOX_BORDER;
1438 y2 += PAN_FOLDER_BOX_BORDER;
1451 if (pi->type == ITEM_TRIANGLE && pi->data)
1465 if (width) *width = x2 - x1;
1466 if (height) *height = y2 - y1;
1469 typedef struct _FlowerGroup FlowerGroup;
1470 struct _FlowerGroup {
1483 static void pan_window_layout_compute_folder_flower_move(FlowerGroup *group, gint x, gint y)
1487 work = group->items;
1505 static void pan_window_layout_compute_folder_flower_position(FlowerGroup *group, FlowerGroup *parent,
1506 gint *result_x, gint *result_y)
1512 radius = parent->circumference / (2*PI);
1513 radius = MAX(radius, parent->diameter / 2 + group->diameter / 2);
1515 a = 2*PI * group->diameter / parent->circumference;
1517 x = (gint)((double)radius * cos(parent->angle + a / 2));
1518 y = (gint)((double)radius * sin(parent->angle + a / 2));
1525 x += parent->width / 2;
1526 y += parent->height / 2;
1528 x -= group->width / 2;
1529 y -= group->height / 2;
1535 static void pan_window_layout_compute_folder_flower_build(PanWindow *pw, FlowerGroup *group, FlowerGroup *parent)
1542 if (parent && parent->children)
1544 pan_window_layout_compute_folder_flower_position(group, parent, &x, &y);
1552 pan_window_layout_compute_folder_flower_move(group, x, y);
1557 gint px, py, gx, gy;
1558 gint x1, y1, x2, y2;
1560 px = parent->x + parent->width / 2;
1561 py = parent->y + parent->height / 2;
1563 gx = group->x + group->width / 2;
1564 gy = group->y + group->height / 2;
1569 x2 = MAX(px, gx + 5);
1570 y2 = MAX(py, gy + 5);
1572 pi = pan_item_new_tri(pw, NULL, x1, y1, x2 - x1, y2 - y1,
1573 px, py, gx, gy, gx + 5, gy + 5,
1575 pan_item_tri_border(pi, BORDER_1 | BORDER_3,
1579 pw->list = g_list_concat(group->items, pw->list);
1580 group->items = NULL;
1582 group->circumference = 0;
1583 work = group->children;
1591 group->circumference += child->diameter;
1594 work = g_list_last(group->children);
1602 pan_window_layout_compute_folder_flower_build(pw, child, group);
1605 g_list_free(group->children);
1609 static FlowerGroup *pan_window_layout_compute_folders_flower_path(PanWindow *pw, const gchar *path,
1622 if (!filelist_read(path, &f, &d)) return NULL;
1623 if (!f && !d) return NULL;
1625 f = filelist_sort(f, SORT_NAME, TRUE);
1626 d = filelist_sort(d, SORT_NAME, TRUE);
1628 pi_box = pan_item_new_text(pw, x, y, path, TEXT_ATTR_NONE,
1629 PAN_TEXT_COLOR, 255);
1631 y += pi_box->height;
1633 pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1635 PAN_FOLDER_BOX_BORDER * 2, PAN_FOLDER_BOX_BORDER * 2,
1636 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1637 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1638 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1640 x += PAN_FOLDER_BOX_BORDER;
1641 y += PAN_FOLDER_BOX_BORDER;
1643 grid_size = (gint)(sqrt(g_list_length(f)) + 0.9);
1657 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1659 pi = pan_item_new_image(pw, fd, x, y, 10, 10);
1660 x += pi->width + PAN_THUMB_GAP;
1661 if (pi->height > y_height) y_height = pi->height;
1665 pi = pan_item_new_thumb(pw, fd, x, y);
1666 x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1667 y_height = PAN_THUMB_SIZE;
1671 if (grid_count >= grid_size)
1675 y += y_height + PAN_THUMB_GAP;
1679 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1682 group = g_new0(FlowerGroup, 1);
1683 group->items = pw->list;
1686 group->width = pi_box->width;
1687 group->height = pi_box->y + pi_box->height;
1688 group->diameter = (int)sqrt(group->width * group->width + group->height * group->height);
1690 group->children = NULL;
1701 if (!is_ignored(fd->path, pw->ignore_symlinks))
1703 child = pan_window_layout_compute_folders_flower_path(pw, fd->path, 0, 0);
1704 if (child) group->children = g_list_prepend(group->children, child);
1708 if (!f && !group->children)
1710 work = group->items;
1721 g_list_free(group->items);
1732 static void pan_window_layout_compute_folders_flower(PanWindow *pw, const gchar *path,
1733 gint *width, gint *height,
1734 gint *scroll_x, gint *scroll_y)
1739 group = pan_window_layout_compute_folders_flower_path(pw, path, 0, 0);
1740 pan_window_layout_compute_folder_flower_build(pw, group, NULL);
1742 pan_window_Layout_compute_folders_flower_size(pw, width, height);
1744 list = pan_item_find_by_path(pw, ITEM_BOX, path, FALSE, FALSE);
1747 PanItem *pi = list->data;
1748 *scroll_x = pi->x + pi->width / 2;
1749 *scroll_y = pi->y + pi->height / 2;
1754 static void pan_window_layout_compute_folders_linear_path(PanWindow *pw, const gchar *path,
1755 gint *x, gint *y, gint *level,
1757 gint *width, gint *height)
1765 if (!filelist_read(path, &f, &d)) return;
1766 if (!f && !d) return;
1768 f = filelist_sort(f, SORT_NAME, TRUE);
1769 d = filelist_sort(d, SORT_NAME, TRUE);
1771 *x = PAN_FOLDER_BOX_BORDER + ((*level) * MAX(PAN_FOLDER_BOX_BORDER, PAN_THUMB_GAP));
1773 pi_box = pan_item_new_text(pw, *x, *y, path, TEXT_ATTR_NONE,
1774 PAN_TEXT_COLOR, 255);
1776 *y += pi_box->height;
1778 pi_box = pan_item_new_box(pw, file_data_new_simple(path),
1780 PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER,
1781 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
1782 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
1783 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
1785 *x += PAN_FOLDER_BOX_BORDER;
1786 *y += PAN_FOLDER_BOX_BORDER;
1797 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
1799 pi = pan_item_new_image(pw, fd, *x, *y, 10, 10);
1800 *x += pi->width + PAN_THUMB_GAP;
1801 if (pi->height > y_height) y_height = pi->height;
1805 pi = pan_item_new_thumb(pw, fd, *x, *y);
1806 *x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1807 y_height = PAN_THUMB_SIZE;
1810 pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER);
1813 if (f) *y = pi_box->y + pi_box->height;
1825 if (!is_ignored(fd->path, pw->ignore_symlinks))
1827 *level = *level + 1;
1828 pan_window_layout_compute_folders_linear_path(pw, fd->path, x, y, level,
1829 pi_box, width, height);
1830 *level = *level - 1;
1836 pan_item_size_by_item(parent, pi_box, PAN_FOLDER_BOX_BORDER);
1838 if (*y < pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER)
1839 *y = pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER;
1841 pan_item_size_coordinates(pi_box, PAN_FOLDER_BOX_BORDER, width, height);
1844 static void pan_window_layout_compute_folders_linear(PanWindow *pw, const gchar *path, gint *width, gint *height)
1851 x = PAN_FOLDER_BOX_BORDER;
1852 y = PAN_FOLDER_BOX_BORDER;
1853 w = PAN_FOLDER_BOX_BORDER * 2;
1854 h = PAN_FOLDER_BOX_BORDER * 2;
1856 pan_window_layout_compute_folders_linear_path(pw, path, &x, &y, &level, NULL, &w, &h);
1858 if (width) *width = w;
1859 if (height) *height = h;
1863 *-----------------------------------------------------------------------------
1865 *-----------------------------------------------------------------------------
1868 static void pan_calendar_update(PanWindow *pw, PanItem *pi_day)
1874 gint x1, y1, x2, y2, x3, y3;
1879 while ((pi = pan_item_find_by_key(pw, ITEM_NONE, "day_bubble"))) pan_item_remove(pw, pi);
1881 if (!pi_day || pi_day->type != ITEM_BOX ||
1882 !pi_day->key || strcmp(pi_day->key, "day") != 0) return;
1884 list = pan_layout_intersect(pw, pi_day->x, pi_day->y, pi_day->width, pi_day->height);
1896 if (dot->type != ITEM_BOX || !dot->fd ||
1897 !dot->key || strcmp(dot->key, "dot") != 0)
1899 list = g_list_delete_link(list, node);
1907 grid = (gint)(sqrt(g_list_length(list)) + 0.5);
1909 x = pi_day->x + pi_day->width + 4;
1913 if (y + grid * (PAN_THUMB_SIZE + PAN_THUMB_GAP) + PAN_FOLDER_BOX_BORDER * 4 > pw->pr->image_height)
1915 y = pw->pr->image_height - (grid * (PAN_THUMB_SIZE + PAN_THUMB_GAP) + PAN_FOLDER_BOX_BORDER * 4);
1919 pbox = pan_item_new_box(pw, NULL, x, y, PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER,
1920 PAN_CAL_POPUP_BORDER,
1921 PAN_CAL_POPUP_COLOR, PAN_CAL_POPUP_ALPHA,
1922 PAN_CAL_POPUP_BORDER_COLOR, PAN_CAL_POPUP_ALPHA);
1923 pan_item_set_key(pbox, "day_bubble");
1930 buf = date_value_string(pi_day->fd->date, DATE_LENGTH_WEEK);
1931 plabel = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
1932 PAN_CAL_POPUP_TEXT_COLOR, 255);
1933 pan_item_set_key(plabel, "day_bubble");
1936 pan_item_size_by_item(pbox, plabel, 0);
1938 y += plabel->height;
1945 x += PAN_FOLDER_BOX_BORDER;
1946 y += PAN_FOLDER_BOX_BORDER;
1960 pimg = pan_item_new_thumb(pw, file_data_new_simple(dot->fd->path), x, y);
1961 pan_item_set_key(pimg, "day_bubble");
1963 pan_item_size_by_item(pbox, pimg, PAN_FOLDER_BOX_BORDER);
1968 x += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1973 x = pbox->x + PAN_FOLDER_BOX_BORDER;
1974 y += PAN_THUMB_SIZE + PAN_THUMB_GAP;
1980 x1 = pi_day->x + pi_day->width - 8;
1983 y2 = pbox->y + MIN(42, pbox->height);
1985 y3 = MAX(pbox->y, y2 - 30);
1986 util_clip_triangle(x1, y1, x2, y2, x3, y3,
1989 pi = pan_item_new_tri(pw, NULL, x, y, w, h,
1990 x1, y1, x2, y2, x3, y3,
1991 PAN_CAL_POPUP_COLOR, PAN_CAL_POPUP_ALPHA);
1992 pan_item_tri_border(pi, BORDER_1 | BORDER_3, PAN_CAL_POPUP_BORDER_COLOR, PAN_CAL_POPUP_ALPHA);
1993 pan_item_set_key(pi, "day_bubble");
1994 pan_item_added(pw, pi);
1996 pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
1997 pan_item_added(pw, pbox);
2000 static void pan_window_layout_compute_calendar(PanWindow *pw, const gchar *path, gint *width, gint *height)
2016 list = pan_window_layout_list(path, SORT_NONE, TRUE, pw->ignore_symlinks);
2018 if (pw->cache_list && SORT_BY_EXIF_DATE)
2020 pw->cache_list = filelist_sort(pw->cache_list, SORT_NAME, TRUE);
2021 list = filelist_sort(list, SORT_NAME, TRUE);
2022 pan_cache_sync_date(pw, list);
2025 pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE);
2026 list = filelist_sort(list, SORT_TIME, TRUE);
2039 if (!date_compare(fd->date, tc, DATE_LENGTH_DAY))
2047 if (day_max < count) day_max = count;
2051 printf("biggest day contains %d images\n", day_max);
2053 grid = (gint)(sqrt((double)day_max) + 0.5) * (PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2 + PAN_THUMB_GAP);
2054 day_width = MAX(PAN_CAL_DAY_WIDTH, grid);
2055 day_height = MAX(PAN_CAL_DAY_HEIGHT, grid);
2059 FileData *fd = list->data;
2061 year = date_value(fd->date, DATE_LENGTH_YEAR);
2062 month = date_value(fd->date, DATE_LENGTH_MONTH);
2065 work = g_list_last(list);
2068 FileData *fd = work->data;
2069 end_year = date_value(fd->date, DATE_LENGTH_YEAR);
2070 end_month = date_value(fd->date, DATE_LENGTH_MONTH);
2073 *width = PAN_FOLDER_BOX_BORDER * 2;
2074 *height = PAN_FOLDER_BOX_BORDER * 2;
2076 x = PAN_FOLDER_BOX_BORDER;
2077 y = PAN_FOLDER_BOX_BORDER;
2080 while (work && (year < end_year || (year == end_year && month <= end_month)))
2091 dt = date_to_time((month == 12) ? year + 1 : year, (month == 12) ? 1 : month + 1, 1);
2093 days = date_value(dt, DATE_LENGTH_DAY);
2094 dt = date_to_time(year, month, 1);
2095 col = date_value(dt, DATE_LENGTH_WEEK);
2098 x = PAN_FOLDER_BOX_BORDER;
2100 pi_month = pan_item_new_box(pw, NULL, x, y, PAN_CAL_DAY_WIDTH * 7, PAN_CAL_DAY_HEIGHT / 4,
2101 PAN_CAL_MONTH_BORDER,
2102 PAN_CAL_MONTH_COLOR, PAN_CAL_MONTH_ALPHA,
2103 PAN_CAL_MONTH_BORDER_COLOR, PAN_CAL_MONTH_ALPHA);
2104 buf = date_value_string(dt, DATE_LENGTH_MONTH);
2105 pi_text = pan_item_new_text(pw, x, y, buf,
2106 TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
2107 PAN_CAL_MONTH_TEXT_COLOR, 255);
2109 pi_text->x = pi_month->x + (pi_month->width - pi_text->width) / 2;
2111 pi_month->height = pi_text->y + pi_text->height - pi_month->y;
2113 x = PAN_FOLDER_BOX_BORDER + col * PAN_CAL_DAY_WIDTH;
2114 y = pi_month->y + pi_month->height + PAN_FOLDER_BOX_BORDER;
2116 for (day = 1; day <= days; day++)
2123 dt = date_to_time(year, month, day);
2125 fd = g_new0(FileData, 1);
2126 /* path and name must be non NULL, so make them an invalid filename */
2127 fd->path = g_strdup("//");
2130 pi_day = pan_item_new_box(pw, fd, x, y, PAN_CAL_DAY_WIDTH, PAN_CAL_DAY_HEIGHT,
2132 PAN_CAL_DAY_COLOR, PAN_CAL_DAY_ALPHA,
2133 PAN_CAL_DAY_BORDER_COLOR, PAN_CAL_DAY_ALPHA);
2134 pan_item_set_key(pi_day, "day");
2136 dx = x + PAN_CAL_DOT_GAP * 2;
2137 dy = y + PAN_CAL_DOT_GAP * 2;
2139 fd = (work) ? work->data : NULL;
2140 while (fd && date_compare(fd->date, dt, DATE_LENGTH_DAY))
2144 pi = pan_item_new_box(pw, fd, dx, dy, PAN_CAL_DOT_SIZE, PAN_CAL_DOT_SIZE,
2146 PAN_CAL_DOT_COLOR, PAN_CAL_DOT_ALPHA,
2148 pan_item_set_key(pi, "dot");
2150 dx += PAN_CAL_DOT_SIZE + PAN_CAL_DOT_GAP;
2151 if (dx + PAN_CAL_DOT_SIZE > pi_day->x + pi_day->width - PAN_CAL_DOT_GAP * 2)
2153 dx = x + PAN_CAL_DOT_GAP * 2;
2154 dy += PAN_CAL_DOT_SIZE + PAN_CAL_DOT_GAP;
2156 if (dy + PAN_CAL_DOT_SIZE > pi_day->y + pi_day->height - PAN_CAL_DOT_GAP * 2)
2158 /* must keep all dots within respective day even if it gets ugly */
2159 dy = y + PAN_CAL_DOT_GAP * 2;
2165 fd = (work) ? work->data : NULL;
2172 pi_day->color_r = MAX(pi_day->color_r - 61 - n * 3, 80);
2173 pi_day->color_g = pi_day->color_r;
2175 buf = g_strdup_printf("( %d )", n);
2176 pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE,
2177 PAN_CAL_DAY_TEXT_COLOR, 255);
2180 pi->x = pi_day->x + (pi_day->width - pi->width) / 2;
2181 pi->y = pi_day->y + (pi_day->height - pi->height) / 2;
2184 buf = g_strdup_printf("%d", day);
2185 pan_item_new_text(pw, x + 4, y + 4, buf, TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
2186 PAN_CAL_DAY_TEXT_COLOR, 255);
2190 pan_item_size_coordinates(pi_day, PAN_FOLDER_BOX_BORDER, width, height);
2197 x = PAN_FOLDER_BOX_BORDER;
2198 y += PAN_CAL_DAY_HEIGHT;
2202 x += PAN_CAL_DAY_WIDTH;
2206 if (col > 0) y += PAN_CAL_DAY_HEIGHT;
2207 y += PAN_FOLDER_BOX_BORDER * 2;
2218 *height = MAX(*height, grid + PAN_FOLDER_BOX_BORDER * 2 * 2);
2223 static void pan_window_layout_compute_timeline(PanWindow *pw, const gchar *path, gint *width, gint *height)
2231 PanItem *pi_month = NULL;
2232 PanItem *pi_day = NULL;
2238 list = pan_window_layout_list(path, SORT_NONE, TRUE, pw->ignore_symlinks);
2240 if (pw->cache_list && SORT_BY_EXIF_DATE)
2242 pw->cache_list = filelist_sort(pw->cache_list, SORT_NAME, TRUE);
2243 list = filelist_sort(list, SORT_NAME, TRUE);
2244 pan_cache_sync_date(pw, list);
2247 pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE);
2248 list = filelist_sort(list, SORT_TIME, TRUE);
2250 *width = PAN_FOLDER_BOX_BORDER * 2;
2251 *height = PAN_FOLDER_BOX_BORDER * 2;
2256 day_start = month_start;
2271 if (!date_compare(fd->date, tc, DATE_LENGTH_DAY))
2276 if (!date_compare(fd->date, tc, DATE_LENGTH_MONTH))
2282 x = pi_month->x + pi_month->width + PAN_FOLDER_BOX_BORDER;
2286 x = PAN_FOLDER_BOX_BORDER;
2289 y = PAN_FOLDER_BOX_BORDER;
2291 buf = date_value_string(fd->date, DATE_LENGTH_MONTH);
2292 pi = pan_item_new_text(pw, x, y, buf,
2293 TEXT_ATTR_BOLD | TEXT_ATTR_HEADING,
2294 PAN_TEXT_COLOR, 255);
2298 pi_month = pan_item_new_box(pw, file_data_new_simple(fd->path),
2300 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
2301 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
2302 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
2304 x += PAN_FOLDER_BOX_BORDER;
2305 y += PAN_FOLDER_BOX_BORDER;
2309 if (pi_day) x = pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER;
2321 if (date_compare(nfd->date, tc, DATE_LENGTH_DAY))
2323 needle = needle->next;
2332 buf = date_value_string(fd->date, DATE_LENGTH_WEEK);
2333 pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE,
2334 PAN_TEXT_COLOR, 255);
2339 pi_day = pan_item_new_box(pw, file_data_new_simple(fd->path), x, y, 0, 0,
2340 PAN_FOLDER_BOX_OUTLINE_THICKNESS,
2341 PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA,
2342 PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA);
2344 x += PAN_FOLDER_BOX_BORDER;
2345 y += PAN_FOLDER_BOX_BORDER;
2349 if (pw->size > LAYOUT_SIZE_THUMB_LARGE)
2351 pi = pan_item_new_image(pw, fd, x, y, 10, 10);
2352 if (pi->width > x_width) x_width = pi->width;
2353 y_height = pi->height;
2357 pi = pan_item_new_thumb(pw, fd, x, y);
2358 x_width = PAN_THUMB_SIZE;
2359 y_height = PAN_THUMB_SIZE;
2362 pan_item_size_by_item(pi_day, pi, PAN_FOLDER_BOX_BORDER);
2363 pan_item_size_by_item(pi_month, pi_day, PAN_FOLDER_BOX_BORDER);
2368 if (total > 0 && count < PAN_GROUP_MAX)
2370 y += y_height + PAN_THUMB_GAP;
2374 x += x_width + PAN_THUMB_GAP;
2384 pan_item_size_coordinates(pi_month, PAN_FOLDER_BOX_BORDER, width, height);
2390 static void pan_window_layout_compute(PanWindow *pw, const gchar *path,
2391 gint *width, gint *height,
2392 gint *scroll_x, gint *scroll_y)
2394 pan_window_items_free(pw);
2398 case LAYOUT_SIZE_THUMB_DOTS:
2399 pw->thumb_size = PAN_THUMB_SIZE_DOTS;
2400 pw->thumb_gap = PAN_THUMB_GAP_DOTS;
2402 case LAYOUT_SIZE_THUMB_NONE:
2403 pw->thumb_size = PAN_THUMB_SIZE_NONE;
2404 pw->thumb_gap = PAN_THUMB_GAP_SMALL;
2406 case LAYOUT_SIZE_THUMB_SMALL:
2407 pw->thumb_size = PAN_THUMB_SIZE_SMALL;
2408 pw->thumb_gap = PAN_THUMB_GAP_SMALL;
2410 case LAYOUT_SIZE_THUMB_NORMAL:
2412 pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
2413 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2415 case LAYOUT_SIZE_THUMB_LARGE:
2416 pw->thumb_size = PAN_THUMB_SIZE_LARGE;
2417 pw->thumb_gap = PAN_THUMB_GAP_LARGE;
2419 case LAYOUT_SIZE_10:
2420 pw->image_size = 10;
2421 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2423 case LAYOUT_SIZE_25:
2424 pw->image_size = 25;
2425 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
2427 case LAYOUT_SIZE_33:
2428 pw->image_size = 33;
2429 pw->thumb_gap = PAN_THUMB_GAP_LARGE;
2431 case LAYOUT_SIZE_50:
2432 pw->image_size = 50;
2433 pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2435 case LAYOUT_SIZE_100:
2436 pw->image_size = 100;
2437 pw->thumb_gap = PAN_THUMB_GAP_HUGE;
2450 pan_window_layout_compute_grid(pw, path, width, height);
2452 case LAYOUT_FOLDERS_LINEAR:
2453 pan_window_layout_compute_folders_linear(pw, path, width, height);
2455 case LAYOUT_FOLDERS_FLOWER:
2456 pan_window_layout_compute_folders_flower(pw, path, width, height, scroll_x, scroll_y);
2458 case LAYOUT_CALENDAR:
2459 pan_window_layout_compute_calendar(pw, path, width, height);
2461 case LAYOUT_TIMELINE:
2462 pan_window_layout_compute_timeline(pw, path, width, height);
2468 printf("computed %d objects\n", g_list_length(pw->list));
2471 static GList *pan_layout_intersect_l(GList *list, GList *item_list,
2472 gint x, gint y, gint width, gint height)
2480 gint rx, ry, rw, rh;
2485 if (util_clip_region(x, y, width, height,
2486 pi->x, pi->y, pi->width, pi->height,
2487 &rx, &ry, &rw, &rh))
2489 list = g_list_prepend(list, pi);
2496 static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height)
2502 grid = pw->list_grid;
2508 if (x < pg->x || x + width > pg->x + pg->w ||
2509 y < pg->y || y + height > pg->y + pg->h)
2515 list = pan_layout_intersect_l(list, pw->list, x, y, width, height);
2519 list = pan_layout_intersect_l(list, pg->list, x, y, width, height);
2523 list = pan_layout_intersect_l(list, pw->list_static, x, y, width, height);
2530 *-----------------------------------------------------------------------------
2532 *-----------------------------------------------------------------------------
2535 static gint pan_layout_queue_step(PanWindow *pw);
2538 static void pan_layout_queue_thumb_done_cb(ThumbLoader *tl, gpointer data)
2540 PanWindow *pw = data;
2548 pw->queue_pi = NULL;
2552 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2553 pi->pixbuf = thumb_loader_get_pixbuf(tl, TRUE);
2556 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2560 thumb_loader_free(pw->tl);
2563 while (pan_layout_queue_step(pw));
2566 static void pan_layout_queue_image_done_cb(ImageLoader *il, gpointer data)
2568 PanWindow *pw = data;
2576 pw->queue_pi = NULL;
2580 if (pi->pixbuf) g_object_unref(pi->pixbuf);
2581 pi->pixbuf = image_loader_get_pixbuf(pw->il);
2582 if (pi->pixbuf) g_object_ref(pi->pixbuf);
2584 if (pi->pixbuf && pw->size != LAYOUT_SIZE_100 &&
2585 (gdk_pixbuf_get_width(pi->pixbuf) > pi->width ||
2586 gdk_pixbuf_get_height(pi->pixbuf) > pi->height))
2591 pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height,
2592 (GdkInterpType)zoom_quality);
2593 g_object_unref(tmp);
2597 image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height);
2601 image_loader_free(pw->il);
2604 while (pan_layout_queue_step(pw));
2608 static void pan_layout_queue_image_area_cb(ImageLoader *il, guint x, guint y,
2609 guint width, guint height, gpointer data)
2611 PanWindow *pw = data;
2622 pi->pixbuf = image_loader_get_pixbuf(pw->il);
2623 if (pi->pixbuf) g_object_ref(pi->pixbuf);
2627 image_area_changed(pw->imd, pi->x + x, pi->y + y, width, height);
2633 static gint pan_layout_queue_step(PanWindow *pw)
2637 if (!pw->queue) return FALSE;
2639 pi = pw->queue->data;
2640 pw->queue = g_list_remove(pw->queue, pi);
2643 if (!pw->queue_pi->fd)
2645 pw->queue_pi->queued = FALSE;
2646 pw->queue_pi = NULL;
2650 image_loader_free(pw->il);
2652 thumb_loader_free(pw->tl);
2655 if (pi->type == ITEM_IMAGE)
2657 pw->il = image_loader_new(pi->fd->path);
2659 if (pw->size != LAYOUT_SIZE_100)
2661 image_loader_set_requested_size(pw->il, pi->width, pi->height);
2665 image_loader_set_area_ready_func(pw->il, pan_layout_queue_image_area_cb, pw);
2667 image_loader_set_error_func(pw->il, pan_layout_queue_image_done_cb, pw);
2669 if (image_loader_start(pw->il, pan_layout_queue_image_done_cb, pw)) return FALSE;
2671 image_loader_free(pw->il);
2674 else if (pi->type == ITEM_THUMB)
2676 pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE);
2678 if (!pw->tl->standard_loader)
2680 /* The classic loader will recreate a thumbnail any time we
2681 * request a different size than what exists. This view will
2682 * almost never use the user configured sizes so disable cache.
2684 thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE);
2687 thumb_loader_set_callbacks(pw->tl,
2688 pan_layout_queue_thumb_done_cb,
2689 pan_layout_queue_thumb_done_cb,
2692 if (thumb_loader_start(pw->tl, pi->fd->path)) return FALSE;
2694 thumb_loader_free(pw->tl);
2698 pw->queue_pi->queued = FALSE;
2699 pw->queue_pi = NULL;
2703 static void pan_layout_queue(PanWindow *pw, PanItem *pi)
2705 if (!pi || pi->queued || pi->pixbuf) return;
2706 if (pw->size <= LAYOUT_SIZE_THUMB_NONE) return;
2709 pw->queue = g_list_prepend(pw->queue, pi);
2711 if (!pw->tl && !pw->il) while(pan_layout_queue_step(pw));
2714 static gint pan_window_request_tile_cb(PixbufRenderer *pr, gint x, gint y,
2715 gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
2717 PanWindow *pw = data;
2722 pixbuf_set_rect_fill(pixbuf,
2723 0, 0, width, height,
2724 PAN_BACKGROUND_COLOR, 255);
2726 for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE)
2728 gint rx, ry, rw, rh;
2730 if (util_clip_region(x, y, width, height,
2732 &rx, &ry, &rw, &rh))
2734 pixbuf_draw_rect_fill(pixbuf,
2735 rx - x, ry - y, rw, rh,
2736 PAN_GRID_COLOR, PAN_GRID_ALPHA);
2739 for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE)
2741 gint rx, ry, rw, rh;
2743 if (util_clip_region(x, y, width, height,
2745 &rx, &ry, &rw, &rh))
2747 pixbuf_draw_rect_fill(pixbuf,
2748 rx - x, ry - y, rw, rh,
2749 PAN_GRID_COLOR, PAN_GRID_ALPHA);
2753 list = pan_layout_intersect(pw, x, y, width, height);
2758 gint tx, ty, tw, th;
2759 gint rx, ry, rw, rh;
2766 if (pi->type == ITEM_THUMB && pi->pixbuf)
2768 tw = gdk_pixbuf_get_width(pi->pixbuf);
2769 th = gdk_pixbuf_get_height(pi->pixbuf);
2771 tx = pi->x + (pi->width - tw) / 2;
2772 ty = pi->y + (pi->height - th) / 2;
2774 if (gdk_pixbuf_get_has_alpha(pi->pixbuf))
2776 if (util_clip_region(x, y, width, height,
2777 tx + PAN_SHADOW_OFFSET, ty + PAN_SHADOW_OFFSET, tw, th,
2778 &rx, &ry, &rw, &rh))
2780 pixbuf_draw_shadow(pixbuf,
2781 rx - x, ry - y, rw, rh,
2782 tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2784 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2789 if (util_clip_region(x, y, width, height,
2790 tx + tw, ty + PAN_SHADOW_OFFSET,
2791 PAN_SHADOW_OFFSET, th - PAN_SHADOW_OFFSET,
2792 &rx, &ry, &rw, &rh))
2794 pixbuf_draw_shadow(pixbuf,
2795 rx - x, ry - y, rw, rh,
2796 tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2798 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2800 if (util_clip_region(x, y, width, height,
2801 tx + PAN_SHADOW_OFFSET, ty + th, tw, PAN_SHADOW_OFFSET,
2802 &rx, &ry, &rw, &rh))
2804 pixbuf_draw_shadow(pixbuf,
2805 rx - x, ry - y, rw, rh,
2806 tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th,
2808 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2812 if (util_clip_region(x, y, width, height,
2814 &rx, &ry, &rw, &rh))
2816 gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2819 1.0, 1.0, GDK_INTERP_NEAREST,
2823 if (util_clip_region(x, y, width, height,
2824 tx, ty, tw, PAN_OUTLINE_THICKNESS,
2825 &rx, &ry, &rw, &rh))
2827 pixbuf_draw_rect_fill(pixbuf,
2828 rx - x, ry - y, rw, rh,
2829 PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2831 if (util_clip_region(x, y, width, height,
2832 tx, ty, PAN_OUTLINE_THICKNESS, th,
2833 &rx, &ry, &rw, &rh))
2835 pixbuf_draw_rect_fill(pixbuf,
2836 rx - x, ry - y, rw, rh,
2837 PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA);
2839 if (util_clip_region(x, y, width, height,
2840 tx + tw - PAN_OUTLINE_THICKNESS, ty + PAN_OUTLINE_THICKNESS,
2841 PAN_OUTLINE_THICKNESS, th - PAN_OUTLINE_THICKNESS,
2842 &rx, &ry, &rw, &rh))
2844 pixbuf_draw_rect_fill(pixbuf,
2845 rx - x, ry - y, rw, rh,
2846 PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2848 if (util_clip_region(x, y, width, height,
2849 tx + PAN_OUTLINE_THICKNESS, ty + th - PAN_OUTLINE_THICKNESS,
2850 tw - PAN_OUTLINE_THICKNESS * 2, PAN_OUTLINE_THICKNESS,
2851 &rx, &ry, &rw, &rh))
2853 pixbuf_draw_rect_fill(pixbuf,
2854 rx - x, ry - y, rw, rh,
2855 PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA);
2858 else if (pi->type == ITEM_THUMB)
2860 tw = pi->width - PAN_SHADOW_OFFSET * 2;
2861 th = pi->height - PAN_SHADOW_OFFSET * 2;
2862 tx = pi->x + PAN_SHADOW_OFFSET;
2863 ty = pi->y + PAN_SHADOW_OFFSET;
2865 if (util_clip_region(x, y, width, height,
2867 &rx, &ry, &rw, &rh))
2871 d = (pw->size <= LAYOUT_SIZE_THUMB_NONE) ? 2 : 8;
2872 pixbuf_draw_rect_fill(pixbuf,
2873 rx - x, ry - y, rw, rh,
2875 PAN_SHADOW_ALPHA / d);
2878 pan_layout_queue(pw, pi);
2880 else if (pi->type == ITEM_IMAGE)
2882 if (util_clip_region(x, y, width, height,
2883 pi->x, pi->y, pi->width, pi->height,
2884 &rx, &ry, &rw, &rh))
2888 gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh,
2891 1.0, 1.0, GDK_INTERP_NEAREST,
2896 pixbuf_draw_rect_fill(pixbuf,
2897 rx - x, ry - y, rw, rh,
2898 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA / 2);
2899 pan_layout_queue(pw, pi);
2903 else if (pi->type == ITEM_BOX)
2917 if (pi->color_a > 254)
2919 pixbuf_draw_shadow(pixbuf, pi->x - x + bw, pi->y - y + shadow[0],
2920 shadow[0], bh - shadow[0],
2921 pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2923 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2924 pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + bh,
2926 pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2928 PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA);
2933 a = pi->color_a * PAN_SHADOW_ALPHA >> 8;
2934 pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + shadow[0],
2936 pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh,
2938 PAN_SHADOW_COLOR, a);
2942 if (util_clip_region(x, y, width, height,
2943 pi->x, pi->y, bw, bh,
2944 &rx, &ry, &rw, &rh))
2946 pixbuf_draw_rect_fill(pixbuf,
2947 rx - x, ry - y, rw, rh,
2948 pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2950 if (util_clip_region(x, y, width, height,
2951 pi->x, pi->y, bw, pi->border,
2952 &rx, &ry, &rw, &rh))
2954 pixbuf_draw_rect_fill(pixbuf,
2955 rx - x, ry - y, rw, rh,
2956 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2958 if (util_clip_region(x, y, width, height,
2959 pi->x, pi->y + pi->border, pi->border, bh - pi->border * 2,
2960 &rx, &ry, &rw, &rh))
2962 pixbuf_draw_rect_fill(pixbuf,
2963 rx - x, ry - y, rw, rh,
2964 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2966 if (util_clip_region(x, y, width, height,
2967 pi->x + bw - pi->border, pi->y + pi->border,
2968 pi->border, bh - pi->border * 2,
2969 &rx, &ry, &rw, &rh))
2971 pixbuf_draw_rect_fill(pixbuf,
2972 rx - x, ry - y, rw, rh,
2973 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2975 if (util_clip_region(x, y, width, height,
2976 pi->x, pi->y + bh - pi->border,
2978 &rx, &ry, &rw, &rh))
2980 pixbuf_draw_rect_fill(pixbuf,
2981 rx - x, ry - y, rw, rh,
2982 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
2985 else if (pi->type == ITEM_TRIANGLE)
2987 if (util_clip_region(x, y, width, height,
2988 pi->x, pi->y, pi->width, pi->height,
2989 &rx, &ry, &rw, &rh) && pi->data)
2991 gint *coord = pi->data;
2992 pixbuf_draw_triangle(pixbuf,
2993 rx - x, ry - y, rw, rh,
2994 coord[0] - x, coord[1] - y,
2995 coord[2] - x, coord[3] - y,
2996 coord[4] - x, coord[5] - y,
2997 pi->color_r, pi->color_g, pi->color_b, pi->color_a);
2999 if (pi->border & BORDER_1)
3001 pixbuf_draw_line(pixbuf,
3002 rx - x, ry - y, rw, rh,
3003 coord[0] - x, coord[1] - y,
3004 coord[2] - x, coord[3] - y,
3005 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
3007 if (pi->border & BORDER_2)
3009 pixbuf_draw_line(pixbuf,
3010 rx - x, ry - y, rw, rh,
3011 coord[2] - x, coord[3] - y,
3012 coord[4] - x, coord[5] - y,
3013 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
3015 if (pi->border & BORDER_3)
3017 pixbuf_draw_line(pixbuf,
3018 rx - x, ry - y, rw, rh,
3019 coord[4] - x, coord[5] - y,
3020 coord[0] - x, coord[1] - y,
3021 pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a);
3025 else if (pi->type == ITEM_TEXT && pi->text)
3027 PangoLayout *layout;
3029 layout = pan_item_text_layout(pi, (GtkWidget *)pr);
3030 pixbuf_draw_layout(pixbuf, layout, (GtkWidget *)pr,
3031 pi->x - x + PAN_TEXT_BORDER_SIZE, pi->y - y + PAN_TEXT_BORDER_SIZE,
3032 pi->color_r, pi->color_g, pi->color_b, pi->color_a);
3033 g_object_unref(G_OBJECT(layout));
3039 if (x%512 == 0 && y%512 == 0)
3041 PangoLayout *layout;
3044 layout = gtk_widget_create_pango_layout((GtkWidget *)pr, NULL);
3046 buf = g_strdup_printf("%d,%d\n(#%d)", x, y,
3047 (x / pr->source_tile_width) +
3048 (y / pr->source_tile_height * (pr->image_width/pr->source_tile_width + 1)));
3049 pango_layout_set_text(layout, buf, -1);
3052 pixbuf_draw_layout(pixbuf, layout, (GtkWidget *)pr, 0, 0, 0, 0, 0, 255);
3054 g_object_unref(G_OBJECT(layout));
3061 static void pan_window_dispose_tile_cb(PixbufRenderer *pr, gint x, gint y,
3062 gint width, gint height, GdkPixbuf *pixbuf, gpointer data)
3064 PanWindow *pw = data;
3068 list = pan_layout_intersect(pw, x, y, width, height);
3077 if (pi->refcount > 0)
3081 if ((pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE) &&
3086 pw->queue = g_list_remove(pw->queue, pi);
3089 if (pw->queue_pi == pi) pw->queue_pi = NULL;
3092 g_object_unref(pi->pixbuf);
3104 *-----------------------------------------------------------------------------
3106 *-----------------------------------------------------------------------------
3109 static void pan_window_message(PanWindow *pw, const gchar *text)
3119 gtk_label_set_text(GTK_LABEL(pw->label_message), text);
3123 work = pw->list_static;
3124 if (pw->layout == LAYOUT_CALENDAR)
3134 pi->type == ITEM_BOX &&
3135 pi->key && strcmp(pi->key, "dot") == 0)
3137 size += pi->fd->size;
3152 (pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE))
3154 size += pi->fd->size;
3160 ss = text_from_size_abrev(size);
3161 buf = g_strdup_printf(_("%d images, %s"), count, ss);
3163 gtk_label_set_text(GTK_LABEL(pw->label_message), buf);
3167 static void pan_warning_folder(const gchar *path, GtkWidget *parent)
3171 message = g_strdup_printf(_("The pan view does not support the folder \"%s\"."), path);
3172 warning_dialog(_("Folder not supported"), message,
3173 GTK_STOCK_DIALOG_INFO, parent);
3177 static void pan_window_zoom_limit(PanWindow *pw)
3183 case LAYOUT_SIZE_THUMB_DOTS:
3184 case LAYOUT_SIZE_THUMB_NONE:
3185 case LAYOUT_SIZE_THUMB_SMALL:
3186 case LAYOUT_SIZE_THUMB_NORMAL:
3188 /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */
3192 case LAYOUT_SIZE_THUMB_LARGE:
3195 case LAYOUT_SIZE_10:
3196 case LAYOUT_SIZE_25:
3199 case LAYOUT_SIZE_33:
3200 case LAYOUT_SIZE_50:
3201 case LAYOUT_SIZE_100:
3207 image_zoom_set_limits(pw->imd, min, 32.0);
3210 static gint pan_window_layout_update_idle_cb(gpointer data)
3212 PanWindow *pw = data;
3218 if (pw->size > LAYOUT_SIZE_THUMB_LARGE ||
3219 (SORT_BY_EXIF_DATE && (pw->layout == LAYOUT_TIMELINE || pw->layout == LAYOUT_CALENDAR)))
3221 if (!pw->cache_list && !pw->cache_todo)
3223 pan_cache_fill(pw, pw->path);
3226 pan_window_message(pw, _("Reading dimensions..."));
3234 if (pw->cache_count == pw->cache_total)
3236 pan_window_message(pw, _("Sorting images..."));
3238 else if (pw->cache_tick > 9)
3242 buf = g_strdup_printf("%s %d", _("Reading dimensions..."),
3243 pw->cache_total - pw->cache_count);
3244 pan_window_message(pw, buf);
3250 if (pan_cache_step(pw)) return TRUE;
3257 pan_window_layout_compute(pw, pw->path, &width, &height, &scroll_x, &scroll_y);
3259 pan_window_zoom_limit(pw);
3261 if (width > 0 && height > 0)
3265 printf("Canvas size is %d x %d\n", width, height);
3267 pan_grid_build(pw, width, height, 1000);
3269 pixbuf_renderer_set_tiles(PIXBUF_RENDERER(pw->imd->pr), width, height,
3270 PAN_TILE_SIZE, PAN_TILE_SIZE, 10,
3271 pan_window_request_tile_cb,
3272 pan_window_dispose_tile_cb, pw, 1.0);
3274 if (scroll_x == 0 && scroll_y == 0)
3282 pixbuf_renderer_scroll_to_point(PIXBUF_RENDERER(pw->imd->pr), scroll_x, scroll_y, align, align);
3285 pan_window_message(pw, NULL);
3291 static void pan_window_layout_update_idle(PanWindow *pw)
3293 if (pw->idle_id == -1)
3295 pw->idle_id = g_idle_add(pan_window_layout_update_idle_cb, pw);
3299 static void pan_window_layout_update(PanWindow *pw)
3301 pan_window_message(pw, _("Sorting images..."));
3302 pan_window_layout_update_idle(pw);
3305 static void pan_window_layout_set_path(PanWindow *pw, const gchar *path)
3309 if (strcmp(path, "/") == 0)
3311 pan_warning_folder(path, pw->window);
3316 pw->path = g_strdup(path);
3318 pan_window_layout_update(pw);
3322 *-----------------------------------------------------------------------------
3323 * pan window keyboard
3324 *-----------------------------------------------------------------------------
3327 static const gchar *pan_menu_click_path(PanWindow *pw)
3329 if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd->path;
3333 static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data)
3335 PanWindow *pw = data;
3337 gdk_window_get_origin(pw->imd->pr->window, x, y);
3338 popup_menu_position_clamp(menu, x, y, 0);
3341 static gint pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
3343 PanWindow *pw = data;
3346 gint stop_signal = FALSE;
3352 pr = PIXBUF_RENDERER(pw->imd->pr);
3353 path = pan_menu_click_path(pw);
3355 focused = (pw->fs || GTK_WIDGET_HAS_FOCUS(GTK_WIDGET(pw->imd->widget)));
3359 switch (event->keyval)
3361 case GDK_Left: case GDK_KP_Left:
3365 case GDK_Right: case GDK_KP_Right:
3369 case GDK_Up: case GDK_KP_Up:
3373 case GDK_Down: case GDK_KP_Down:
3377 case GDK_Page_Up: case GDK_KP_Page_Up:
3378 pixbuf_renderer_scroll(pr, 0, 0 - pr->vis_height / 2);
3380 case GDK_Page_Down: case GDK_KP_Page_Down:
3381 pixbuf_renderer_scroll(pr, 0, pr->vis_height / 2);
3383 case GDK_Home: case GDK_KP_Home:
3384 pixbuf_renderer_scroll(pr, 0 - pr->vis_width / 2, 0);
3386 case GDK_End: case GDK_KP_End:
3387 pixbuf_renderer_scroll(pr, pr->vis_width / 2, 0);
3392 if (focused && !(event->state & GDK_CONTROL_MASK) )
3393 switch (event->keyval)
3395 case '+': case '=': case GDK_KP_Add:
3396 pixbuf_renderer_zoom_adjust(pr, ZOOM_INCREMENT);
3398 case '-': case GDK_KP_Subtract:
3399 pixbuf_renderer_zoom_adjust(pr, -ZOOM_INCREMENT);
3401 case 'Z': case 'z': case GDK_KP_Divide: case '1':
3402 pixbuf_renderer_zoom_set(pr, 1.0);
3405 pixbuf_renderer_zoom_set(pr, 2.0);
3408 pixbuf_renderer_zoom_set(pr, 3.0);
3411 pixbuf_renderer_zoom_set(pr, 4.0);
3414 pixbuf_renderer_zoom_set(pr, -4.0);
3417 pixbuf_renderer_zoom_set(pr, -3.0);
3420 pixbuf_renderer_zoom_set(pr, -2.0);
3424 pan_fullscreen_toggle(pw, FALSE);
3429 pan_overlay_toggle(pw);
3432 case GDK_Delete: case GDK_KP_Delete:
3437 if (GTK_WIDGET_VISIBLE(pw->search_box))
3439 gtk_widget_grab_focus(pw->search_entry);
3443 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE);
3451 pan_fullscreen_toggle(pw, TRUE);
3454 else if (GTK_WIDGET_VISIBLE(pw->search_entry))
3456 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3462 menu = pan_popup_menu(pw);
3463 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME);
3468 if (event->state & GDK_CONTROL_MASK)
3471 switch (event->keyval)
3504 if (path) file_util_copy(path, NULL, NULL, GTK_WIDGET(pr));
3507 if (path) file_util_move(path, NULL, NULL, GTK_WIDGET(pr));
3510 if (path) file_util_rename(path, NULL, GTK_WIDGET(pr));
3513 if (path) file_util_delete(path, NULL, GTK_WIDGET(pr));
3516 if (path) info_window_new(path, NULL);
3519 pan_window_close(pw);
3522 if (n != -1 && path)
3524 pan_fullscreen_toggle(pw, TRUE);
3525 start_editor_from_file(n, path);
3529 else if (event->state & GDK_SHIFT_MASK)
3536 switch (event->keyval)
3541 pan_fullscreen_toggle(pw, TRUE);
3544 else if (GTK_WIDGET_HAS_FOCUS(pw->search_entry))
3546 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
3547 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE);
3556 if (x != 0 || y!= 0)
3558 keyboard_scroll_calc(&x, &y, event);
3559 pixbuf_renderer_scroll(pr, x, y);
3566 *-----------------------------------------------------------------------------
3568 *-----------------------------------------------------------------------------
3571 static void pan_info_update(PanWindow *pw, PanItem *pi)
3577 gint x1, y1, x2, y2, x3, y3;
3580 if (pw->click_pi == pi) return;
3581 if (pi && !pi->fd) pi = NULL;
3583 while ((p = pan_item_find_by_key(pw, ITEM_NONE, "info"))) pan_item_remove(pw, p);
3588 if (debug) printf("info set to %s\n", pi->fd->path);
3590 pbox = pan_item_new_box(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10,
3592 PAN_POPUP_COLOR, PAN_POPUP_ALPHA,
3593 PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3594 pan_item_set_key(pbox, "info");
3596 if (pi->type == ITEM_THUMB && pi->pixbuf)
3598 w = gdk_pixbuf_get_width(pi->pixbuf);
3599 h = gdk_pixbuf_get_height(pi->pixbuf);
3601 x1 = pi->x + pi->width - (pi->width - w) / 2 - 8;
3602 y1 = pi->y + (pi->height - h) / 2 + 8;
3606 x1 = pi->x + pi->width - 8;
3614 util_clip_triangle(x1, y1, x2, y2, x3, y3,
3617 p = pan_item_new_tri(pw, NULL, x, y, w, h,
3618 x1, y1, x2, y2, x3, y3,
3619 PAN_POPUP_COLOR, PAN_POPUP_ALPHA);
3620 pan_item_tri_border(p, BORDER_1 | BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA);
3621 pan_item_set_key(p, "info");
3622 pan_item_added(pw, p);
3624 plabel = pan_item_new_text(pw, pbox->x, pbox->y,
3625 _("Filename:"), TEXT_ATTR_BOLD,
3626 PAN_POPUP_TEXT_COLOR, 255);
3627 pan_item_set_key(plabel, "info");
3628 p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3629 pi->fd->name, TEXT_ATTR_NONE,
3630 PAN_POPUP_TEXT_COLOR, 255);
3631 pan_item_set_key(p, "info");
3632 pan_item_size_by_item(pbox, p, 0);
3634 plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3635 _("Date:"), TEXT_ATTR_BOLD,
3636 PAN_POPUP_TEXT_COLOR, 255);
3637 pan_item_set_key(plabel, "info");
3638 p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3639 text_from_time(pi->fd->date), TEXT_ATTR_NONE,
3640 PAN_POPUP_TEXT_COLOR, 255);
3641 pan_item_set_key(p, "info");
3642 pan_item_size_by_item(pbox, p, 0);
3644 plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height,
3645 _("Size:"), TEXT_ATTR_BOLD,
3646 PAN_POPUP_TEXT_COLOR, 255);
3647 pan_item_set_key(plabel, "info");
3648 buf = text_from_size(pi->fd->size);
3649 p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y,
3650 buf, TEXT_ATTR_NONE,
3651 PAN_POPUP_TEXT_COLOR, 255);
3653 pan_item_set_key(p, "info");
3654 pan_item_size_by_item(pbox, p, 0);
3656 pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2);
3657 pan_item_added(pw, pbox);
3662 *-----------------------------------------------------------------------------
3664 *-----------------------------------------------------------------------------
3667 static void pan_search_status(PanWindow *pw, const gchar *text)
3669 gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : "");
3672 static gint pan_search_by_path(PanWindow *pw, const gchar *path)
3680 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3682 list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
3683 if (!list) return FALSE;
3685 found = g_list_find(list, pw->click_pi);
3686 if (found && found->next)
3688 found = found->next;
3696 pan_info_update(pw, pi);
3697 image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3699 buf = g_strdup_printf("%s ( %d / %d )",
3700 (path[0] == '/') ? _("path found") : _("filename found"),
3701 g_list_index(list, pi) + 1,
3702 g_list_length(list));
3703 pan_search_status(pw, buf);
3711 static gint pan_search_by_partial(PanWindow *pw, const gchar *text)
3719 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3721 list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
3722 if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
3727 needle = g_utf8_strdown(text, -1);
3728 list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
3731 if (!list) return FALSE;
3733 found = g_list_find(list, pw->click_pi);
3734 if (found && found->next)
3736 found = found->next;
3744 pan_info_update(pw, pi);
3745 image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);
3747 buf = g_strdup_printf("%s ( %d / %d )",
3749 g_list_index(list, pi) + 1,
3750 g_list_length(list));
3751 pan_search_status(pw, buf);
3759 static gint valid_date_separator(gchar c)
3761 return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
3764 static GList *pan_search_by_date_val(PanWindow *pw, ItemType type,
3765 gint year, gint month, gint day,
3771 work = g_list_last(pw->list_static);
3779 if (pi->fd && (pi->type == type || type == ITEM_NONE) &&
3780 ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
3784 tl = localtime(&pi->fd->date);
3789 match = (tl->tm_year == year - 1900);
3790 if (match && month >= 0) match = (tl->tm_mon == month - 1);
3791 if (match && day > 0) match = (tl->tm_mday == day);
3793 if (match) list = g_list_prepend(list, pi);
3798 return g_list_reverse(list);
3801 static gint pan_search_by_date(PanWindow *pw, const gchar *text)
3817 if (!text) return FALSE;
3819 ptr = (gchar *)text;
3820 while (*ptr != '\0')
3822 if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
3827 if (t == -1) return FALSE;
3829 if (!lt) return FALSE;
3831 if (valid_date_separator(*text))
3834 mptr = (gchar *)text;
3838 year = (gint)strtol(text, &mptr, 10);
3839 if (mptr == text) return FALSE;
3842 if (*mptr != '\0' && valid_date_separator(*mptr))
3847 month = strtol(mptr, &dptr, 10);
3850 if (valid_date_separator(*dptr))
3852 month = lt->tm_mon + 1;
3860 if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
3864 day = strtol(dptr, &eptr, 10);
3874 year = lt->tm_year + 1900;
3876 else if (year < 100)
3885 month < -1 || month == 0 || month > 12 ||
3886 day < -1 || day == 0 || day > 31) return FALSE;
3888 t = date_to_time(year, month, day);
3889 if (t < 0) return FALSE;
3891 if (pw->layout == LAYOUT_CALENDAR)
3893 list = pan_search_by_date_val(pw, ITEM_BOX, year, month, day, "day");
3899 type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB;
3900 list = pan_search_by_date_val(pw, type, year, month, day, NULL);
3905 found = g_list_find(list, pw->search_pi);
3906 if (found && found->next)
3908 found = found->next;
3919 if (pw->layout == LAYOUT_CALENDAR && pi && pi->type == ITEM_BOX)
3921 pan_info_update(pw, NULL);
3922 pan_calendar_update(pw, pi);
3923 image_scroll_to_point(pw->imd,
3924 pi->x + pi->width / 2,
3925 pi->y + pi->height / 2, 0.5, 0.5);
3929 pan_info_update(pw, pi);
3930 image_scroll_to_point(pw->imd,
3931 pi->x - PAN_FOLDER_BOX_BORDER * 5 / 2,
3937 buf = date_value_string(t, DATE_LENGTH_MONTH);
3942 buf = g_strdup_printf("%d %s", day, tmp);
3948 buf = date_value_string(t, DATE_LENGTH_YEAR);
3953 buf_count = g_strdup_printf("( %d / %d )",
3954 g_list_index(list, pi) + 1,
3955 g_list_length(list));
3959 buf_count = g_strdup_printf("(%s)", _("no match"));
3962 message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
3965 pan_search_status(pw, message);
3973 static void pan_search_activate_cb(const gchar *text, gpointer data)
3975 PanWindow *pw = data;
3979 tab_completion_append_to_history(pw->search_entry, text);
3981 if (pan_search_by_path(pw, text)) return;
3983 if ((pw->layout == LAYOUT_TIMELINE ||
3984 pw->layout == LAYOUT_CALENDAR) &&
3985 pan_search_by_date(pw, text))
3990 if (pan_search_by_partial(pw, text)) return;
3992 pan_search_status(pw, _("no match"));
3995 static void pan_search_toggle_cb(GtkWidget *button, gpointer data)
3997 PanWindow *pw = data;
4000 visible = GTK_WIDGET_VISIBLE(pw->search_box);
4001 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;
4005 gtk_widget_hide(pw->search_box);
4006 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
4010 gtk_widget_show(pw->search_box);
4011 gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
4012 gtk_widget_grab_focus(pw->search_entry);
4018 *-----------------------------------------------------------------------------
4019 * view window main routines
4020 *-----------------------------------------------------------------------------
4023 static void button_cb(PixbufRenderer *pr, GdkEventButton *event, gpointer data)
4025 PanWindow *pw = data;
4033 rx = (double)(pr->x_scroll + event->x - pr->x_offset) / pr->scale;
4034 ry = (double)(pr->y_scroll + event->y - pr->y_offset) / pr->scale;
4037 pi = pan_item_find_by_coord(pw, (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB,
4040 switch (event->button)
4043 pan_info_update(pw, pi);
4045 if (!pi && pw->layout == LAYOUT_CALENDAR)
4047 pi = pan_item_find_by_coord(pw, ITEM_BOX, rx, ry, "day");
4048 pan_calendar_update(pw, pi);
4054 pan_info_update(pw, pi);
4055 menu = pan_popup_menu(pw);
4056 gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, event->time);
4063 static void scroll_cb(PixbufRenderer *pr, GdkEventScroll *event, gpointer data)
4066 PanWindow *pw = data;
4073 if (!(event->state & GDK_SHIFT_MASK))
4079 if (event->state & GDK_CONTROL_MASK)
4081 switch (event->direction)
4084 pixbuf_renderer_zoom_adjust_at_point(pr, ZOOM_INCREMENT,
4085 (gint)event->x, (gint)event->y);
4087 case GDK_SCROLL_DOWN:
4088 pixbuf_renderer_zoom_adjust_at_point(pr, -ZOOM_INCREMENT,
4089 (gint)event->x, (gint)event->y);
4097 switch (event->direction)
4100 pixbuf_renderer_scroll(pr, 0, -h);
4102 case GDK_SCROLL_DOWN:
4103 pixbuf_renderer_scroll(pr, 0, h);
4105 case GDK_SCROLL_LEFT:
4106 pixbuf_renderer_scroll(pr, -w, 0);
4108 case GDK_SCROLL_RIGHT:
4109 pixbuf_renderer_scroll(pr, w, 0);
4117 static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd)
4119 g_signal_connect(G_OBJECT(imd->pr), "clicked",
4120 G_CALLBACK(button_cb), pw);
4121 g_signal_connect(G_OBJECT(imd->pr), "scroll_event",
4122 G_CALLBACK(scroll_cb), pw);
4125 static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data)
4127 PanWindow *pw = data;
4130 pw->imd = pw->imd_normal;
4133 static void pan_fullscreen_toggle(PanWindow *pw, gint force_off)
4135 if (force_off && !pw->fs) return;
4139 fullscreen_stop(pw->fs);
4143 pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw);
4144 pan_image_set_buttons(pw, pw->fs->imd);
4145 g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event",
4146 G_CALLBACK(pan_window_key_press_cb), pw);
4148 pw->imd = pw->fs->imd;
4152 static void pan_window_image_zoom_cb(PixbufRenderer *pr, gdouble zoom, gpointer data)
4154 PanWindow *pw = data;
4157 text = image_zoom_get_as_text(pw->imd);
4158 gtk_label_set_text(GTK_LABEL(pw->label_zoom), text);
4162 static void pan_window_image_scroll_notify_cb(PixbufRenderer *pr, gpointer data)
4164 PanWindow *pw = data;
4169 if (pr->scale == 0.0) return;
4171 pixbuf_renderer_get_visible_rect(pr, &rect);
4172 pixbuf_renderer_get_image_size(pr, &width, &height);
4174 adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h));
4175 adj->page_size = (gdouble)rect.width;
4176 adj->page_increment = adj->page_size / 2.0;
4177 adj->step_increment = 48.0 / pr->scale;
4179 adj->upper = MAX((gdouble)width, 1.0);
4180 adj->value = (gdouble)rect.x;
4182 pref_signal_block_data(pw->scrollbar_h, pw);
4183 gtk_adjustment_changed(adj);
4184 gtk_adjustment_value_changed(adj);
4185 pref_signal_unblock_data(pw->scrollbar_h, pw);
4187 adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v));
4188 adj->page_size = (gdouble)rect.height;
4189 adj->page_increment = adj->page_size / 2.0;
4190 adj->step_increment = 48.0 / pr->scale;
4192 adj->upper = MAX((gdouble)height, 1.0);
4193 adj->value = (gdouble)rect.y;
4195 pref_signal_block_data(pw->scrollbar_v, pw);
4196 gtk_adjustment_changed(adj);
4197 gtk_adjustment_value_changed(adj);
4198 pref_signal_unblock_data(pw->scrollbar_v, pw);
4201 static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data)
4203 PanWindow *pw = data;
4207 pr = PIXBUF_RENDERER(pw->imd_normal->pr);
4209 if (!pr->scale) return;
4211 x = (gint)gtk_range_get_value(range);
4213 pixbuf_renderer_scroll_to_point(pr, x, (gint)((gdouble)pr->y_scroll / pr->scale), 0.0, 0.0);
4216 static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data)
4218 PanWindow *pw = data;
4222 pr = PIXBUF_RENDERER(pw->imd_normal->pr);
4224 if (!pr->scale) return;
4226 y = (gint)gtk_range_get_value(range);
4228 pixbuf_renderer_scroll_to_point(pr, (gint)((gdouble)pr->x_scroll / pr->scale), y, 0.0, 0.0);
4231 static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data)
4233 PanWindow *pw = data;
4235 pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
4236 pan_window_layout_update(pw);
4239 static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data)
4241 PanWindow *pw = data;
4243 pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
4244 pan_window_layout_update(pw);
4247 static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data)
4249 PanWindow *pw = data;
4252 path = remove_trailing_slash(new_text);
4253 parse_out_relatives(path);
4257 warning_dialog(_("Folder not found"),
4258 _("The entered path is not a folder"),
4259 GTK_STOCK_DIALOG_WARNING, pw->path_entry);
4263 tab_completion_append_to_history(pw->path_entry, path);
4265 pan_window_layout_set_path(pw, path);
4271 static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data)
4273 PanWindow *pw = data;
4276 if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return;
4278 text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry)));
4279 pan_window_entry_activate_cb(text, pw);
4283 static void pan_window_close(PanWindow *pw)
4285 pan_window_list = g_list_remove(pan_window_list, pw);
4287 if (pw->idle_id != -1)
4289 g_source_remove(pw->idle_id);
4292 pan_fullscreen_toggle(pw, TRUE);
4293 gtk_widget_destroy(pw->window);
4295 pan_window_items_free(pw);
4303 static gint pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data)
4305 PanWindow *pw = data;
4307 pan_window_close(pw);
4311 static void pan_window_new_real(const gchar *path)
4320 GdkGeometry geometry;
4322 pw = g_new0(PanWindow, 1);
4324 pw->path = g_strdup(path);
4325 pw->layout = LAYOUT_TIMELINE;
4326 pw->size = LAYOUT_SIZE_THUMB_NORMAL;
4327 pw->thumb_size = PAN_THUMB_SIZE_NORMAL;
4328 pw->thumb_gap = PAN_THUMB_GAP_NORMAL;
4330 pw->ignore_symlinks = TRUE;
4333 pw->list_static = NULL;
4334 pw->list_grid = NULL;
4337 pw->overlay_id = -1;
4340 pw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4342 geometry.min_width = 8;
4343 geometry.min_height = 8;
4344 gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE);
4346 gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE);
4347 gtk_window_set_title (GTK_WINDOW(pw->window), "Pan View - GQview");
4348 gtk_window_set_wmclass(GTK_WINDOW(pw->window), "view", "GQview");
4349 gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0);
4351 window_set_icon(pw->window, NULL, NULL);
4353 vbox = gtk_vbox_new(FALSE, 0);
4354 gtk_container_add(GTK_CONTAINER(pw->window), vbox);
4355 gtk_widget_show(vbox);
4357 box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
4359 pref_spacer(box, 0);
4360 pref_label_new(box, _("Location:"));
4361 combo = tab_completion_new_with_history(&pw->path_entry, path, "pan_view", -1,
4362 pan_window_entry_activate_cb, pw);
4363 g_signal_connect(G_OBJECT(pw->path_entry->parent), "changed",
4364 G_CALLBACK(pan_window_entry_change_cb), pw);
4365 gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0);
4366 gtk_widget_show(combo);
4368 combo = gtk_combo_box_new_text();
4369 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Timeline"));
4370 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Calendar"));
4371 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders"));
4372 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders (flower)"));
4373 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Grid"));
4375 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout);
4376 g_signal_connect(G_OBJECT(combo), "changed",
4377 G_CALLBACK(pan_window_layout_change_cb), pw);
4378 gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
4379 gtk_widget_show(combo);
4381 combo = gtk_combo_box_new_text();
4382 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Dots"));
4383 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("No Images"));
4384 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Small Thumbnails"));
4385 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Normal Thumbnails"));
4386 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Large Thumbnails"));
4387 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:10 (10%)"));
4388 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:4 (25%)"));
4389 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:3 (33%)"));
4390 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:2 (50%)"));
4391 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:1 (100%)"));
4393 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size);
4394 g_signal_connect(G_OBJECT(combo), "changed",
4395 G_CALLBACK(pan_window_layout_size_cb), pw);
4396 gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0);
4397 gtk_widget_show(combo);
4399 table = pref_table_new(vbox, 2, 2, FALSE, TRUE);
4400 gtk_table_set_row_spacings(GTK_TABLE(table), 2);
4401 gtk_table_set_col_spacings(GTK_TABLE(table), 2);
4403 pw->imd = image_new(TRUE);
4404 pw->imd_normal = pw->imd;
4406 g_signal_connect(G_OBJECT(pw->imd->pr), "zoom",
4407 G_CALLBACK(pan_window_image_zoom_cb), pw);
4408 g_signal_connect(G_OBJECT(pw->imd->pr), "scroll_notify",
4409 G_CALLBACK(pan_window_image_scroll_notify_cb), pw);
4411 gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1,
4412 GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0);
4413 gtk_widget_show(GTK_WIDGET(pw->imd->widget));
4415 pan_window_dnd_init(pw);
4417 pan_image_set_buttons(pw, pw->imd);
4419 pw->scrollbar_h = gtk_hscrollbar_new(NULL);
4420 g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed",
4421 G_CALLBACK(pan_window_scrollbar_h_value_cb), pw);
4422 gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2,
4423 GTK_FILL | GTK_EXPAND, 0, 0, 0);
4424 gtk_widget_show(pw->scrollbar_h);
4426 pw->scrollbar_v = gtk_vscrollbar_new(NULL);
4427 g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed",
4428 G_CALLBACK(pan_window_scrollbar_v_value_cb), pw);
4429 gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1,
4430 0, GTK_FILL | GTK_EXPAND, 0, 0);
4431 gtk_widget_show(pw->scrollbar_v);
4435 pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
4436 gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2);
4438 pref_spacer(pw->search_box, 0);
4439 pref_label_new(pw->search_box, _("Find:"));
4441 hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
4442 gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0);
4443 gtk_widget_show(hbox);
4445 combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1,
4446 pan_search_activate_cb, pw);
4447 gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
4448 gtk_widget_show(combo);
4450 pw->search_label = gtk_label_new("");
4451 gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0);
4452 gtk_widget_show(pw->search_label);
4456 box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4458 frame = gtk_frame_new(NULL);
4459 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
4460 gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
4461 gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0);
4462 gtk_widget_show(frame);
4464 hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
4465 gtk_container_add(GTK_CONTAINER(frame), hbox);
4466 gtk_widget_show(hbox);
4468 pref_spacer(hbox, 0);
4469 pw->label_message = pref_label_new(hbox, "");
4471 frame = gtk_frame_new(NULL);
4472 gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
4473 gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1);
4474 gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0);
4475 gtk_widget_show(frame);
4477 pw->label_zoom = gtk_label_new("");
4478 gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom);
4479 gtk_widget_show(pw->label_zoom);
4481 pw->search_button = gtk_toggle_button_new();
4482 gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE);
4483 gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE);
4484 hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
4485 gtk_container_add(GTK_CONTAINER(pw->search_button), hbox);
4486 gtk_widget_show(hbox);
4487 pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
4488 gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0);
4489 gtk_widget_show(pw->search_button_arrow);
4490 pref_label_new(hbox, _("Find"));
4492 gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0);
4493 gtk_widget_show(pw->search_button);
4494 g_signal_connect(G_OBJECT(pw->search_button), "clicked",
4495 G_CALLBACK(pan_search_toggle_cb), pw);
4497 g_signal_connect(G_OBJECT(pw->window), "delete_event",
4498 G_CALLBACK(pan_window_delete_cb), pw);
4499 g_signal_connect(G_OBJECT(pw->window), "key_press_event",
4500 G_CALLBACK(pan_window_key_press_cb), pw);
4502 gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT);
4504 pan_window_layout_update(pw);
4506 gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
4507 gtk_widget_show(pw->window);
4509 pan_window_list = g_list_append(pan_window_list, pw);
4513 *-----------------------------------------------------------------------------
4514 * peformance warnings
4515 *-----------------------------------------------------------------------------
4518 static void pan_warning_ok_cb(GenericDialog *gd, gpointer data)
4522 generic_dialog_close(gd);
4524 pan_window_new_real(path);
4528 static void pan_warning_hide_cb(GtkWidget *button, gpointer data)
4532 hide_dlg = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
4533 pref_list_int_set(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, hide_dlg);
4536 static gint pan_warning(const gchar *path)
4542 GtkWidget *ct_button;
4545 if (path && strcmp(path, "/") == 0)
4547 pan_warning_folder(path, NULL);
4551 if (enable_thumb_caching &&
4552 thumbnail_spec_standard) return FALSE;
4554 if (!pref_list_int_get(PAN_PREF_GROUP, PAN_PREF_HIDE_WARNING, &hide_dlg)) hide_dlg = FALSE;
4555 if (hide_dlg) return FALSE;
4557 gd = generic_dialog_new(_("Pan View Performance"), "GQview", "pan_view_warning", NULL, FALSE,
4559 gd->data = g_strdup(path);
4560 generic_dialog_add_button(gd, GTK_STOCK_OK, NULL,
4561 pan_warning_ok_cb, TRUE);
4563 box = generic_dialog_add_message(gd, GTK_STOCK_DIALOG_INFO,
4564 _("Pan view performance may be poor."),
4565 _("To improve performance of thumbnails in the pan view the"
4566 " following options can be enabled. Note that both options"
4567 " must be enabled to notice a change in performance."));
4569 group = pref_box_new(box, FALSE, GTK_ORIENTATION_HORIZONTAL, 0);
4570 pref_spacer(group, PREF_PAD_INDENT);
4571 group = pref_box_new(group, TRUE, GTK_ORIENTATION_VERTICAL, PREF_PAD_GAP);
4573 ct_button = pref_checkbox_new_int(group, _("Cache thumbnails"),
4574 enable_thumb_caching, &enable_thumb_caching);
4575 button = pref_checkbox_new_int(group, _("Use shared thumbnail cache"),
4576 thumbnail_spec_standard, &thumbnail_spec_standard);
4577 pref_checkbox_link_sensitivity(ct_button, button);
4581 pref_checkbox_new(box, _("Do not show this dialog again"), hide_dlg,
4582 G_CALLBACK(pan_warning_hide_cb), NULL);
4584 gtk_widget_show(gd->dialog);
4591 *-----------------------------------------------------------------------------
4593 *-----------------------------------------------------------------------------
4596 void pan_window_new(const gchar *path)
4598 if (pan_warning(path)) return;
4600 pan_window_new_real(path);
4604 *-----------------------------------------------------------------------------
4606 *-----------------------------------------------------------------------------
4609 static void pan_new_window_cb(GtkWidget *widget, gpointer data)
4611 PanWindow *pw = data;
4614 path = pan_menu_click_path(pw);
4617 pan_fullscreen_toggle(pw, TRUE);
4618 view_window_new(path);
4622 static void pan_edit_cb(GtkWidget *widget, gpointer data)
4628 pw = submenu_item_get_data(widget);
4629 n = GPOINTER_TO_INT(data);
4632 path = pan_menu_click_path(pw);
4635 pan_fullscreen_toggle(pw, TRUE);
4636 start_editor_from_file(n, path);
4640 static void pan_info_cb(GtkWidget *widget, gpointer data)
4642 PanWindow *pw = data;
4645 path = pan_menu_click_path(pw);
4646 if (path) info_window_new(path, NULL);
4649 static void pan_zoom_in_cb(GtkWidget *widget, gpointer data)
4651 PanWindow *pw = data;
4653 image_zoom_adjust(pw->imd, ZOOM_INCREMENT);
4656 static void pan_zoom_out_cb(GtkWidget *widget, gpointer data)
4658 PanWindow *pw = data;
4660 image_zoom_adjust(pw->imd, -ZOOM_INCREMENT);
4663 static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data)
4665 PanWindow *pw = data;
4667 image_zoom_set(pw->imd, 1.0);
4670 static void pan_copy_cb(GtkWidget *widget, gpointer data)
4672 PanWindow *pw = data;
4675 path = pan_menu_click_path(pw);
4676 if (path) file_util_copy(path, NULL, NULL, pw->imd->widget);
4679 static void pan_move_cb(GtkWidget *widget, gpointer data)
4681 PanWindow *pw = data;
4684 path = pan_menu_click_path(pw);
4685 if (path) file_util_move(path, NULL, NULL, pw->imd->widget);
4688 static void pan_rename_cb(GtkWidget *widget, gpointer data)
4690 PanWindow *pw = data;
4693 path = pan_menu_click_path(pw);
4694 if (path) file_util_rename(path, NULL, pw->imd->widget);
4697 static void pan_delete_cb(GtkWidget *widget, gpointer data)
4699 PanWindow *pw = data;
4702 path = pan_menu_click_path(pw);
4703 if (path) file_util_delete(path, NULL, pw->imd->widget);
4706 static void pan_fullscreen_cb(GtkWidget *widget, gpointer data)
4708 PanWindow *pw = data;
4710 pan_fullscreen_toggle(pw, FALSE);
4713 static void pan_close_cb(GtkWidget *widget, gpointer data)
4715 PanWindow *pw = data;
4717 pan_window_close(pw);
4720 static GtkWidget *pan_popup_menu(PanWindow *pw)
4726 active = (pw->click_pi != NULL);
4728 menu = popup_menu_short_lived();
4730 menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN,
4731 G_CALLBACK(pan_zoom_in_cb), pw);
4732 menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT,
4733 G_CALLBACK(pan_zoom_out_cb), pw);
4734 menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100,
4735 G_CALLBACK(pan_zoom_1_1_cb), pw);
4736 menu_item_add_divider(menu);
4738 submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw);
4739 gtk_widget_set_sensitive(item, active);
4741 menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active,
4742 G_CALLBACK(pan_info_cb), pw);
4744 menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active,
4745 G_CALLBACK(pan_new_window_cb), pw);
4747 menu_item_add_divider(menu);
4748 menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active,
4749 G_CALLBACK(pan_copy_cb), pw);
4750 menu_item_add_sensitive(menu, _("_Move..."), active,
4751 G_CALLBACK(pan_move_cb), pw);
4752 menu_item_add_sensitive(menu, _("_Rename..."), active,
4753 G_CALLBACK(pan_rename_cb), pw);
4754 menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active,
4755 G_CALLBACK(pan_delete_cb), pw);
4757 menu_item_add_divider(menu);
4761 menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4765 menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw);
4768 menu_item_add_divider(menu);
4769 menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw);
4775 *-----------------------------------------------------------------------------
4777 *-----------------------------------------------------------------------------
4780 static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context,
4782 GtkSelectionData *selection_data, guint info,
4783 guint time, gpointer data)
4785 PanWindow *pw = data;
4787 if (gtk_drag_get_source_widget(context) == pw->imd->pr) return;
4789 if (info == TARGET_URI_LIST)
4793 list = uri_list_from_text(selection_data->data, TRUE);
4794 if (list && isdir((gchar *)list->data))
4796 gchar *path = list->data;
4798 pan_window_layout_set_path(pw, path);
4801 path_list_free(list);
4805 static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context,
4806 GtkSelectionData *selection_data, guint info,
4807 guint time, gpointer data)
4809 PanWindow *pw = data;
4812 path = pan_menu_click_path(pw);
4822 case TARGET_URI_LIST:
4825 case TARGET_TEXT_PLAIN:
4830 list = g_list_append(NULL, (gchar *)path);
4831 text = uri_text_from_list(list, &len, plain_text);
4835 gtk_selection_data_set (selection_data, selection_data->target,
4842 gtk_selection_data_set (selection_data, selection_data->target,
4847 static void pan_window_dnd_init(PanWindow *pw)
4851 widget = pw->imd->pr;
4853 gtk_drag_source_set(widget, GDK_BUTTON2_MASK,
4854 dnd_file_drag_types, dnd_file_drag_types_count,
4855 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4856 g_signal_connect(G_OBJECT(widget), "drag_data_get",
4857 G_CALLBACK(pan_window_set_dnd_data), pw);
4859 gtk_drag_dest_set(widget,
4860 GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP,
4861 dnd_file_drop_types, dnd_file_drop_types_count,
4862 GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK);
4863 g_signal_connect(G_OBJECT(widget), "drag_data_received",
4864 G_CALLBACK(pan_window_get_dnd_data), pw);
4868 *-----------------------------------------------------------------------------
4869 * maintenance (for rename, move, remove)
4870 *-----------------------------------------------------------------------------