From 2dc62aebe430351c100f05bf94753055dd4ac368 Mon Sep 17 00:00:00 2001 From: John Ellis Date: Tue, 1 Mar 2005 17:16:34 +0000 Subject: [PATCH] ##### Note: GQview CVS on sourceforge is not always up to date, please use ##### ##### an offical release when making enhancements and translation updates. ##### Tue Mar 1 11:32:26 2005 John Ellis * src/Makefile.am: Add pan-view.[ch]: * image.[ch]: Add support for using a grid of tiles as soource image. Added scroll_notify callback for when the viewable regionis scrolled. Added ability to set min and max for the zoom range. Removed unnecessary gtk_widget_size_request from image_size_sync. Added image_scroll_to_point. * layout_util.c: Add menu item and callback for the new 'Pan view'. * pixbuf_util.c (pixbuf_draw_layout): Fix for when offset is non-zero. * typedefs.h: Add source tile stuff for ImageWindow. * ui_tabcomp.c: Fix tab completion pop-up menu placement. * pan-view.[ch]: New files for the pan view - 2.1 is officially started :) --- ChangeLog | 13 + TODO | 37 +- configure.in | 2 +- src/Makefile.am | 2 + src/image.c | 635 ++++++- src/image.h | 14 + src/layout_util.c | 10 + src/pan-view.c | 4122 +++++++++++++++++++++++++++++++++++++++++++++ src/pan-view.h | 20 + src/pixbuf_util.c | 2 +- src/typedefs.h | 22 + src/ui_tabcomp.c | 27 +- 12 files changed, 4881 insertions(+), 25 deletions(-) create mode 100644 src/pan-view.c create mode 100644 src/pan-view.h diff --git a/ChangeLog b/ChangeLog index 6ef0461d..37842c04 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,16 @@ +Tue Mar 1 11:32:26 2005 John Ellis + + * src/Makefile.am: Add pan-view.[ch]: + * image.[ch]: Add support for using a grid of tiles as soource image. Added + scroll_notify callback for when the viewable regionis scrolled. Added ability + to set min and max for the zoom range. Removed unnecessary + gtk_widget_size_request from image_size_sync. Added image_scroll_to_point. + * layout_util.c: Add menu item and callback for the new 'Pan view'. + * pixbuf_util.c (pixbuf_draw_layout): Fix for when offset is non-zero. + * typedefs.h: Add source tile stuff for ImageWindow. + * ui_tabcomp.c: Fix tab completion pop-up menu placement. + * pan-view.[ch]: New files for the pan view - 2.1 is officially started :) + Sat Feb 26 14:42:42 2005 John Ellis * README: Updates. diff --git a/TODO b/TODO index 2f11da07..705612be 100644 --- a/TODO +++ b/TODO @@ -1,16 +1,43 @@ TODO key: d = done, w = work in progress, ? = possibly fixed * = required before next release -Major (release blockers): +Major: ---------------------------------------------- - must Fix for 2.0.0: - ------------------- - -d> update translations: bg(x) es(x) it(x) ja(x) nl(x) pl(x) ru(x) + > work on pan view: + > Pick a better keyboard shortcut than Control + J :) + > Add warning dialog that it will be slow if the standard thumbnail cache is not enabled. + > Fix occasional redraw bugs when zoomed out. + > Fix occasional odd requests for non-visible tiles when zoomed out (related to above?). + > Fix slowness in image.c with huge grid size by changing use of pre-allocated tile array + to on-demand tile allocation (can this be implemented like source tiles?). + OR: use an array so that we do not need to walk a GList of pre-allocated tile containers + > Fix search scrolling to try to center image and info popup. + > Fix info popup location to opposing horizontal side when near edge of grid. + > Find something to do with middle mouse clicks. + > Set drag and drop data to clicked image. + > Should the copy/move/rename/delete operations be available here? + > ^ and if so, should delete key actually work? + > searching for same item more than once should step through all matches + > search should highlight all matching images + > should non-thumbnail images have a drop shadow? + + > time line view: + > searching by date should scroll to proper alignment with year/month/day boundaries. + + > grid view: + > allow sorting by name, date, size, dimensions, etc. + + > flower view: + > fix it :) + + > the info dialog is not set as a transient of the calling window, this causes it to be behind + a full screen window when 'stay above other windows' is enabled. ------------- + > update translations: ( ) ( ) ( ) + > document recent additions/changes: (none currently) diff --git a/configure.in b/configure.in index 94b6c60c..0ff8c0a2 100644 --- a/configure.in +++ b/configure.in @@ -1,7 +1,7 @@ AC_INIT(src/main.c) AC_CONFIG_HEADER(config.h) -AM_INIT_AUTOMAKE(gqview, 2.0.0) +AM_INIT_AUTOMAKE(gqview, 2.1.0) AM_MAINTAINER_MODE diff --git a/src/Makefile.am b/src/Makefile.am index 23b8dc7e..d76963f2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -109,6 +109,8 @@ gqview_SOURCES = \ md5-util.h \ menu.c \ menu.h \ + pan-view.c \ + pan-view.h \ pixbuf_util.c \ pixbuf_util.h \ preferences.c \ diff --git a/src/image.c b/src/image.c index ae385056..35f94928 100644 --- a/src/image.c +++ b/src/image.c @@ -23,7 +23,7 @@ #include -#define IMAGE_TILE_SIZE 128 +#define IMAGE_TILE_SIZE 512 #define IMAGE_ZOOM_MIN -32.0 #define IMAGE_ZOOM_MAX 32.0 @@ -119,6 +119,468 @@ struct _OverlayData gint always; /* hide temporarily when scrolling */ }; +/* needed to be declared before the source_tile stuff */ +static void image_pixbuf_sync(ImageWindow *imd, gdouble zoom, gint blank, gint new); +static void image_zoom_sync(ImageWindow *imd, gdouble zoom, + gint force, gint blank, gint new, + gint center_point, gint px, gint py); +static void image_queue(ImageWindow *imd, gint x, gint y, gint w, gint h, + gint clamp, TileRenderType render, gint new_data); + +static gint util_clip_region(gint x, gint y, gint w, gint h, + gint clip_x, gint clip_y, gint clip_w, gint clip_h, + gint *rx, gint *ry, gint *rw, gint *rh); + +/* + *------------------------------------------------------------------- + * source tiles + *------------------------------------------------------------------- + */ + +typedef struct _SourceTile SourceTile; +struct _SourceTile +{ + gint x; + gint y; + GdkPixbuf *pixbuf; + gint blank; +}; + +static void source_tile_free(SourceTile *st) +{ + if (!st) return; + + if (st->pixbuf) gdk_pixbuf_unref(st->pixbuf); + g_free(st); +} + +static void source_tile_free_all(ImageWindow *imd) +{ + GList *work; + + work = imd->source_tiles; + while (work) + { + SourceTile *st = work->data; + work = work->next; + + source_tile_free(st); + } + + g_list_free(imd->source_tiles); + imd->source_tiles = NULL; +} + +static gint source_tile_visible(ImageWindow *imd, SourceTile *st) +{ + gint x, y, w, h; + + if (!st) return FALSE; + + x = (imd->x_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE; + y = (imd->y_scroll / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE; + w = ((imd->x_scroll + imd->vis_width) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE; + h = ((imd->y_scroll + imd->vis_height) / IMAGE_TILE_SIZE) * IMAGE_TILE_SIZE + IMAGE_TILE_SIZE; + + return !((double)st->x * imd->scale < (double)x || + (double)(st->x + imd->source_tile_width) * imd->scale > (double)w || + (double)st->y * imd->scale < (double)y || + (double)(st->y + imd->source_tile_height) * imd->scale > (double)h); +} + +static SourceTile *source_tile_new(ImageWindow *imd, gint x, gint y) +{ + SourceTile *st = NULL; + gint count; + + if (imd->source_tiles_cache_size < 4) imd->source_tiles_cache_size = 4; + + if (imd->source_tile_width < 1 || imd->source_tile_height < 1) + { + printf("warning: source tile size too small %d x %d\n", imd->source_tile_width, imd->source_tile_height); + return NULL; + } + + count = g_list_length(imd->source_tiles); + if (count >= imd->source_tiles_cache_size) + { + GList *work; + + work = g_list_last(imd->source_tiles); + while (work && count >= imd->source_tiles_cache_size) + { + SourceTile *needle; + + needle = work->data; + work = work->prev; + + if (!source_tile_visible(imd, needle)) + { + imd->source_tiles = g_list_remove(imd->source_tiles, needle); + + if (imd->func_tile_dispose) + { + if (debug) printf("tile dispose: %d x %d @ %d x %d\n", + needle->x, needle->y, imd->x_scroll, imd->y_scroll); + imd->func_tile_dispose(imd, needle->x, needle->y, + imd->source_tile_width, imd->source_tile_height, + needle->pixbuf, imd->data_tile); + } + + if (!st) + { + st = needle; + } + else + { + source_tile_free(needle); + } + + count--; + } + else if (debug) + { + printf("we still think %d x %d is visble\n", needle->x, needle->y); + } + } + + if (debug) + { + printf("cache count %d, max is %d\n", count, imd->source_tiles_cache_size); + } + } + + if (!st) + { + st = g_new0(SourceTile, 1); + st->pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, + imd->source_tile_width, imd->source_tile_height); + } + + st->x = (x / imd->source_tile_width) * imd->source_tile_width; + st->y = (y / imd->source_tile_height) * imd->source_tile_height; + st->blank = TRUE; + + imd->source_tiles = g_list_prepend(imd->source_tiles, st); + + if (debug) + { + printf("tile request: %d x %d\n", st->x, st->y); + if (!source_tile_visible(imd, st)) printf("tile request for invisible tile!\n"); + } + + return st; +} + +static void image_tile_invalidate(ImageWindow *imd, gint x, gint y, gint w, gint h) +{ + gint i, j; + gint x1, x2; + gint y1, y2; + GList *work; + + x1 = (gint)floor(x / imd->tile_width) * imd->tile_width; + x2 = (gint)ceil((x + w) / imd->tile_width) * imd->tile_width; + + y1 = (gint)floor(y / imd->tile_height) * imd->tile_height; + y2 = (gint)ceil((y + h) / imd->tile_height) * imd->tile_height; + + work = g_list_nth(imd->tiles, y1 / imd->tile_height * imd->tile_cols + (x1 / imd->tile_width)); + for (j = y1; j <= y2; j += imd->tile_height) + { + GList *tmp; + tmp = work; + for (i = x1; i <= x2; i += imd->tile_width) + { + if (tmp) + { + ImageTile *it = tmp->data; + + it->render_done = TILE_RENDER_NONE; + it->render_todo = TILE_RENDER_ALL; + + tmp = tmp->next; + } + } + work = g_list_nth(work, imd->tile_cols); /* step 1 row */ + } +} + +static SourceTile *source_tile_request(ImageWindow *imd, gint x, gint y) +{ + SourceTile *st; + + st = source_tile_new(imd, x, y); + + if (imd->func_tile_request && + imd->func_tile_request(imd, st->x, st->y, + imd->source_tile_width, imd->source_tile_height, st->pixbuf, imd->data_tile)) + { + st->blank = FALSE; + } +#if 0 + /* fixme: somehow invalidate the new st region */ + image_queue(imd, st->x, st->y, imd->source_tile_width, imd->source_tile_height, FALSE, TILE_RENDER_AREA, TRUE); +#endif + image_tile_invalidate(imd, st->x * imd->scale, st->y * imd->scale, + imd->source_tile_width * imd->scale, imd->source_tile_height * imd->scale); + + return st; +} + +static SourceTile *source_tile_find(ImageWindow *imd, gint x, gint y) +{ + GList *work; + + work = imd->source_tiles; + while (work) + { + SourceTile *st = work->data; + + if (x >= st->x && x < st->x + imd->source_tile_width && + y >= st->y && y < st->y + imd->source_tile_height) + { + if (work != imd->source_tiles) + { + imd->source_tiles = g_list_remove_link(imd->source_tiles, work); + imd->source_tiles = g_list_concat(work, imd->source_tiles); + } + return st; + } + + work = work->next; + } + + return NULL; +} + +static GList *source_tile_compute_region(ImageWindow *imd, gint x, gint y, gint w, gint h, gint request) +{ + gint x1, y1; + GList *list = NULL; + gint sx, sy; + + if (x < 0) x = 0; + if (y < 0) y = 0; + if (w > imd->image_width) w = imd->image_width; + if (h > imd->image_height) h = imd->image_height; + + sx = (x / imd->source_tile_width) * imd->source_tile_width; + sy = (y / imd->source_tile_height) * imd->source_tile_height; + + for (x1 = sx; x1 < x + w; x1+= imd->source_tile_width) + { + for (y1 = sy; y1 < y + h; y1 += imd->source_tile_height) + { + SourceTile *st; + + st = source_tile_find(imd, x1, y1); + if (!st && request) st = source_tile_request(imd, x1, y1); + + if (st) list = g_list_prepend(list, st); + } + } + + return g_list_reverse(list); +} + +static void source_tile_changed(ImageWindow *imd, gint x, gint y, gint width, gint height) +{ + GList *work; + + work = imd->source_tiles; + while (work) + { + SourceTile *st; + gint rx, ry, rw, rh; + + st = work->data; + work = work->next; + + if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height, + x, y, width, height, + &rx, &ry, &rw, &rh)) + { + GdkPixbuf *pixbuf; + + pixbuf = gdk_pixbuf_new_subpixbuf(st->pixbuf, rx - st->x, ry - st->y, rw, rh); + if (imd->func_tile_request && + imd->func_tile_request(imd, rx, ry, rw, rh, pixbuf, imd->data_tile)) + { + image_tile_invalidate(imd, rx * imd->scale, ry * imd->scale, rw * imd->scale, rh * imd->scale); + } + g_object_unref(pixbuf); + } + } +} + + +static gint source_tile_render(ImageWindow *imd, ImageTile *it, + gint x, gint y, gint w, gint h, + gint new_data, gint fast) +{ + GList *list; + GList *work; + gint draw = FALSE; + + if (imd->zoom == 1.0 || imd->scale == 1.0) + { + list = source_tile_compute_region(imd, it->x + x, it->y + y, w, h, TRUE); + work = list; + while (work) + { + SourceTile *st; + gint rx, ry, rw, rh; + + st = work->data; + work = work->next; + + if (util_clip_region(st->x, st->y, imd->source_tile_width, imd->source_tile_height, + it->x + x, it->y + y, w, h, + &rx, &ry, &rw, &rh)) + { + if (st->blank) + { + gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE, + rx - st->x, ry - st->y, rw, rh); + } + else /* (imd->zoom == 1.0 || imd->scale == 1.0) */ + { + gdk_draw_pixbuf(it->pixmap, + imd->image->style->fg_gc[GTK_WIDGET_STATE(imd->image)], + st->pixbuf, + rx - st->x, ry - st->y, + rx - it->x, ry - it->y, + rw, rh, + (GdkRgbDither)dither_quality, rx, ry); + } + } + } + } + else + { + double scale_x, scale_y; + gint sx, sy, sw, sh; + + if (imd->image_width == 0 || imd->image_height == 0) return FALSE; + scale_x = (double)imd->width / imd->image_width; + scale_y = (double)imd->height / imd->image_height; + + sx = (double)(it->x + x) / scale_x; + sy = (double)(it->y + y) / scale_y; + sw = (double)w / scale_x; + sh = (double)h / scale_y; + + if (imd->width < IMAGE_MIN_SCALE_SIZE || imd->height < IMAGE_MIN_SCALE_SIZE) fast = TRUE; + +#if 0 + /* draws red over draw region, to check for leaks (regions not filled) */ + pixbuf_draw_rect(it->pixbuf, x, y, w, h, 255, 0, 0, 255, FALSE); +#endif + + list = source_tile_compute_region(imd, sx, sy, sw, sh, TRUE); + work = list; + while (work) + { + SourceTile *st; + gint rx, ry, rw, rh; + gint stx, sty, stw, sth; + + st = work->data; + work = work->next; + + stx = floor((double)st->x * scale_x); + sty = floor((double)st->y * scale_y); + stw = ceil ((double)(st->x + imd->source_tile_width) * scale_x) - stx; + sth = ceil ((double)(st->y + imd->source_tile_height) * scale_y) - sty; + + if (util_clip_region(stx, sty, stw, sth, + it->x + x, it->y + y, w, h, + &rx, &ry, &rw, &rh)) + { + if (st->blank) + { + gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE, + rx - st->x, ry - st->y, rw, rh); + } + else + { + double offset_x; + double offset_y; + + /* may need to use unfloored stx,sty values here */ + offset_x = ((double)stx < (double)it->x) ? + (double)stx - (double)it->x : 0.0; + offset_y = ((double)sty < (double)it->y) ? + (double)sty - (double)it->y : 0.0; + + gdk_pixbuf_scale(st->pixbuf, it->pixbuf, rx - it->x, ry - it->y, rw, rh, + (double) 0.0 + rx - it->x + offset_x, + (double) 0.0 + ry - it->y + offset_y, + scale_x, scale_y, + (fast) ? GDK_INTERP_NEAREST : (GdkInterpType)zoom_quality); + draw = TRUE; + } + } + } + } + + g_list_free(list); + + return draw; +} + +static void image_source_tile_unset(ImageWindow *imd) +{ + source_tile_free_all(imd); + + imd->source_tiles_enabled = FALSE; +} + +void image_set_image_as_tiles(ImageWindow *imd, gint width, gint height, + gint tile_width, gint tile_height, gint cache_size, + ImageTileRequestFunc func_tile_request, + ImageTileDisposeFunc func_tile_dispose, + gpointer data, + gdouble zoom) +{ + /* FIXME: unset any current image */ + image_source_tile_unset(imd); + + if (tile_width < 32 || tile_height < 32) + { + printf("warning: tile size too small %d x %d (min 32x32)\n", tile_width, tile_height); + return; + } + if (width < 32 || height < 32) + { + printf("warning: tile canvas too small %d x %d (min 32x32)\n", width, height); + return; + } + if (!func_tile_request) + { + printf("warning: tile request function is null\n"); + } + + printf("Setting source tiles to size %d x %d, grid is %d x %d\n", tile_width, tile_height, width, height); + + if (cache_size < 4) cache_size = 4; + + imd->source_tiles_enabled = TRUE; + imd->source_tiles_cache_size = cache_size; + imd->source_tile_width = tile_width; + imd->source_tile_height = tile_height; + + imd->image_width = width; + imd->image_height = height; + + imd->func_tile_request = func_tile_request; + imd->func_tile_dispose = func_tile_dispose; + imd->data_tile = data; + + image_zoom_sync(imd, zoom, TRUE, FALSE, TRUE, FALSE, 0, 0); +} + static void image_queue_clear(ImageWindow *imd); @@ -210,9 +672,25 @@ static void image_tile_cache_free(ImageWindow *imd, CacheData *cd) static void image_tile_cache_free_space(ImageWindow *imd, gint space, ImageTile *it) { - GList *work = g_list_last(imd->tile_cache); + GList *work; + gint tile_max; + + work = g_list_last(imd->tile_cache); - while (work && imd->tile_cache_size > 0 && imd->tile_cache_size + space > tile_cache_max * 1048576) + if (imd->source_tiles_enabled && imd->scale < 1.0) + { + gint tiles; + + tiles = (imd->vis_width / IMAGE_TILE_SIZE + 1) * (imd->vis_width / IMAGE_TILE_SIZE + 1); + tile_max = MAX(tiles * IMAGE_TILE_SIZE * IMAGE_TILE_SIZE * 3, + (gint)((double)tile_cache_max * 1048576.0 * imd->scale)); + } + else + { + tile_max = tile_cache_max * 1048576; + } + + while (work && imd->tile_cache_size > 0 && imd->tile_cache_size + space > tile_max) { CacheData *cd = work->data; work = work->prev; @@ -252,16 +730,23 @@ static void image_tile_prepare(ImageWindow *imd, ImageTile *it) image_tile_cache_add(imd, it, pixmap, NULL, size); } - if ((imd->zoom != 1.0 || gdk_pixbuf_get_has_alpha(imd->pixbuf)) && + if ((imd->zoom != 1.0 || imd->source_tiles_enabled || (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf)) ) && !it->pixbuf) { GdkPixbuf *pixbuf; guint size; - pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(imd->pixbuf), - gdk_pixbuf_get_has_alpha(imd->pixbuf), - gdk_pixbuf_get_bits_per_sample(imd->pixbuf), - imd->tile_width, imd->tile_height); + if (imd->pixbuf) + { + pixbuf = gdk_pixbuf_new(gdk_pixbuf_get_colorspace(imd->pixbuf), + gdk_pixbuf_get_has_alpha(imd->pixbuf), + gdk_pixbuf_get_bits_per_sample(imd->pixbuf), + imd->tile_width, imd->tile_height); + } + else + { + pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, imd->tile_width, imd->tile_height); + } size = gdk_pixbuf_get_rowstride(pixbuf) * imd->tile_height; image_tile_cache_free_space(imd, size, it); @@ -396,7 +881,7 @@ static void image_tile_render(ImageWindow *imd, ImageTile *it, gint has_alpha; gint draw = FALSE; - if (it->render_todo == TILE_RENDER_NONE && it->pixmap) return; + if (it->render_todo == TILE_RENDER_NONE && it->pixmap && !new_data) return; if (it->render_done != TILE_RENDER_ALL) { @@ -417,7 +902,7 @@ static void image_tile_render(ImageWindow *imd, ImageTile *it, if (new_data) it->blank = FALSE; image_tile_prepare(imd, it); - has_alpha = gdk_pixbuf_get_has_alpha(imd->pixbuf); + has_alpha = (imd->pixbuf && gdk_pixbuf_get_has_alpha(imd->pixbuf)); /* FIXME checker colors for alpha should be configurable, * also should be drawn for blank = TRUE @@ -429,6 +914,10 @@ static void image_tile_render(ImageWindow *imd, ImageTile *it, gdk_draw_rectangle(it->pixmap, imd->image->style->black_gc, TRUE, 0, 0, it->w, it->h); } + else if (imd->source_tiles_enabled) + { + draw = source_tile_render(imd, it, x, y, w, h, new_data, fast); + } else if (imd->zoom == 1.0 || imd->scale == 1.0) { if (has_alpha) @@ -536,7 +1025,7 @@ static gint image_queue_draw_idle_cb(gpointer data) QueueData *qd; gint fast; - if (!imd->pixbuf || (!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1) + if ((!imd->pixbuf && !imd->source_tiles_enabled) || (!imd->draw_queue && !imd->draw_queue_2pass) || imd->draw_idle_id == -1) { if (!imd->completed) image_complete_util(imd, FALSE); @@ -862,7 +1351,7 @@ static void image_border_draw(ImageWindow *imd, gint x, gint y, gint w, gint h) if (!imd->image->window) return; - if (!imd->pixbuf) + if (!imd->pixbuf && !imd->source_tiles_enabled) { if (util_clip_region(x, y, w, h, 0, 0, @@ -924,6 +1413,19 @@ static void image_border_clear(ImageWindow *imd) image_border_draw(imd, 0, 0, imd->window_width, imd->window_height); } +static void image_scroll_notify(ImageWindow *imd) +{ + if (imd->func_scroll_notify && imd->scale) + { + imd->func_scroll_notify(imd, + (gint)((gdouble)imd->x_scroll / imd->scale), + (gint)((gdouble)imd->y_scroll / imd->scale), + (gint)((gdouble)imd->image_width - imd->vis_width / imd->scale), + (gint)((gdouble)imd->image_height - imd->vis_height / imd->scale), + imd->data_scroll_notify); + } +} + static gint image_scroll_clamp(ImageWindow *imd) { gint old_xs; @@ -933,6 +1435,8 @@ static gint image_scroll_clamp(ImageWindow *imd) { imd->x_scroll = 0; imd->y_scroll = 0; + + image_scroll_notify(imd); return FALSE; } @@ -957,6 +1461,8 @@ static gint image_scroll_clamp(ImageWindow *imd) imd->y_scroll = CLAMP(imd->y_scroll, 0, imd->height - imd->vis_height); } + image_scroll_notify(imd); + return (old_xs != imd->x_scroll || old_ys != imd->y_scroll); } @@ -965,7 +1471,7 @@ static gint image_zoom_clamp(ImageWindow *imd, gdouble zoom, gint force, gint ne gint w, h; gdouble scale; - zoom = CLAMP(zoom, IMAGE_ZOOM_MIN, IMAGE_ZOOM_MAX); + zoom = CLAMP(zoom, imd->zoom_min, imd->zoom_max); if (imd->zoom == zoom && !force) return FALSE; @@ -1090,7 +1596,9 @@ static void image_size_sync(ImageWindow *imd, gint new_width, gint new_height) image_size_clamp(imd); image_scroll_clamp(imd); +#if 0 gtk_widget_set_size_request(imd->image, imd->window_width, imd->window_height); +#endif /* ensure scroller remains visible */ if (imd->scroller_overlay != -1) @@ -1211,7 +1719,7 @@ static void image_scroll_real(ImageWindow *imd, gint x, gint y) gint x_off, y_off; gint w, h; - if (!imd->pixbuf) return; + if (!imd->pixbuf && !imd->source_tiles_enabled) return; old_x = imd->x_scroll; old_y = imd->y_scroll; @@ -1952,6 +2460,8 @@ static void image_change_complete(ImageWindow *imd, gdouble zoom, gint new) { gint sync = TRUE; + image_source_tile_unset(imd); + imd->zoom = zoom; /* store the zoom, needed by the loader */ image_reset(imd); @@ -2696,6 +3206,14 @@ void image_set_scroll_func(ImageWindow *imd, imd->data_scroll = data; } +void image_set_scroll_notify_func(ImageWindow *imd, + void (*func)(ImageWindow *imd, gint x, gint y, gint width, gint height, gpointer data), + gpointer data) +{ + imd->func_scroll_notify = func; + imd->data_scroll_notify = data; +} + /* path, name */ const gchar *image_get_path(ImageWindow *imd) @@ -2726,11 +3244,15 @@ void image_change_path(ImageWindow *imd, const gchar *path, gdouble zoom) if (imd->image_path == path || (path && imd->image_path && !strcmp(path, imd->image_path)) ) return; + image_source_tile_unset(imd); + image_change_real(imd, path, NULL, NULL, zoom); } void image_change_pixbuf(ImageWindow *imd, GdkPixbuf *pixbuf, gdouble zoom) { + image_source_tile_unset(imd); + image_set_pixbuf(imd, pixbuf, zoom, TRUE); image_new_util(imd); } @@ -2739,6 +3261,8 @@ void image_change_from_collection(ImageWindow *imd, CollectionData *cd, CollectI { if (!cd || !info || !g_list_find(cd->list, info)) return; + image_source_tile_unset(imd); + image_change_real(imd, info->path, cd, info, zoom); } @@ -2778,6 +3302,9 @@ void image_change_from_image(ImageWindow *imd, ImageWindow *source) { if (imd == source) return; + imd->zoom_min = source->zoom_min; + imd->zoom_max = source->zoom_max; + imd->unknown = source->unknown; image_set_pixbuf(imd, source->pixbuf, image_zoom_get(source), TRUE); @@ -2829,6 +3356,43 @@ void image_change_from_image(ImageWindow *imd, ImageWindow *source) imd->x_scroll = source->x_scroll; imd->y_scroll = source->y_scroll; + if (imd->source_tiles_enabled) + { + image_source_tile_unset(imd); + } + + if (source->source_tiles_enabled) + { + imd->source_tiles_enabled = source->source_tiles_enabled; + imd->source_tiles_cache_size = source->source_tiles_cache_size; + imd->source_tiles = source->source_tiles; + imd->source_tile_width = source->source_tile_width; + imd->source_tile_height = source->source_tile_height; + + source->source_tiles_enabled = FALSE; + source->source_tiles = NULL; + + imd->func_tile_request = source->func_tile_request; + imd->func_tile_dispose = source->func_tile_dispose; + imd->data_tile = source->data_tile; + + source->func_tile_request = NULL; + source->func_tile_dispose = NULL; + source->data_tile = NULL; + + imd->image_width = source->image_width; + imd->image_height = source->image_height; + + if (image_zoom_clamp(imd, source->zoom, TRUE, TRUE)) + { + image_size_clamp(imd); + image_scroll_clamp(imd); + image_tile_sync(imd, imd->width, imd->height, FALSE); + image_redraw(imd, FALSE); + } + return; + } + image_scroll_clamp(imd); } @@ -2843,11 +3407,18 @@ void image_area_changed(ImageWindow *imd, gint x, gint y, gint width, gint heigh sw = (gint)ceil((double)width * imd->scale); sh = (gint)ceil((double)height * imd->scale); + if (imd->source_tiles_enabled) + { + source_tile_changed(imd, x, y, width, height); + } + image_queue(imd, sx, sy, sw, sh, FALSE, TILE_RENDER_AREA, TRUE); } void image_reload(ImageWindow *imd) { + if (imd->source_tiles_enabled) return; + image_change_complete(imd, imd->zoom, FALSE); } @@ -2856,8 +3427,20 @@ void image_scroll(ImageWindow *imd, gint x, gint y) image_scroll_real(imd, x, y); } +void image_scroll_to_point(ImageWindow *imd, gint x, gint y) +{ + gint px, py; + + px = (gdouble)x * imd->scale - imd->x_scroll; + py = (gdouble)y * imd->scale - imd->y_scroll; + + image_scroll(imd, px, py); +} + void image_alter(ImageWindow *imd, AlterType type) { + if (imd->source_tiles_enabled) return; + if (imd->il) { /* still loading, wait till done */ @@ -2925,6 +3508,16 @@ void image_zoom_adjust_at_point(ImageWindow *imd, gdouble increment, gint x, gin image_zoom_adjust_real(imd, increment, TRUE, x, y); } +void image_zoom_set_limits(ImageWindow *imd, gdouble min, gdouble max) +{ + if (min > 1.0 || max < 1.0) return; + if (min < 1.0 && min > -1.0) return; + if (min < -200.0 || max > 200.0) return; + + imd->zoom_min = min; + imd->zoom_max = max; +} + void image_zoom_set(ImageWindow *imd, gdouble zoom) { image_zoom_sync(imd, zoom, FALSE, FALSE, FALSE, FALSE, 0, 0); @@ -3029,6 +3622,8 @@ gdouble image_zoom_get_default(ImageWindow *imd, gint mode) void image_prebuffer_set(ImageWindow *imd, const gchar *path) { + if (imd->source_tiles_enabled) return; + if (path) { image_read_ahead_set(imd, path); @@ -3063,6 +3658,7 @@ static gint image_auto_refresh_cb(gpointer data) void image_auto_refresh(ImageWindow *imd, gint interval) { if (!imd) return; + if (imd->source_tiles_enabled) return; if (imd->auto_refresh_id > -1) { @@ -3162,7 +3758,6 @@ void image_to_root_window(ImageWindow *imd, gint scaled) if (!imd || !imd->pixbuf) return; - screen = gtk_widget_get_screen(imd->image); rootwindow = gdk_screen_get_root_window(screen); if (gdk_drawable_get_visual(rootwindow) != gdk_visual_get_system()) return; @@ -3210,6 +3805,8 @@ static void image_free(ImageWindow *imd) image_overlay_list_clear(imd); + source_tile_free_all(imd); + g_free(imd); } @@ -3224,6 +3821,9 @@ ImageWindow *image_new(gint frame) ImageWindow *imd; imd = g_new0(ImageWindow, 1); + + imd->zoom_min = IMAGE_ZOOM_MIN; + imd->zoom_max = IMAGE_ZOOM_MAX; imd->zoom = 1.0; imd->scale = 1.0; @@ -3262,6 +3862,8 @@ ImageWindow *image_new(gint frame) imd->func_update = NULL; imd->func_complete = NULL; + imd->func_tile_request = NULL; + imd->func_tile_dispose = NULL; imd->func_button = NULL; imd->func_scroll = NULL; @@ -3269,6 +3871,9 @@ ImageWindow *image_new(gint frame) imd->scroller_id = -1; imd->scroller_overlay = -1; + imd->source_tiles_enabled = FALSE; + imd->source_tiles = NULL; + imd->image = gtk_drawing_area_new(); gtk_widget_set_double_buffered(imd->image, FALSE); diff --git a/src/image.h b/src/image.h index cb87f465..b5c03a2e 100644 --- a/src/image.h +++ b/src/image.h @@ -28,6 +28,9 @@ void image_set_button_func(ImageWindow *imd, void image_set_scroll_func(ImageWindow *imd, void (*func)(ImageWindow *, GdkScrollDirection direction, guint32 time, gdouble x, gdouble y, guint state, gpointer), gpointer data); +void image_set_scroll_notify_func(ImageWindow *imd, + void (*func)(ImageWindow *imd, gint x, gint y, gint width, gint height, gpointer data), + gpointer data); void image_set_complete_func(ImageWindow *imd, void (*func)(ImageWindow *, gint preload, gpointer), gpointer data); @@ -53,11 +56,13 @@ void image_change_from_image(ImageWindow *imd, ImageWindow *source); void image_area_changed(ImageWindow *imd, gint x, gint y, gint width, gint height); void image_reload(ImageWindow *imd); void image_scroll(ImageWindow *imd, gint x, gint y); +void image_scroll_to_point(ImageWindow *imd, gint x, gint y); void image_alter(ImageWindow *imd, AlterType type); /* zoom */ void image_zoom_adjust(ImageWindow *imd, gdouble increment); void image_zoom_adjust_at_point(ImageWindow *imd, gdouble increment, gint x, gint y); +void image_zoom_set_limits(ImageWindow *imd, gdouble min, gdouble max); void image_zoom_set(ImageWindow *imd, gdouble zoom); void image_zoom_set_fill_geometry(ImageWindow *imd, gint vertical); gdouble image_zoom_get(ImageWindow *imd); @@ -92,6 +97,15 @@ gint image_overlay_get(ImageWindow *imd, gint id, GdkPixbuf **pixbuf, gint *x, g void image_overlay_remove(ImageWindow *imd, gint id); + +void image_set_image_as_tiles(ImageWindow *imd, gint width, gint height, + gint tile_width, gint tile_height, gint cache_size, + ImageTileRequestFunc func_tile_request, + ImageTileDisposeFunc func_tile_dispose, + gpointer data, + gdouble zoom); + + #endif diff --git a/src/layout_util.c b/src/layout_util.c index 7dbb3e9e..99c97299 100644 --- a/src/layout_util.c +++ b/src/layout_util.c @@ -23,6 +23,7 @@ #include "editors.h" #include "info.h" #include "layout_image.h" +#include "pan-view.h" #include "pixbuf_util.h" #include "preferences.h" #include "print.h" @@ -289,6 +290,13 @@ static void layout_menu_dupes_cb(GtkAction *action, gpointer data) dupe_window_new(DUPE_MATCH_NAME); } +static void layout_menu_pan_cb(GtkAction *action, gpointer data) +{ + LayoutWindow *lw = data; + + pan_window_new(layout_get_path(lw)); +} + static void layout_menu_print_cb(GtkAction *action, gpointer data) { LayoutWindow *lw = data; @@ -757,6 +765,7 @@ static GtkActionEntry menu_entries[] = { { "OpenRecent", NULL, N_("Open _recent") }, { "Search", GTK_STOCK_FIND, N_("_Search..."), "F3", NULL, CB(layout_menu_search_cb) }, { "FindDupes", GTK_STOCK_FIND, N_("_Find duplicates..."),"D", NULL, CB(layout_menu_dupes_cb) }, + { "PanView", NULL, N_("Pan _view"), "J", NULL, CB(layout_menu_pan_cb) }, { "Print", GTK_STOCK_PRINT,N_("_Print..."), "P", NULL, CB(layout_menu_print_cb) }, { "NewFolder", NULL, N_("N_ew folder..."), "F", NULL, CB(layout_menu_dir_cb) }, { "Copy", NULL, N_("_Copy..."), "C", NULL, CB(layout_menu_copy_cb) }, @@ -831,6 +840,7 @@ static const char *menu_ui_description = " " " " " " +" " " " " " " " diff --git a/src/pan-view.c b/src/pan-view.c new file mode 100644 index 00000000..3c5bcf94 --- /dev/null +++ b/src/pan-view.c @@ -0,0 +1,4122 @@ +/* + * GQview + * (C) 2005 John Ellis + * + * Author: John Ellis + * + * This software is released under the GNU General Public License (GNU GPL). + * Please read the included file COPYING for more information. + * This software comes with no warranty of any kind, use at your own risk! + */ + + +#include "gqview.h" +#include "pan-view.h" + +#include "cache.h" +#include "dnd.h" +#include "editors.h" +#include "filelist.h" +#include "fullscreen.h" +#include "image.h" +#include "image-load.h" +#include "img-view.h" +#include "info.h" +#include "menu.h" +#include "pixbuf_util.h" +#include "thumb.h" +#include "utilops.h" +#include "ui_bookmark.h" +#include "ui_fileops.h" +#include "ui_menu.h" +#include "ui_misc.h" +#include "ui_tabcomp.h" + +#include /* for keyboard values */ +#include + + +#define PAN_WINDOW_DEFAULT_WIDTH 720 +#define PAN_WINDOW_DEFAULT_HEIGHT 500 + +#define PAN_TILE_SIZE 512 + +#define PAN_THUMB_SIZE_NONE 24 +#define PAN_THUMB_SIZE_SMALL 64 +#define PAN_THUMB_SIZE_NORMAL 128 +#define PAN_THUMB_SIZE_LARGE 256 +#define PAN_THUMB_SIZE pw->thumb_size + +#define PAN_THUMB_GAP_SMALL 14 +#define PAN_THUMB_GAP_NORMAL 30 +#define PAN_THUMB_GAP_LARGE 40 +#define PAN_THUMB_GAP_HUGE 50 +#define PAN_THUMB_GAP pw->thumb_gap + +#define PAN_SHADOW_OFFSET 6 +#define PAN_SHADOW_FADE 5 +#define PAN_SHADOW_COLOR 0, 0, 0 +#define PAN_SHADOW_ALPHA 64 + +#define PAN_OUTLINE_THICKNESS 1 +#define PAN_OUTLINE_COLOR_1 255, 255, 255 +#define PAN_OUTLINE_COLOR_2 64, 64, 64 +#define PAN_OUTLINE_ALPHA 180 + +#define PAN_BACKGROUND_COLOR 255, 255, 230 + +#define PAN_GRID_SIZE 10 +#define PAN_GRID_COLOR 0, 0, 0 +#define PAN_GRID_ALPHA 20 + +#define PAN_FOLDER_BOX_COLOR 0, 0, 255 +#define PAN_FOLDER_BOX_ALPHA 10 +#define PAN_FOLDER_BOX_BORDER 20 + +#define PAN_FOLDER_BOX_OUTLINE_THICKNESS 4 +#define PAN_FOLDER_BOX_OUTLINE_COLOR 0, 0, 255 +#define PAN_FOLDER_BOX_OUTLINE_ALPHA 64 + +#define PAN_TEXT_BORDER_SIZE 4 +#define PAN_TEXT_COLOR 0, 0, 0 + +#define PAN_POPUP_COLOR 255, 255, 220 +#define PAN_POPUP_ALPHA 255 +#define PAN_POPUP_BORDER 1 +#define PAN_POPUP_BORDER_COLOR 0, 0, 0 +#define PAN_POPUP_TEXT_COLOR 0, 0, 0 + +#define PAN_GROUP_MAX 16 + +#define ZOOM_INCREMENT 1.0 +#define ZOOM_LABEL_WIDTH 64 + + +typedef enum { + LAYOUT_TIMELINE = 0, + LAYOUT_FOLDERS_LINEAR, + LAYOUT_FOLDERS_FLOWER, + LAYOUT_GRID, +} LayoutType; + +typedef enum { + LAYOUT_SIZE_THUMB_NONE = 0, + LAYOUT_SIZE_THUMB_SMALL, + LAYOUT_SIZE_THUMB_NORMAL, + LAYOUT_SIZE_THUMB_LARGE, + LAYOUT_SIZE_10, + LAYOUT_SIZE_25, + LAYOUT_SIZE_33, + LAYOUT_SIZE_50, + LAYOUT_SIZE_100 +} LayoutSize; + +typedef enum { + ITEM_NONE, + ITEM_THUMB, + ITEM_BOX, + ITEM_TRIANGLE, + ITEM_TEXT, + ITEM_IMAGE +} ItemType; + +typedef enum { + TEXT_ATTR_NONE = 0, + TEXT_ATTR_BOLD = 1 << 0, + TEXT_ATTR_HEADING = 1 << 1, + TEXT_ATTR_MARKUP = 1 << 2 +} TextAttrType; + +enum { + BORDER_NONE = 0, + BORDER_1 = 1 << 0, + BORDER_2 = 1 << 1, + BORDER_3 = 1 << 2, + BORDER_4 = 1 << 3 +}; + +typedef struct _PanItem PanItem; +struct _PanItem { + ItemType type; + gint x; + gint y; + gint width; + gint height; + gchar *key; + + FileData *fd; + + GdkPixbuf *pixbuf; + gint refcount; + + gchar *text; + TextAttrType text_attr; + + guint8 color_r; + guint8 color_g; + guint8 color_b; + guint8 color_a; + + guint8 color2_r; + guint8 color2_g; + guint8 color2_b; + guint8 color2_a; + gint border; + + gpointer data; + + gint queued; +}; + +typedef struct _PanWindow PanWindow; +struct _PanWindow +{ + GtkWidget *window; + ImageWindow *imd; + ImageWindow *imd_normal; + FullScreenData *fs; + + GtkWidget *path_entry; + + GtkWidget *label_message; + GtkWidget *label_zoom; + + GtkWidget *search_box; + GtkWidget *search_entry; + GtkWidget *search_label; + GtkWidget *search_button; + GtkWidget *search_button_arrow; + + GtkWidget *scrollbar_h; + GtkWidget *scrollbar_v; + + gint overlay_id; + + gchar *path; + LayoutType layout; + LayoutSize size; + gint thumb_size; + gint thumb_gap; + gint image_size; + + GList *list; + + GList *cache_list; + GList *cache_todo; + gint cache_count; + gint cache_total; + gint cache_tick; + + ImageLoader *il; + ThumbLoader *tl; + PanItem *queue_pi; + GList *queue; + + PanItem *click_pi; + + gint idle_id; +}; + +typedef struct _PanCacheData PanCacheData; +struct _PanCacheData { + FileData fd; + CacheData *cd; +}; + + +static GList *pan_window_list = NULL; + + +static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend); + +static GtkWidget *pan_popup_menu(PanWindow *pw); +static void pan_fullscreen_toggle(PanWindow *pw, gint force_off); +static void pan_overlay_toggle(PanWindow *pw); + +static void pan_window_close(PanWindow *pw); + +static void pan_window_dnd_init(PanWindow *pw); + + +static gint util_clip_region(gint x, gint y, gint w, gint h, + gint clip_x, gint clip_y, gint clip_w, gint clip_h, + gint *rx, gint *ry, gint *rw, gint *rh) +{ + if (clip_x + clip_w <= x || + clip_x >= x + w || + clip_y + clip_h <= y || + clip_y >= y + h) + { + return FALSE; + } + + *rx = MAX(x, clip_x); + *rw = MIN((x + w), (clip_x + clip_w)) - *rx; + + *ry = MAX(y, clip_y); + *rh = MIN((y + h), (clip_y + clip_h)) - *ry; + + return TRUE; +} + +static gint util_clip_region_test(gint x, gint y, gint w, gint h, + gint clip_x, gint clip_y, gint clip_w, gint clip_h) +{ + gint rx, ry, rw, rh; + + return util_clip_region(x, y, w, h, + clip_x, clip_y, clip_w, clip_h, + &rx, &ry, &rw, &rh); +} + +typedef enum { + DATE_LENGTH_EXACT, + DATE_LENGTH_HOUR, + DATE_LENGTH_DAY, + DATE_LENGTH_WEEK, + DATE_LENGTH_MONTH, + DATE_LENGTH_YEAR +} DateLengthType; + +static gint date_compare(time_t a, time_t b, DateLengthType length) +{ + struct tm ta; + struct tm tb; + + if (length == DATE_LENGTH_EXACT) return (a == b); + + if (!localtime_r(&a, &ta) || + !localtime_r(&b, &tb)) return FALSE; + + if (ta.tm_year != tb.tm_year) return FALSE; + if (length == DATE_LENGTH_YEAR) return TRUE; + + if (ta.tm_mon != tb.tm_mon) return FALSE; + if (length == DATE_LENGTH_MONTH) return TRUE; + + if (length == DATE_LENGTH_WEEK) return (ta.tm_yday / 7 == tb.tm_yday / 7); + + if (ta.tm_mday != tb.tm_mday) return FALSE; + if (length == DATE_LENGTH_DAY) return TRUE; + + return (ta.tm_hour == tb.tm_hour); +} + +static gchar *date_value_string(time_t d, DateLengthType length) +{ + struct tm td; + gchar buf[128]; + gchar *format = NULL; + + if (!localtime_r(&d, &td)) return g_strdup(""); + + switch (length) + { + case DATE_LENGTH_DAY: + return g_strdup_printf("%d", td.tm_mday); + break; + case DATE_LENGTH_WEEK: + format = "%A %e"; + break; + case DATE_LENGTH_MONTH: + format = "%B %Y"; + break; + case DATE_LENGTH_YEAR: + return g_strdup_printf("%d", td.tm_year + 1900); + break; + case DATE_LENGTH_EXACT: + default: + return g_strdup(text_from_time(d)); + break; + } + + + if (format && strftime(buf, sizeof(buf), format, &td) > 0) + { + gchar *ret = g_locale_to_utf8(buf, -1, NULL, NULL, NULL); + if (ret) return ret; + } + + return g_strdup(""); +} + +static time_t date_to_time(gint year, gint month, gint day) +{ + struct tm lt; + + lt.tm_sec = 0; + lt.tm_min = 0; + lt.tm_hour = 0; + lt.tm_mday = (day >= 1 && day <= 31) ? day : 1; + lt.tm_mon = (month >= 1 && month <= 12) ? month - 1 : 0; + lt.tm_year = year - 1900; + lt.tm_isdst = 0; + + return mktime(<); +} + +/* + *----------------------------------------------------------------------------- + * drawing utils + *----------------------------------------------------------------------------- + */ + +static void triangle_rect_region(gint x1, gint y1, gint x2, gint y2, gint x3, gint y3, + gint *rx, gint *ry, gint *rw, gint *rh) +{ + gint tx, ty, tw, th; + + tx = MIN(x1, x2); + tx = MIN(tx, x3); + ty = MIN(y1, y2); + ty = MIN(ty, y3); + tw = MAX(abs(x1 - x2), abs(x2 - x3)); + tw = MAX(tw, abs(x3 - x1)); + th = MAX(abs(y1 - y2), abs(y2 - y3)); + th = MAX(th, abs(y3 - y1)); + + *rx = tx; + *ry = ty; + *rw = tw; + *rh = th; +} + +static void pixbuf_draw_triangle(GdkPixbuf *pb, + gint clip_x, gint clip_y, gint clip_w, gint clip_h, + gint x1, gint y1, gint x2, gint y2, gint x3, gint y3, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + gint p_alpha; + gint pw, ph, prs; + gint rx, ry, rw, rh; + gint tx, ty, tw, th; + gint fx1, fy1; + gint fx2, fy2; + gint fw, fh; + guchar *p_pix; + guchar *pp; + gint p_step; + gint i, j; + + if (!pb) return; + + pw = gdk_pixbuf_get_width(pb); + ph = gdk_pixbuf_get_height(pb); + + if (!util_clip_region(0, 0, pw, ph, + clip_x, clip_y, clip_w, clip_h, + &rx, &ry, &rw, &rh)) return; + + triangle_rect_region(x1, y1, x2, y2, x3, y3, + &tx, &ty, &tw, &th); + + if (!util_clip_region(rx, ry, rw, rh, + tx, ty, tw, th, + &fx1, &fy1, &fw, &fh)) return; + fx2 = fx1 + fw; + fy2 = fy1 + fh; + + p_alpha = gdk_pixbuf_get_has_alpha(pb); + prs = gdk_pixbuf_get_rowstride(pb); + p_pix = gdk_pixbuf_get_pixels(pb); + + p_step = (p_alpha) ? 4 : 3; + for (i = fy1; i < fy2; i++) + { + pp = p_pix + i * prs + (fx1 * p_step); + for (j = fx1; j < fx2; j++) + { + gint z1, z2; + + z1 = (y1 - y2)*(j - x2) + (x2 - x1)*(i - y2); + z2 = (y2 - y3)*(j - x3) + (x3 - x2)*(i - y3); + if ((z1 ^ z2) >= 0) + { + z2 = (y3 - y1)*(j - x1) + (x1 - x3)*(i - y1); + if ((z1 ^ z2) >= 0) + { + pp[0] = (r * a + pp[0] * (256-a)) >> 8; + pp[1] = (g * a + pp[1] * (256-a)) >> 8; + pp[2] = (b * a + pp[2] * (256-a)) >> 8; + } + } + pp += p_step; + } + } +} + +static void pixbuf_draw_line(GdkPixbuf *pb, + gint clip_x, gint clip_y, gint clip_w, gint clip_h, + gint x1, gint y1, gint x2, gint y2, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + gint p_alpha; + gint pw, ph, prs; + gint rx, ry, rw, rh; + gint fx1, fy1, fx2, fy2; + guchar *p_pix; + guchar *pp; + gint p_step; + gint xd, yd; + gint xa, ya; + gdouble xstep, ystep; + gdouble i, j; + gint n, nt; + gint x, y; + + if (!pb) return; + + pw = gdk_pixbuf_get_width(pb); + ph = gdk_pixbuf_get_height(pb); + + if (!util_clip_region(0, 0, pw, ph, + clip_x, clip_y, clip_w, clip_h, + &rx, &ry, &rw, &rh)) return; + + fx1 = rx; + fy1 = ry; + fx2 = rx + rw; + fy2 = ry + rh; + + xd = x2 - x1; + yd = y2 - y1; + xa = abs(xd); + ya = abs(yd); + + if (xa == 0 && ya == 0) return; +#if 0 + nt = sqrt(xd * xd + yd * yd); +#endif + nt = (xa > ya) ? xa : ya; + xstep = (double)xd / nt; + ystep = (double)yd / nt; + + p_alpha = gdk_pixbuf_get_has_alpha(pb); + prs = gdk_pixbuf_get_rowstride(pb); + p_pix = gdk_pixbuf_get_pixels(pb); + + p_step = (p_alpha) ? 4 : 3; + + i = (double)y1; + j = (double)x1; + for (n = 0; n < nt; n++) + { + x = (gint)(j + 0.5); + y = (gint)(i + 0.5); + + if (x >= fx1 && x < fx2 && + y >= fy1 && y < fy2) + { + pp = p_pix + y * prs + x * p_step; + *pp = (r * a + *pp * (256-a)) >> 8; + pp++; + *pp = (g * a + *pp * (256-a)) >> 8; + pp++; + *pp = (b * a + *pp * (256-a)) >> 8; + } + i += ystep; + j += xstep; + } +} + +static void pixbuf_draw_fade_linear(guchar *p_pix, gint prs, gint p_alpha, + gint s, gint vertical, gint border, + gint x1, gint y1, gint x2, gint y2, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + guchar *pp; + gint p_step; + guint8 n = a; + gint i, j; + + p_step = (p_alpha) ? 4 : 3; + for (j = y1; j < y2; j++) + { + pp = p_pix + j * prs + x1 * p_step; + if (!vertical) n = a - a * abs(j - s) / border; + for (i = x1; i < x2; i++) + { + if (vertical) n = a - a * abs(i - s) / border; + *pp = (r * n + *pp * (256-n)) >> 8; + pp++; + *pp = (g * n + *pp * (256-n)) >> 8; + pp++; + *pp = (b * n + *pp * (256-n)) >> 8; + pp++; + if (p_alpha) pp++; + } + } +} + +static void pixbuf_draw_fade_radius(guchar *p_pix, gint prs, gint p_alpha, + gint sx, gint sy, gint border, + gint x1, gint y1, gint x2, gint y2, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + guchar *pp; + gint p_step; + gint i, j; + + p_step = (p_alpha) ? 4 : 3; + for (j = y1; j < y2; j++) + { + pp = p_pix + j * prs + x1 * p_step; + for (i = x1; i < x2; i++) + { + guint8 n; + gint r; + + r = MIN(border, (gint)sqrt((i-sx)*(i-sx) + (j-sy)*(j-sy))); + n = a - a * r / border; + *pp = (r * n + *pp * (256-n)) >> 8; + pp++; + *pp = (g * n + *pp * (256-n)) >> 8; + pp++; + *pp = (b * n + *pp * (256-n)) >> 8; + pp++; + if (p_alpha) pp++; + } + } +} + +static void pixbuf_draw_shadow(GdkPixbuf *pb, + gint clip_x, gint clip_y, gint clip_w, gint clip_h, + gint x, gint y, gint w, gint h, gint border, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + gint p_alpha; + gint pw, ph, prs; + gint rx, ry, rw, rh; + gint fx, fy, fw, fh; + guchar *p_pix; + + if (!pb) return; + + pw = gdk_pixbuf_get_width(pb); + ph = gdk_pixbuf_get_height(pb); + + if (!util_clip_region(0, 0, pw, ph, + clip_x, clip_y, clip_w, clip_h, + &rx, &ry, &rw, &rh)) return; + + p_alpha = gdk_pixbuf_get_has_alpha(pb); + prs = gdk_pixbuf_get_rowstride(pb); + p_pix = gdk_pixbuf_get_pixels(pb); + + if (util_clip_region(x + border, y + border, w - border * 2, h - border * 2, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_rect_fill(pb, fx, fy, fw, fh, r, g, b, a); + } + + if (border < 1) return; + + if (util_clip_region(x, y + border, border, h - border * 2, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_linear(p_pix, prs, p_alpha, + x + border, TRUE, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x + w - border, y + border, border, h - border * 2, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_linear(p_pix, prs, p_alpha, + x + w - border, TRUE, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x + border, y, w - border * 2, border, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_linear(p_pix, prs, p_alpha, + y + border, FALSE, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x + border, y + h - border, w - border * 2, border, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_linear(p_pix, prs, p_alpha, + y + h - border, FALSE, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x, y, border, border, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_radius(p_pix, prs, p_alpha, + x + border, y + border, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x + w - border, y, border, border, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_radius(p_pix, prs, p_alpha, + x + w - border, y + border, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x, y + h - border, border, border, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_radius(p_pix, prs, p_alpha, + x + border, y + h - border, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } + if (util_clip_region(x + w - border, y + h - border, border, border, + rx, ry, rw, rh, + &fx, &fy, &fw, &fh)) + { + pixbuf_draw_fade_radius(p_pix, prs, p_alpha, + x + w - border, y + h - border, border, + fx, fy, fx + fw, fy + fh, + r, g, b, a); + } +} + + +/* + *----------------------------------------------------------------------------- + * cache + *----------------------------------------------------------------------------- + */ + +static void pan_cache_free(PanWindow *pw) +{ + GList *work; + + work = pw->cache_list; + while (work) + { + PanCacheData *pc; + + pc = work->data; + work = work->next; + + cache_sim_data_free(pc->cd); + file_data_free((FileData *)pc); + } + + g_list_free(pw->cache_list); + pw->cache_list = NULL; + + filelist_free(pw->cache_todo); + pw->cache_todo = NULL; + + pw->cache_count = 0; + pw->cache_total = 0; + pw->cache_tick = 0; +} + +static void pan_cache_fill(PanWindow *pw, const gchar *path) +{ + GList *list; + + pan_cache_free(pw); + + list = pan_window_layout_list(path, SORT_NAME, TRUE); + pw->cache_todo = g_list_reverse(list); + + pw->cache_total = g_list_length(pw->cache_todo); +} + +static gint pan_cache_step(PanWindow *pw) +{ + FileData *fd; + PanCacheData *pc; + CacheData *cd = NULL; + + if (!pw->cache_todo) return FALSE; + + fd = pw->cache_todo->data; + pw->cache_todo = g_list_remove(pw->cache_todo, fd); + + if (enable_thumb_caching) + { + gchar *found; + + found = cache_find_location(CACHE_TYPE_SIM, fd->path); + if (found && filetime(found) == fd->date) + { + cd = cache_sim_data_load(found); + } + g_free(found); + } + + if (!cd) cd = cache_sim_data_new(); + + if (!cd->dimensions) + { + cd->dimensions = image_load_dimensions(fd->path, &cd->width, &cd->height); + if (enable_thumb_caching && + cd->dimensions) + { + gchar *base; + mode_t mode = 0755; + + base = cache_get_location(CACHE_TYPE_SIM, fd->path, FALSE, &mode); + if (cache_ensure_dir_exists(base, mode)) + { + g_free(cd->path); + cd->path = cache_get_location(CACHE_TYPE_SIM, fd->path, TRUE, NULL); + if (cache_sim_data_save(cd)) + { + filetime_set(cd->path, filetime(fd->path)); + } + } + g_free(base); + } + + pw->cache_tick = 9; + } + + pc = g_new0(PanCacheData, 1); + memcpy(pc, fd, sizeof(FileData)); + g_free(fd); + + pc->cd = cd; + + pw->cache_list = g_list_prepend(pw->cache_list, pc); + + return TRUE; +} + + +/* + *----------------------------------------------------------------------------- + * item objects + *----------------------------------------------------------------------------- + */ + +static void pan_item_free(PanItem *pi) +{ + if (!pi) return; + + if (pi->pixbuf) g_object_unref(pi->pixbuf); + if (pi->fd) file_data_free(pi->fd); + g_free(pi->text); + g_free(pi->key); + g_free(pi->data); + + g_free(pi); +} + +static void pan_window_items_free(PanWindow *pw) +{ + GList *work; + + work = pw->list; + while (work) + { + PanItem *pi = work->data; + work = work->next; + + pan_item_free(pi); + } + + g_list_free(pw->list); + pw->list = NULL; + + g_list_free(pw->queue); + pw->queue = NULL; + pw->queue_pi = NULL; + + image_loader_free(pw->il); + pw->il = NULL; + + thumb_loader_free(pw->tl); + pw->tl = NULL; + + pw->click_pi = NULL; +} + +static PanItem *pan_item_new_thumb(PanWindow *pw, FileData *fd, gint x, gint y) +{ + PanItem *pi; + + pi = g_new0(PanItem, 1); + pi->type = ITEM_THUMB; + pi->fd = fd; + pi->x = x; + pi->y = y; + pi->width = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2; + pi->height = PAN_THUMB_SIZE + PAN_SHADOW_OFFSET * 2; + + pi->pixbuf = NULL; + + pi->queued = FALSE; + + pw->list = g_list_prepend(pw->list, pi); + + return pi; +} + +static PanItem *pan_item_new_box(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height, + gint border_size, + guint8 base_r, guint8 base_g, guint8 base_b, guint8 base_a, + guint8 bord_r, guint8 bord_g, guint8 bord_b, guint8 bord_a) +{ + PanItem *pi; + + pi = g_new0(PanItem, 1); + pi->type = ITEM_BOX; + pi->fd = fd; + pi->x = x; + pi->y = y; + pi->width = width; + pi->height = height; + + pi->color_r = base_r; + pi->color_g = base_g; + pi->color_b = base_b; + pi->color_a = base_a; + + pi->color2_r = bord_r; + pi->color2_g = bord_g; + pi->color2_b = bord_b; + pi->color2_a = bord_a; + pi->border = border_size; + + pw->list = g_list_prepend(pw->list, pi); + + return pi; +} + +static void pan_item_box_shadow(PanItem *pi, gint offset, gint fade) +{ + gint *shadow; + + if (!pi || pi->type != ITEM_BOX) return; + + shadow = pi->data; + if (shadow) + { + pi->width -= shadow[0]; + pi->height -= shadow[0]; + } + + shadow = g_new0(gint, 2); + shadow[0] = offset; + shadow[1] = fade; + + pi->width += offset; + pi->height += offset; + + g_free(pi->data); + pi->data = shadow; +} + +static PanItem *pan_item_new_tri(PanWindow *pw, FileData *fd, gint x, gint y, gint width, gint height, + gint x1, gint y1, gint x2, gint y2, gint x3, gint y3, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + PanItem *pi; + gint *coord; + + pi = g_new0(PanItem, 1); + pi->type = ITEM_TRIANGLE; + pi->x = x; + pi->y = y; + pi->width = width; + pi->height = height; + + pi->color_r = r; + pi->color_g = g; + pi->color_b = b; + pi->color_a = a; + + coord = g_new0(gint, 6); + coord[0] = x1; + coord[1] = y1; + coord[2] = x2; + coord[3] = y2; + coord[4] = x3; + coord[5] = y3; + + pi->data = coord; + + pi->border = BORDER_NONE; + + pw->list = g_list_prepend(pw->list, pi); + + return pi; +} + +static void pan_item_tri_border(PanItem *pi, gint borders, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + if (!pi || pi->type != ITEM_TRIANGLE) return; + + pi->border = borders; + + pi->color2_r = r; + pi->color2_g = g; + pi->color2_b = b; + pi->color2_a = a; +} + +static PangoLayout *pan_item_text_layout(PanItem *pi, GtkWidget *widget) +{ + PangoLayout *layout; + + layout = gtk_widget_create_pango_layout(widget, NULL); + + if (pi->text_attr & TEXT_ATTR_MARKUP) + { + pango_layout_set_markup(layout, pi->text, -1); + return layout; + } + + if (pi->text_attr & TEXT_ATTR_BOLD || + pi->text_attr & TEXT_ATTR_HEADING) + { + PangoAttrList *pal; + PangoAttribute *pa; + + pal = pango_attr_list_new(); + if (pi->text_attr & TEXT_ATTR_BOLD) + { + pa = pango_attr_weight_new(PANGO_WEIGHT_BOLD); + pa->start_index = 0; + pa->end_index = G_MAXINT; + pango_attr_list_insert(pal, pa); + } + if (pi->text_attr & TEXT_ATTR_HEADING) + { + pa = pango_attr_scale_new(PANGO_SCALE_LARGE); + pa->start_index = 0; + pa->end_index = G_MAXINT; + pango_attr_list_insert(pal, pa); + } + pango_layout_set_attributes(layout, pal); + pango_attr_list_unref(pal); + } + + pango_layout_set_text(layout, pi->text, -1); + return layout; +} + +static void pan_item_text_compute_size(PanItem *pi, GtkWidget *widget) +{ + PangoLayout *layout; + + if (!pi || !pi->text || !widget) return; + + layout = pan_item_text_layout(pi, widget); + pango_layout_get_pixel_size(layout, &pi->width, &pi->height); + g_object_unref(G_OBJECT(layout)); + + pi->width += PAN_TEXT_BORDER_SIZE * 2; + pi->height += PAN_TEXT_BORDER_SIZE * 2; +} + +static PanItem *pan_item_new_text(PanWindow *pw, gint x, gint y, const gchar *text, TextAttrType attr, + guint8 r, guint8 g, guint8 b, guint8 a) +{ + PanItem *pi; + + pi = g_new0(PanItem, 1); + pi->type = ITEM_TEXT; + pi->x = x; + pi->y = y; + pi->text = g_strdup(text); + pi->text_attr = attr; + + pi->color_r = r; + pi->color_g = g; + pi->color_b = b; + pi->color_a = a; + + pan_item_text_compute_size(pi, pw->imd->widget); + + pw->list = g_list_prepend(pw->list, pi); + + return pi; +} + +static void pan_item_set_key(PanItem *pi, const gchar *key) +{ + gchar *tmp; + + if (!pi) return; + + tmp = pi->key; + pi->key = g_strdup(key); + g_free(tmp); +} + +static void pan_item_added(PanWindow *pw, PanItem *pi) +{ + if (!pi) return; + image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height); +} + +static void pan_item_remove(PanWindow *pw, PanItem *pi) +{ + if (!pi) return; + + if (pw->click_pi == pi) pw->click_pi = NULL; + + pw->list = g_list_remove(pw->list, pi); + image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height); + pan_item_free(pi); +} + +static void pan_item_size_by_item(PanItem *pi, PanItem *child, gint border) +{ + if (!pi || !child) return; + + if (pi->x + pi->width < child->x + child->width + border) + pi->width = child->x + child->width + border - pi->x; + + if (pi->y + pi->height < child->y + child->height + border) + pi->height = child->y + child->height + border - pi->y; +} + +static void pan_item_image_find_size(PanWindow *pw, PanItem *pi, gint w, gint h) +{ + GList *work; + + pi->width = w; + pi->height = h; + + if (!pi->fd) return; + + work = pw->cache_list; + while (work) + { + PanCacheData *pc; + gchar *path; + + pc = work->data; + work = work->next; + + path = ((FileData *)pc)->path; + + if (pc->cd && pc->cd->dimensions && + path && strcmp(path, pi->fd->path) == 0) + { + pi->width = MAX(1, pc->cd->width * pw->image_size / 100); + pi->height = MAX(1, pc->cd->height * pw->image_size / 100); + + pw->cache_list = g_list_remove(pw->cache_list, pc); + cache_sim_data_free(pc->cd); + file_data_free((FileData *)pc); + return; + } + } +} + +static PanItem *pan_item_new_image(PanWindow *pw, FileData *fd, gint x, gint y, gint w, gint h) +{ + PanItem *pi; + + pi = g_new0(PanItem, 1); + pi->type = ITEM_IMAGE; + pi->fd = fd; + pi->x = x; + pi->y = y; + + pan_item_image_find_size(pw, pi, w, h); + + pw->list = g_list_prepend(pw->list, pi); + + return pi; +} + +static PanItem *pan_item_find_by_key(PanWindow *pw, ItemType type, const gchar *key) +{ + GList *work; + + if (!key) return NULL; + + work = g_list_last(pw->list); + while (work) + { + PanItem *pi; + + pi = work->data; + if ((pi->type == type || type == ITEM_NONE) && + pi->key && strcmp(pi->key, key) == 0) + { + return pi; + } + work = work->prev; + } + + return NULL; +} + +/* when ignore_case and partial are TRUE, path should be converted to lower case */ +static PanItem *pan_item_find_by_path(PanWindow *pw, ItemType type, const gchar *path, + gint ignore_case, gint partial) +{ + GList *work; + + if (!path) return NULL; + if (partial && path[0] == '/') return NULL; + + work = g_list_last(pw->list); + while (work) + { + PanItem *pi; + + pi = work->data; + if ((pi->type == type || type == ITEM_NONE) && pi->fd) + { + if (path[0] == '/') + { + if (pi->fd->path && strcmp(path, pi->fd->path) == 0) return pi; + } + else if (pi->fd->name) + { + if (partial) + { + if (ignore_case) + { + gchar *haystack; + gint match; + + haystack = g_utf8_strdown(pi->fd->name, -1); + match = (strstr(haystack, path) != NULL); + g_free(haystack); + if (match) return pi; + } + else + { + if (strstr(pi->fd->name, path)) return pi; + } + } + else if (ignore_case) + { + if (strcasecmp(path, pi->fd->name) == 0) return pi; + } + else + { + if (strcmp(path, pi->fd->name) == 0) return pi; + } + } + } + work = work->prev; + } + + return NULL; +} + +static PanItem *pan_item_find_by_coord(PanWindow *pw, ItemType type, gint x, gint y) +{ + GList *work; + + if (x < 0 || x >= pw->imd->image_width || + y < 0 || y >= pw->imd->image_height) return NULL; + + work = pw->list; + while (work) + { + PanItem *pi; + + pi = work->data; + if ((pi->type == type || type == ITEM_NONE) && + x >= pi->x && x < pi->x + pi->width && + y >= pi->y && y < pi->y + pi->height) + { + return pi; + } + work = work->next; + } + + return NULL; +} + +/* + *----------------------------------------------------------------------------- + * layout generation + *----------------------------------------------------------------------------- + */ + +static GList *pan_window_layout_list(const gchar *path, SortType sort, gint ascend) +{ + GList *flist = NULL; + GList *dlist = NULL; + GList *result; + GList *folders; + + filelist_read(path, &flist, &dlist); + if (sort != SORT_NONE) + { + flist = filelist_sort(flist, sort, ascend); + dlist = filelist_sort(dlist, sort, ascend); + } + + result = flist; + folders = dlist; + while (folders) + { + FileData *fd; + + fd = folders->data; + folders = g_list_remove(folders, fd); + + if (filelist_read(fd->path, &flist, &dlist)) + { + if (sort != SORT_NONE) + { + flist = filelist_sort(flist, sort, ascend); + dlist = filelist_sort(dlist, sort, ascend); + } + + result = g_list_concat(result, flist); + folders = g_list_concat(dlist, folders); + } + + file_data_free(fd); + } + + return result; +} + +static void pan_window_layout_compute_grid(PanWindow *pw, const gchar *path, gint *width, gint *height) +{ + GList *list; + GList *work; + gint x, y; + gint w, h; + gint grid_size; + gint next_y; + + list = pan_window_layout_list(path, SORT_NAME, TRUE); + + grid_size = (gint)sqrt((double)g_list_length(list)); + if (pw->size > LAYOUT_SIZE_THUMB_LARGE) + { + grid_size = grid_size * (512 + PAN_THUMB_GAP) * pw->image_size / 100; + } + else + { + grid_size = grid_size * (PAN_THUMB_SIZE + PAN_THUMB_GAP); + } + + next_y = 0; + + w = PAN_THUMB_GAP * 2; + h = PAN_THUMB_GAP * 2; + + x = PAN_THUMB_GAP; + y = PAN_THUMB_GAP; + work = list; + while (work) + { + FileData *fd; + PanItem *pi; + + fd = work->data; + work = work->next; + + if (pw->size > LAYOUT_SIZE_THUMB_LARGE) + { + pi = pan_item_new_image(pw, fd, x, y, 10, 10); + + x += pi->width + PAN_THUMB_GAP; + if (y + pi->height + PAN_THUMB_GAP > next_y) next_y = y + pi->height + PAN_THUMB_GAP; + if (x > grid_size) + { + x = PAN_THUMB_GAP; + y = next_y; + } + } + else + { + pi = pan_item_new_thumb(pw, fd, x, y); + + x += PAN_THUMB_SIZE + PAN_THUMB_GAP; + if (x > grid_size) + { + x = PAN_THUMB_GAP; + y += PAN_THUMB_SIZE + PAN_THUMB_GAP; + } + } + + if (w < pi->x + pi->width + PAN_THUMB_GAP) w = pi->x + pi->width + PAN_THUMB_GAP; + if (h < pi->y + pi->height + PAN_THUMB_GAP) h = pi->y + pi->height + PAN_THUMB_GAP; + } + + if (width) *width = w; + if (height) *height = h; + + g_list_free(list); +} + +static void pan_window_Layout_compute_folders_flower_size(PanWindow *pw, gint *width, gint *height) +{ + GList *work; + gint x1, y1, x2, y2; + + x1 = 0; + y1 = 0; + x2 = 0; + y2 = 0; + + work = pw->list; + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->next; + + if (x1 > pi->x) x1 = pi->x; + if (y1 > pi->y) y1 = pi->y; + if (x2 < pi->x + pi->width) x2 = pi->x + pi->width; + if (y2 < pi->y + pi->height) y2 = pi->y + pi->height; + } + + x1 -= PAN_FOLDER_BOX_BORDER; + y1 -= PAN_FOLDER_BOX_BORDER; + x2 += PAN_FOLDER_BOX_BORDER; + y2 += PAN_FOLDER_BOX_BORDER; + + work = pw->list; + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->next; + + pi->x -= x1; + pi->y -= y1; + + if (pi->type == ITEM_TRIANGLE && pi->data) + { + gint *coord; + + coord = pi->data; + coord[0] -= x1; + coord[1] -= y1; + coord[2] -= x1; + coord[3] -= y1; + coord[4] -= x1; + coord[5] -= y1; + } + } + + if (width) *width = x2 - x1; + if (height) *height = y2 - y1; +} + +typedef struct _FlowerGroup FlowerGroup; +struct _FlowerGroup { + GList *items; + GList *children; + gint x; + gint y; + gint width; + gint height; + + gdouble angle; + gint circumference; + gint diameter; +}; + +static void pan_window_layout_compute_folder_flower_move(FlowerGroup *group, gint x, gint y) +{ + GList *work; + + work = group->items; + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->next; + + pi->x += x; + pi->y += y; + } + + group->x += x; + group->y += y; +} + +#define PI 3.14159 + +static void pan_window_layout_compute_folder_flower_position(FlowerGroup *group, FlowerGroup *parent, + gint *result_x, gint *result_y) +{ + gint x, y; + gint radius; + gdouble a; + + radius = parent->circumference / (2*PI); + radius = MAX(radius, parent->diameter / 2 + group->diameter / 2); + + a = 2*PI * group->diameter / parent->circumference; + + x = (gint)((double)radius * cos(parent->angle + a / 2)); + y = (gint)((double)radius * sin(parent->angle + a / 2)); + + parent->angle += a; + + x += parent->x; + y += parent->y; + + x += parent->width / 2; + y += parent->height / 2; + + x -= group->width / 2; + y -= group->height / 2; + + *result_x = x; + *result_y = y; +} + +static void pan_window_layout_compute_folder_flower_build(PanWindow *pw, FlowerGroup *group, FlowerGroup *parent) +{ + GList *work; + gint x, y; + + if (!group) return; + + if (parent && parent->children) + { + pan_window_layout_compute_folder_flower_position(group, parent, &x, &y); + } + else + { + x = 0; + y = 0; + } + + pan_window_layout_compute_folder_flower_move(group, x, y); + + if (parent) + { + PanItem *pi; + gint px, py, gx, gy; + gint x1, y1, x2, y2; + + px = parent->x + parent->width / 2; + py = parent->y + parent->height / 2; + + gx = group->x + group->width / 2; + gy = group->y + group->height / 2; + + x1 = MIN(px, gx); + y1 = MIN(py, gy); + + x2 = MAX(px, gx + 5); + y2 = MAX(py, gy + 5); + + pi = pan_item_new_tri(pw, NULL, x1, y1, x2 - x1, y2 - y1, + px, py, gx, gy, gx + 5, gy + 5, + 255, 40, 40, 128); + pan_item_tri_border(pi, BORDER_1 | BORDER_3, + 255, 0, 0, 128); + } + + pw->list = g_list_concat(group->items, pw->list); + group->items = NULL; + + group->circumference = 0; + work = group->children; + while (work) + { + FlowerGroup *child; + + child = work->data; + work = work->next; + + group->circumference += child->diameter; + } + + work = g_list_last(group->children); + while (work) + { + FlowerGroup *child; + + child = work->data; + work = work->prev; + + pan_window_layout_compute_folder_flower_build(pw, child, group); + } + + g_list_free(group->children); + g_free(group); +} + +static FlowerGroup *pan_window_layout_compute_folders_flower_path(PanWindow *pw, const gchar *path, + gint x, gint y) +{ + FlowerGroup *group; + GList *f; + GList *d; + GList *work; + PanItem *pi_box; + gint x_start; + gint y_height; + gint grid_size; + gint grid_count; + + if (!filelist_read(path, &f, &d)) return NULL; + if (!f && !d) return NULL; + + f = filelist_sort(f, SORT_NAME, TRUE); + d = filelist_sort(d, SORT_NAME, TRUE); + + pi_box = pan_item_new_text(pw, x, y, path, TEXT_ATTR_NONE, + PAN_TEXT_COLOR, 255); + + y += pi_box->height; + + pi_box = pan_item_new_box(pw, file_data_new_simple(path), + x, y, + PAN_FOLDER_BOX_BORDER * 2, PAN_FOLDER_BOX_BORDER * 2, + PAN_FOLDER_BOX_OUTLINE_THICKNESS, + PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA, + PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA); + + x += PAN_FOLDER_BOX_BORDER; + y += PAN_FOLDER_BOX_BORDER; + + grid_size = (gint)(sqrt(g_list_length(f)) + 0.9); + grid_count = 0; + x_start = x; + y_height = y; + + work = f; + while (work) + { + FileData *fd; + PanItem *pi; + + fd = work->data; + work = work->next; + + if (pw->size > LAYOUT_SIZE_THUMB_LARGE) + { + pi = pan_item_new_image(pw, fd, x, y, 10, 10); + x += pi->width + PAN_THUMB_GAP; + if (pi->height > y_height) y_height = pi->height; + } + else + { + pi = pan_item_new_thumb(pw, fd, x, y); + x += PAN_THUMB_SIZE + PAN_THUMB_GAP; + y_height = PAN_THUMB_SIZE; + } + + grid_count++; + if (grid_count >= grid_size) + { + grid_count = 0; + x = x_start; + y += y_height + PAN_THUMB_GAP; + y_height = 0; + } + + pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER); + } + + g_list_free(f); + + group = g_new0(FlowerGroup, 1); + group->items = pw->list; + pw->list = NULL; + + group->width = pi_box->width; + group->height = pi_box->y + pi_box->height; + group->diameter = (int)sqrt(group->width * group->width + group->height * group->height); + + group->children = NULL; + + work = d; + while (work) + { + FileData *fd; + FlowerGroup *child; + + fd = work->data; + work = work->next; + + child = pan_window_layout_compute_folders_flower_path(pw, fd->path, 0, 0); + if (child) group->children = g_list_prepend(group->children, child); + } + + filelist_free(d); + + return group; +} + +static void pan_window_layout_compute_folders_flower(PanWindow *pw, const gchar *path, + gint *width, gint *height, + gint *scroll_x, gint *scroll_y) +{ + FlowerGroup *group; + PanItem *pi; + + group = pan_window_layout_compute_folders_flower_path(pw, path, 0, 0); + pan_window_layout_compute_folder_flower_build(pw, group, NULL); + + pan_window_Layout_compute_folders_flower_size(pw, width, height); + + pi = pan_item_find_by_path(pw, ITEM_BOX, path, FALSE, FALSE); + if (pi) + { + *scroll_x = pi->x - PAN_FOLDER_BOX_BORDER; + *scroll_y = pi->y - PAN_FOLDER_BOX_BORDER; + } +} + +static void pan_window_layout_compute_folders_linear_path(PanWindow *pw, const gchar *path, + gint *x, gint *y, gint *level, + PanItem *parent, + gint *width, gint *height) +{ + GList *f; + GList *d; + GList *work; + PanItem *pi_box; + gint y_height = 0; + + if (!filelist_read(path, &f, &d)) return; + if (!f && !d) return; + + f = filelist_sort(f, SORT_NAME, TRUE); + d = filelist_sort(d, SORT_NAME, TRUE); + + *x = PAN_THUMB_GAP + ((*level) * (PAN_THUMB_GAP * 2)); + + pi_box = pan_item_new_text(pw, *x, *y, path, TEXT_ATTR_NONE, + PAN_TEXT_COLOR, 255); + + *y += pi_box->height; + + pi_box = pan_item_new_box(pw, file_data_new_simple(path), + *x, *y, + PAN_FOLDER_BOX_BORDER, PAN_FOLDER_BOX_BORDER, + PAN_FOLDER_BOX_OUTLINE_THICKNESS, + PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA, + PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA); + + *x += PAN_FOLDER_BOX_BORDER; + *y += PAN_FOLDER_BOX_BORDER; + + work = f; + while (work) + { + FileData *fd; + PanItem *pi; + + fd = work->data; + work = work->next; + + if (pw->size > LAYOUT_SIZE_THUMB_LARGE) + { + pi = pan_item_new_image(pw, fd, *x, *y, 10, 10); + *x += pi->width + PAN_THUMB_GAP; + if (pi->height > y_height) y_height = pi->height; + } + else + { + pi = pan_item_new_thumb(pw, fd, *x, *y); + *x += PAN_THUMB_SIZE + PAN_THUMB_GAP; + y_height = PAN_THUMB_SIZE; + } + + pan_item_size_by_item(pi_box, pi, PAN_FOLDER_BOX_BORDER); + } + + if (f) *y = pi_box->y + pi_box->height; + + g_list_free(f); + + work = d; + while (work) + { + FileData *fd; + + fd = work->data; + work = work->next; + + *level = *level + 1; + pan_window_layout_compute_folders_linear_path(pw, fd->path, x, y, level, + pi_box, width, height); + *level = *level - 1; + } + + filelist_free(d); + + pan_item_size_by_item(parent, pi_box, PAN_FOLDER_BOX_BORDER); + + if (*y < pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER) + *y = pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER; + + if (*width < pi_box->x + pi_box->width + PAN_FOLDER_BOX_BORDER) + *width = pi_box->x + pi_box->width + PAN_FOLDER_BOX_BORDER; + if (*height < pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER) + *height = pi_box->y + pi_box->height + PAN_FOLDER_BOX_BORDER; +} + +static void pan_window_layout_compute_folders_linear(PanWindow *pw, const gchar *path, gint *width, gint *height) +{ + gint x, y; + gint level; + gint w, h; + + level = 0; + x = PAN_FOLDER_BOX_BORDER; + y = PAN_FOLDER_BOX_BORDER; + w = PAN_FOLDER_BOX_BORDER * 2; + h = PAN_FOLDER_BOX_BORDER * 2; + + pan_window_layout_compute_folders_linear_path(pw, path, &x, &y, &level, NULL, &w, &h); + + if (width) *width = w; + if (height) *height = h; +} + +static void pan_window_layout_compute_timeline(PanWindow *pw, const gchar *path, gint *width, gint *height) +{ + GList *list; + GList *work; + gint x, y; + gint w, h; + time_t tc; + gint total; + gint count; + PanItem *pi_month = NULL; + PanItem *pi_day = NULL; + gint month_start; + gint day_start; + gint x_width; + gint y_height; + + pw->cache_list = filelist_sort(pw->cache_list, SORT_TIME, TRUE); + + list = pan_window_layout_list(path, SORT_NONE, TRUE); + list = filelist_sort(list, SORT_TIME, TRUE); + + w = PAN_FOLDER_BOX_BORDER * 2; + h = PAN_FOLDER_BOX_BORDER * 2; + + x = 0; + y = 0; + month_start = y; + day_start = month_start; + x_width = 0; + y_height = 0; + tc = 0; + total = 0; + count = 0; + work = list; + while (work) + { + FileData *fd; + PanItem *pi; + + fd = work->data; + work = work->next; + + if (!date_compare(fd->date, tc, DATE_LENGTH_DAY)) + { + GList *needle; + gchar *buf; + + if (!date_compare(fd->date, tc, DATE_LENGTH_MONTH)) + { + pi_day = NULL; + + if (pi_month) + { + x = pi_month->x + pi_month->width + PAN_FOLDER_BOX_BORDER; + } + else + { + x = PAN_FOLDER_BOX_BORDER; + } + + y = PAN_FOLDER_BOX_BORDER; + + buf = date_value_string(fd->date, DATE_LENGTH_MONTH); + pi = pan_item_new_text(pw, x, y, buf, + TEXT_ATTR_BOLD | TEXT_ATTR_HEADING, + PAN_TEXT_COLOR, 255); + y += pi->height; + + pi_month = pan_item_new_box(pw, file_data_new_simple(fd->path), + x, y, 0, 0, + PAN_FOLDER_BOX_OUTLINE_THICKNESS, + PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA, + PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA); + + x += PAN_FOLDER_BOX_BORDER; + y += PAN_FOLDER_BOX_BORDER; + month_start = y; + } + + if (pi_day) x = pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER; + + tc = fd->date; + total = 1; + count = 0; + + needle = work; + while (needle) + { + FileData *nfd; + + nfd = needle->data; + if (date_compare(nfd->date, tc, DATE_LENGTH_DAY)) + { + needle = needle->next; + total++; + } + else + { + needle = NULL; + } + } + + buf = date_value_string(fd->date, DATE_LENGTH_WEEK); + pi = pan_item_new_text(pw, x, y, buf, TEXT_ATTR_NONE, + PAN_TEXT_COLOR, 255); + g_free(buf); + + y += pi->height; + + pi_day = pan_item_new_box(pw, file_data_new_simple(fd->path), x, y, 0, 0, + PAN_FOLDER_BOX_OUTLINE_THICKNESS, + PAN_FOLDER_BOX_COLOR, PAN_FOLDER_BOX_ALPHA, + PAN_FOLDER_BOX_OUTLINE_COLOR, PAN_FOLDER_BOX_OUTLINE_ALPHA); + + x += PAN_FOLDER_BOX_BORDER; + y += PAN_FOLDER_BOX_BORDER; + day_start = y; + } + + if (pw->size > LAYOUT_SIZE_THUMB_LARGE) + { + pi = pan_item_new_image(pw, fd, x, y, 10, 10); + if (pi->width > x_width) x_width = pi->width; + y_height = pi->height; + } + else + { + pi = pan_item_new_thumb(pw, fd, x, y); + x_width = PAN_THUMB_SIZE; + y_height = PAN_THUMB_SIZE; + } + + pan_item_size_by_item(pi_day, pi, PAN_FOLDER_BOX_BORDER); + pan_item_size_by_item(pi_month, pi_day, PAN_FOLDER_BOX_BORDER); +#if 0 + if (pi_day) + { + if (pi->x + pi->width + PAN_FOLDER_BOX_BORDER > pi_day->x + pi_day->width) + pi_day->width = pi->x + pi->width + PAN_FOLDER_BOX_BORDER - pi_day->x; + if (pi->y + pi->height + PAN_FOLDER_BOX_BORDER > pi_day->y + pi_day->height) + pi_day->height = pi->y + pi->height + PAN_FOLDER_BOX_BORDER - pi_day->y; + } + + if (pi_month && pi_day) + { + if (pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER > pi_month->x + pi_month->width) + pi_month->width = pi_day->x + pi_day->width + PAN_FOLDER_BOX_BORDER - pi_month->x; + if (pi_day->y + pi_day->height + PAN_FOLDER_BOX_BORDER > pi_month->y + pi_month->height) + pi_month->height = pi_day->y + pi_day->height + PAN_FOLDER_BOX_BORDER - pi_month->y; + } +#endif + + total--; + count++; + + if (total > 0 && count < PAN_GROUP_MAX) + { + y += y_height + PAN_THUMB_GAP; + } + else + { + x += x_width + PAN_THUMB_GAP; + x_width = 0; + count = 0; + + if (total > 0) + y = day_start; + else + y = month_start; + } + + if (w < pi->x + pi->width + PAN_THUMB_GAP) w = pi->x + pi->width + PAN_THUMB_GAP; + if (h < pi->y + pi->height + PAN_THUMB_GAP) h = pi->y + pi->height + PAN_THUMB_GAP; + } + + w += PAN_FOLDER_BOX_BORDER; + h += PAN_FOLDER_BOX_BORDER; + + if (width) *width = w; + if (height) *height = h; + + g_list_free(list); +} + +static void pan_window_layout_compute(PanWindow *pw, const gchar *path, + gint *width, gint *height, + gint *scroll_x, gint *scroll_y) +{ + pan_window_items_free(pw); + + switch (pw->size) + { + case LAYOUT_SIZE_THUMB_NONE: + pw->thumb_size = PAN_THUMB_SIZE_NONE; + pw->thumb_gap = PAN_THUMB_GAP_SMALL; + break; + case LAYOUT_SIZE_THUMB_SMALL: + pw->thumb_size = PAN_THUMB_SIZE_SMALL; + pw->thumb_gap = PAN_THUMB_GAP_SMALL; + break; + case LAYOUT_SIZE_THUMB_NORMAL: + default: + pw->thumb_size = PAN_THUMB_SIZE_NORMAL; + pw->thumb_gap = PAN_THUMB_GAP_NORMAL; + break; + case LAYOUT_SIZE_THUMB_LARGE: + pw->thumb_size = PAN_THUMB_SIZE_LARGE; + pw->thumb_gap = PAN_THUMB_GAP_LARGE; + break; + case LAYOUT_SIZE_10: + pw->image_size = 10; + pw->thumb_gap = PAN_THUMB_GAP_NORMAL; + break; + case LAYOUT_SIZE_25: + pw->image_size = 25; + pw->thumb_gap = PAN_THUMB_GAP_NORMAL; + break; + case LAYOUT_SIZE_33: + pw->image_size = 33; + pw->thumb_gap = PAN_THUMB_GAP_LARGE; + break; + case LAYOUT_SIZE_50: + pw->image_size = 50; + pw->thumb_gap = PAN_THUMB_GAP_HUGE; + break; + case LAYOUT_SIZE_100: + pw->image_size = 100; + pw->thumb_gap = PAN_THUMB_GAP_HUGE; + break; + } + + *width = 0; + *height = 0; + *scroll_x = 0; + *scroll_y = 0; + + switch (pw->layout) + { + case LAYOUT_GRID: + default: + pan_window_layout_compute_grid(pw, path, width, height); + break; + case LAYOUT_FOLDERS_LINEAR: + pan_window_layout_compute_folders_linear(pw, path, width, height); + break; + case LAYOUT_FOLDERS_FLOWER: + pan_window_layout_compute_folders_flower(pw, path, width, height, scroll_x, scroll_y); + break; + case LAYOUT_TIMELINE: + pan_window_layout_compute_timeline(pw, path, width, height); + break; + } + + pan_cache_free(pw); + + printf("computed %d objects\n", g_list_length(pw->list)); +} + +static GList *pan_layout_intersect(PanWindow *pw, gint x, gint y, gint width, gint height) +{ + GList *list = NULL; + GList *work; + + work = pw->list; + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->next; + + if (util_clip_region_test(x, y, width, height, + pi->x, pi->y, pi->width, pi->height)) + { + list = g_list_prepend(list, pi); + } + } + + return list; +} + + + +/* + *----------------------------------------------------------------------------- + * tile generation + *----------------------------------------------------------------------------- + */ + +static gint pan_layout_queue_step(PanWindow *pw); + + +static void pan_layout_queue_thumb_done_cb(ThumbLoader *tl, gpointer data) +{ + PanWindow *pw = data; + + if (pw->queue_pi) + { + PanItem *pi; + gint rc; + + pi = pw->queue_pi; + pw->queue_pi = NULL; + + pi->queued = FALSE; + + if (pi->pixbuf) g_object_unref(pi->pixbuf); + pi->pixbuf = thumb_loader_get_pixbuf(tl, TRUE); + + rc = pi->refcount; + image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height); + pi->refcount = rc; + } + + thumb_loader_free(pw->tl); + pw->tl = NULL; + + while (pan_layout_queue_step(pw)); +} + +static void pan_layout_queue_image_done_cb(ImageLoader *il, gpointer data) +{ + PanWindow *pw = data; + + if (pw->queue_pi) + { + PanItem *pi; + gint rc; + + pi = pw->queue_pi; + pw->queue_pi = NULL; + + pi->queued = FALSE; + + if (pi->pixbuf) g_object_unref(pi->pixbuf); + pi->pixbuf = image_loader_get_pixbuf(pw->il); + if (pi->pixbuf) g_object_ref(pi->pixbuf); + + if (pi->pixbuf && pw->size != LAYOUT_SIZE_100 && + (gdk_pixbuf_get_width(pi->pixbuf) > pi->width || + gdk_pixbuf_get_height(pi->pixbuf) > pi->height)) + { + GdkPixbuf *tmp; + + tmp = pi->pixbuf; + pi->pixbuf = gdk_pixbuf_scale_simple(tmp, pi->width, pi->height, + (GdkInterpType)zoom_quality); + g_object_unref(tmp); + } + + rc = pi->refcount; + image_area_changed(pw->imd, pi->x, pi->y, pi->width, pi->height); + pi->refcount = rc; + } + + image_loader_free(pw->il); + pw->il = NULL; + + while (pan_layout_queue_step(pw)); +} + +#if 0 +static void pan_layout_queue_image_area_cb(ImageLoader *il, guint x, guint y, + guint width, guint height, gpointer data) +{ + PanWindow *pw = data; + + if (pw->queue_pi) + { + PanItem *pi; + gint rc; + + pi = pw->queue_pi; + + if (!pi->pixbuf) + { + pi->pixbuf = image_loader_get_pixbuf(pw->il); + if (pi->pixbuf) g_object_ref(pi->pixbuf); + } + + rc = pi->refcount; + image_area_changed(pw->imd, pi->x + x, pi->y + y, width, height); + pi->refcount = rc; + } +} +#endif + +static gint pan_layout_queue_step(PanWindow *pw) +{ + PanItem *pi; + + if (!pw->queue) return FALSE; + + pi = pw->queue->data; + pw->queue = g_list_remove(pw->queue, pi); + pw->queue_pi = pi; + + image_loader_free(pw->il); + pw->il = NULL; + thumb_loader_free(pw->tl); + pw->tl = NULL; + + if (pi->type == ITEM_IMAGE) + { + pw->il = image_loader_new(pi->fd->path); + + if (pw->size != LAYOUT_SIZE_100) + { + image_loader_set_requested_size(pw->il, pi->width, pi->height); + } + +#if 0 + image_loader_set_area_ready_func(pw->il, pan_layout_queue_image_area_cb, pw); +#endif + image_loader_set_error_func(pw->il, pan_layout_queue_image_done_cb, pw); + + if (image_loader_start(pw->il, pan_layout_queue_image_done_cb, pw)) return FALSE; + + image_loader_free(pw->il); + pw->il = NULL; + } + else if (pi->type == ITEM_THUMB) + { + pw->tl = thumb_loader_new(PAN_THUMB_SIZE, PAN_THUMB_SIZE); + + if (!pw->tl->standard_loader) + { + /* The classic loader will recreate a thumbnail any time we + * request a different size than what exists. This view will + * almost never use the user configured sizes so disable cache. + */ + thumb_loader_set_cache(pw->tl, FALSE, FALSE, FALSE); + } + + thumb_loader_set_callbacks(pw->tl, + pan_layout_queue_thumb_done_cb, + pan_layout_queue_thumb_done_cb, + NULL, pw); + + if (thumb_loader_start(pw->tl, pi->fd->path)) return FALSE; + + thumb_loader_free(pw->tl); + pw->tl = NULL; + } + + pw->queue_pi->queued = FALSE; + pw->queue_pi = NULL; + return TRUE; +} + +static void pan_layout_queue(PanWindow *pw, PanItem *pi) +{ + if (!pi || pi->queued || pi->pixbuf) return; + if (pw->size == LAYOUT_SIZE_THUMB_NONE) return; + + pi->queued = TRUE; + pw->queue = g_list_prepend(pw->queue, pi); + + if (!pw->tl && !pw->il) while(pan_layout_queue_step(pw)); +} + +static gint pan_window_request_tile_cb(ImageWindow *imd, gint x, gint y, gint width, gint height, + GdkPixbuf *pixbuf, gpointer data) +{ + PanWindow *pw = data; + GList *list; + GList *work; + gint i; + + pixbuf_draw_rect_fill(pixbuf, + 0, 0, width, height, + PAN_BACKGROUND_COLOR, 255); + + for (i = (x / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < x + width; i += PAN_GRID_SIZE) + { + gint rx, ry, rw, rh; + + if (util_clip_region(x, y, width, height, + i, y, 1, height, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_GRID_COLOR, PAN_GRID_ALPHA); + } + } + for (i = (y / PAN_GRID_SIZE) * PAN_GRID_SIZE; i < y + height; i += PAN_GRID_SIZE) + { + gint rx, ry, rw, rh; + + if (util_clip_region(x, y, width, height, + x, i, width, 1, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_GRID_COLOR, PAN_GRID_ALPHA); + } + } + + list = pan_layout_intersect(pw, x, y, width, height); + work = list; + while (work) + { + PanItem *pi; + gint tx, ty, tw, th; + gint rx, ry, rw, rh; + + pi = work->data; + work = work->next; + + pi->refcount++; + + if (pi->type == ITEM_THUMB && pi->pixbuf) + { + tw = gdk_pixbuf_get_width(pi->pixbuf); + th = gdk_pixbuf_get_height(pi->pixbuf); + + tx = pi->x + (pi->width - tw) / 2; + ty = pi->y + (pi->height - th) / 2; + + if (gdk_pixbuf_get_has_alpha(pi->pixbuf)) + { + if (util_clip_region(x, y, width, height, + tx + PAN_SHADOW_OFFSET, ty + PAN_SHADOW_OFFSET, tw, th, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_shadow(pixbuf, + rx - x, ry - y, rw, rh, + tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th, + PAN_SHADOW_FADE, + PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA); + } + } + else + { + if (util_clip_region(x, y, width, height, + tx + tw, ty + PAN_SHADOW_OFFSET, + PAN_SHADOW_OFFSET, th - PAN_SHADOW_OFFSET, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_shadow(pixbuf, + rx - x, ry - y, rw, rh, + tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th, + PAN_SHADOW_FADE, + PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA); + } + if (util_clip_region(x, y, width, height, + tx + PAN_SHADOW_OFFSET, ty + th, tw, PAN_SHADOW_OFFSET, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_shadow(pixbuf, + rx - x, ry - y, rw, rh, + tx + PAN_SHADOW_OFFSET - x, ty + PAN_SHADOW_OFFSET - y, tw, th, + PAN_SHADOW_FADE, + PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA); + } + } + + if (util_clip_region(x, y, width, height, + tx, ty, tw, th, + &rx, &ry, &rw, &rh)) + { + gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh, + (double) tx - x, + (double) ty - y, + 1.0, 1.0, GDK_INTERP_NEAREST, + 255); + } + + if (util_clip_region(x, y, width, height, + tx, ty, tw, PAN_OUTLINE_THICKNESS, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA); + } + if (util_clip_region(x, y, width, height, + tx, ty, PAN_OUTLINE_THICKNESS, th, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_OUTLINE_COLOR_1, PAN_OUTLINE_ALPHA); + } + if (util_clip_region(x, y, width, height, + tx + tw - PAN_OUTLINE_THICKNESS, ty + PAN_OUTLINE_THICKNESS, + PAN_OUTLINE_THICKNESS, th - PAN_OUTLINE_THICKNESS, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA); + } + if (util_clip_region(x, y, width, height, + tx + PAN_OUTLINE_THICKNESS, ty + th - PAN_OUTLINE_THICKNESS, + tw - PAN_OUTLINE_THICKNESS * 2, PAN_OUTLINE_THICKNESS, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_OUTLINE_COLOR_2, PAN_OUTLINE_ALPHA); + } + } + else if (pi->type == ITEM_THUMB) + { + tw = pi->width - PAN_SHADOW_OFFSET * 2; + th = pi->height - PAN_SHADOW_OFFSET * 2; + tx = pi->x + PAN_SHADOW_OFFSET; + ty = pi->y + PAN_SHADOW_OFFSET; + + if (util_clip_region(x, y, width, height, + tx, ty, tw, th, + &rx, &ry, &rw, &rh)) + { + gint d; + + d = (pw->size == LAYOUT_SIZE_THUMB_NONE) ? 2 : 8; + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_SHADOW_COLOR, + PAN_SHADOW_ALPHA / d); + } + + pan_layout_queue(pw, pi); + } + else if (pi->type == ITEM_IMAGE) + { + if (util_clip_region(x, y, width, height, + pi->x, pi->y, pi->width, pi->height, + &rx, &ry, &rw, &rh)) + { + if (pi->pixbuf) + { + gdk_pixbuf_composite(pi->pixbuf, pixbuf, rx - x, ry - y, rw, rh, + (double) pi->x - x, + (double) pi->y - y, + 1.0, 1.0, GDK_INTERP_NEAREST, + 255); + } + else + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA / 2); + pan_layout_queue(pw, pi); + } + } + } + else if (pi->type == ITEM_BOX) + { + gint bw, bh; + gint *shadow; + + bw = pi->width; + bh = pi->height; + + shadow = pi->data; + if (shadow) + { + bw -= shadow[0]; + bh -= shadow[0]; + + if (pi->color_a > 254) + { + pixbuf_draw_shadow(pixbuf, pi->x - x + bw, pi->y - y + shadow[0], + shadow[0], bh - shadow[0], + pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh, + shadow[1], + PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA); + pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + bh, + bw, shadow[0], + pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh, + shadow[1], + PAN_SHADOW_COLOR, PAN_SHADOW_ALPHA); + } + else + { + gint a; + a = pi->color_a * PAN_SHADOW_ALPHA >> 8; + pixbuf_draw_shadow(pixbuf, pi->x - x + shadow[0], pi->y - y + shadow[0], + bw, bh, + pi->x - x + shadow[0], pi->y - y + shadow[0], bw, bh, + shadow[1], + PAN_SHADOW_COLOR, a); + } + } + + if (util_clip_region(x, y, width, height, + pi->x, pi->y, bw, bh, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + pi->color_r, pi->color_g, pi->color_b, pi->color_a); + } + if (util_clip_region(x, y, width, height, + pi->x, pi->y, bw, pi->border, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + if (util_clip_region(x, y, width, height, + pi->x, pi->y + pi->border, pi->border, bh - pi->border * 2, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + if (util_clip_region(x, y, width, height, + pi->x + bw - pi->border, pi->y + pi->border, + pi->border, bh - pi->border * 2, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + if (util_clip_region(x, y, width, height, + pi->x, pi->y + bh - pi->border, + bw, pi->border, + &rx, &ry, &rw, &rh)) + { + pixbuf_draw_rect_fill(pixbuf, + rx - x, ry - y, rw, rh, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + } + else if (pi->type == ITEM_TRIANGLE) + { + if (util_clip_region(x, y, width, height, + pi->x, pi->y, pi->width, pi->height, + &rx, &ry, &rw, &rh) && pi->data) + { + gint *coord = pi->data; + pixbuf_draw_triangle(pixbuf, + rx - x, ry - y, rw, rh, + coord[0] - x, coord[1] - y, + coord[2] - x, coord[3] - y, + coord[4] - x, coord[5] - y, + pi->color_r, pi->color_g, pi->color_b, pi->color_a); + + if (pi->border & BORDER_1) + { + pixbuf_draw_line(pixbuf, + rx - x, ry - y, rw, rh, + coord[0] - x, coord[1] - y, + coord[2] - x, coord[3] - y, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + if (pi->border & BORDER_2) + { + pixbuf_draw_line(pixbuf, + rx - x, ry - y, rw, rh, + coord[2] - x, coord[3] - y, + coord[4] - x, coord[5] - y, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + if (pi->border & BORDER_3) + { + pixbuf_draw_line(pixbuf, + rx - x, ry - y, rw, rh, + coord[4] - x, coord[5] - y, + coord[0] - x, coord[1] - y, + pi->color2_r, pi->color2_g, pi->color2_b, pi->color2_a); + } + } + } + else if (pi->type == ITEM_TEXT && pi->text) + { + PangoLayout *layout; + + layout = pan_item_text_layout(pi, imd->image); + pixbuf_draw_layout(pixbuf, layout, imd->image, + pi->x - x + PAN_TEXT_BORDER_SIZE, pi->y - y + PAN_TEXT_BORDER_SIZE, + pi->color_r, pi->color_g, pi->color_b, pi->color_a); + g_object_unref(G_OBJECT(layout)); + } + } + g_list_free(list); + + if (0) + { + static gint count = 0; + PangoLayout *layout; + gint lx, ly; + gint lw, lh; + GdkPixmap *pixmap; + gchar *buf; + + layout = gtk_widget_create_pango_layout(imd->image, NULL); + + buf = g_strdup_printf("%d,%d\n(#%d)", x, y, + (x / imd->source_tile_width) + + (y / imd->source_tile_height * (imd->image_width/imd->source_tile_width + 1))); + pango_layout_set_text(layout, buf, -1); + g_free(buf); + + pango_layout_get_pixel_size(layout, &lw, &lh); + + pixmap = gdk_pixmap_new(imd->widget->window, lw, lh, -1); + gdk_draw_rectangle(pixmap, imd->widget->style->black_gc, TRUE, 0, 0, lw, lh); + gdk_draw_layout(pixmap, imd->widget->style->white_gc, 0, 0, layout); + g_object_unref(G_OBJECT(layout)); + + lx = MAX(0, width / 2 - lw / 2); + ly = MAX(0, height / 2 - lh / 2); + lw = MIN(lw, width - lx); + lh = MIN(lh, height - ly); + gdk_pixbuf_get_from_drawable(pixbuf, pixmap, gdk_drawable_get_colormap(imd->image->window), + 0, 0, lx, ly, lw, lh); + g_object_unref(pixmap); + + count++; + } + + return TRUE; +} + +static void pan_window_dispose_tile_cb(ImageWindow *imd, gint x, gint y, gint width, gint height, + GdkPixbuf *pixbuf, gpointer data) +{ + PanWindow *pw = data; + GList *list; + GList *work; + + list = pan_layout_intersect(pw, x, y, width, height); + work = list; + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->next; + + if (pi->refcount > 0) + { + pi->refcount--; + + if ((pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE) && + pi->refcount == 0) + { + if (pi->queued) + { + pw->queue = g_list_remove(pw->queue, pi); + pi->queued = FALSE; + } + if (pw->queue_pi == pi) pw->queue_pi = NULL; + if (pi->pixbuf) + { + g_object_unref(pi->pixbuf); + pi->pixbuf = NULL; + } + } + } + } + + g_list_free(list); +} + + +/* + *----------------------------------------------------------------------------- + * misc + *----------------------------------------------------------------------------- + */ + +static void pan_window_message(PanWindow *pw, const gchar *text) +{ + GList *work; + gint count = 0; + gint64 size = 0; + gchar *ss; + gchar *buf; + + if (text) + { + gtk_label_set_text(GTK_LABEL(pw->label_message), text); + return; + } + + work = pw->list; + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->next; + + if (pi->fd && + (pi->type == ITEM_THUMB || pi->type == ITEM_IMAGE)) + { + size += pi->fd->size; + count++; + } + } + + ss = text_from_size_abrev(size); + buf = g_strdup_printf(_("%d images, %s"), count, ss); + g_free(ss); + gtk_label_set_text(GTK_LABEL(pw->label_message), buf); + g_free(buf); +} + +static ImageWindow *pan_window_active_image(PanWindow *pw) +{ + if (pw->fs) return pw->fs->imd; + + return pw->imd; +} + +static void pan_window_zoom_limit(PanWindow *pw) +{ + gdouble min; + + switch (pw->size) + { + case LAYOUT_SIZE_THUMB_NONE: + case LAYOUT_SIZE_THUMB_SMALL: + case LAYOUT_SIZE_THUMB_NORMAL: +#if 0 + /* easily requires > 512mb ram when window size > 1024x768 and zoom is <= -8 */ + min = -16.0; + break; +#endif + case LAYOUT_SIZE_THUMB_LARGE: + min = -6.0; + break; + case LAYOUT_SIZE_10: + case LAYOUT_SIZE_25: + min = -4.0; + break; + case LAYOUT_SIZE_33: + case LAYOUT_SIZE_50: + case LAYOUT_SIZE_100: + default: + min = -2.0; + break; + } + + image_zoom_set_limits(pw->imd, min, 32.0); +} + +static gint pan_window_layout_update_idle_cb(gpointer data) +{ + PanWindow *pw = data; + gint width; + gint height; + gint scroll_x; + gint scroll_y; + + if (pw->size > LAYOUT_SIZE_THUMB_LARGE) + { + if (!pw->cache_list && !pw->cache_todo) + { + pan_cache_fill(pw, pw->path); + if (pw->cache_todo) + { + pan_window_message(pw, _("Reading dimensions...")); + return TRUE; + } + } + if (pan_cache_step(pw)) + { + pw->cache_count++; + pw->cache_tick++; + if (pw->cache_count == pw->cache_total) + { + pan_window_message(pw, _("Sorting images...")); + } + else if (pw->cache_tick > 9) + { + gchar *buf; + + buf = g_strdup_printf("%s %d", _("Reading dimensions..."), + pw->cache_total - pw->cache_count); + pan_window_message(pw, buf); + g_free(buf); + + pw->cache_tick = 0; + } + + return TRUE; + } + } + + pan_window_layout_compute(pw, pw->path, &width, &height, &scroll_x, &scroll_y); + + pan_window_zoom_limit(pw); + + if (width > 0 && height > 0) + { + image_set_image_as_tiles(pw->imd, width, height, + PAN_TILE_SIZE, PAN_TILE_SIZE, 8, + pan_window_request_tile_cb, + pan_window_dispose_tile_cb, pw, 1.0); + image_scroll_to_point(pw->imd, scroll_x, scroll_y); + } + + pan_window_message(pw, NULL); + + pw->idle_id = -1; + + return FALSE; +} + +static void pan_window_layout_update_idle(PanWindow *pw) +{ + if (pw->idle_id == -1) + { + pan_window_message(pw, _("Sorting images...")); + pw->idle_id = g_idle_add(pan_window_layout_update_idle_cb, pw); + } +} + +/* + *----------------------------------------------------------------------------- + * pan window keyboard + *----------------------------------------------------------------------------- + */ + +static const gchar *pan_menu_click_path(PanWindow *pw) +{ + if (pw->click_pi && pw->click_pi->fd) return pw->click_pi->fd->path; + return NULL; +} + +static void pan_window_menu_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data) +{ + PanWindow *pw = data; + ImageWindow *imd; + + imd = pan_window_active_image(pw); + gdk_window_get_origin(imd->image->window, x, y); + popup_menu_position_clamp(menu, x, y, 0); +} + +static gint pan_window_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + PanWindow *pw = data; + ImageWindow *imd; + const gchar *path; + gint stop_signal = FALSE; + GtkWidget *menu; + gint x = 0; + gint y = 0; + gint focused; + + focused = (pw->fs || GTK_WIDGET_HAS_FOCUS(pw->imd->widget)); + + imd = pan_window_active_image(pw); + path = pan_menu_click_path(pw); + + if (focused) + { + switch (event->keyval) + { + case GDK_Left: case GDK_KP_Left: + x -= 1; + stop_signal = TRUE; + break; + case GDK_Right: case GDK_KP_Right: + x += 1; + stop_signal = TRUE; + break; + case GDK_Up: case GDK_KP_Up: + y -= 1; + stop_signal = TRUE; + break; + case GDK_Down: case GDK_KP_Down: + y += 1; + stop_signal = TRUE; + break; + case GDK_Page_Up: case GDK_KP_Page_Up: + image_scroll(imd, 0, 0-imd->vis_height / 2); + break; + case GDK_Page_Down: case GDK_KP_Page_Down: + image_scroll(imd, 0, imd->vis_height / 2); + break; + case GDK_Home: case GDK_KP_Home: + image_scroll(imd, 0-imd->vis_width / 2, 0); + break; + case GDK_End: case GDK_KP_End: + image_scroll(imd, imd->vis_width / 2, 0); + break; + } + } + + if (focused && !(event->state & GDK_CONTROL_MASK) ) + switch (event->keyval) + { + case '+': case '=': case GDK_KP_Add: + image_zoom_adjust(imd, ZOOM_INCREMENT); + break; + case '-': case GDK_KP_Subtract: + image_zoom_adjust(imd, -ZOOM_INCREMENT); + break; + case 'Z': case 'z': case GDK_KP_Divide: case '1': + image_zoom_set(imd, 1.0); + break; + case '2': + image_zoom_set(imd, 2.0); + break; + case '3': + image_zoom_set(imd, 3.0); + break; + case '4': + image_zoom_set(imd, 4.0); + break; + case '7': + image_zoom_set(imd, -4.0); + break; + case '8': + image_zoom_set(imd, -3.0); + break; + case '9': + image_zoom_set(imd, -2.0); + break; + case 'F': case 'f': + case 'V': case 'v': + pan_fullscreen_toggle(pw, FALSE); + stop_signal = TRUE; + break; + case 'I': case 'i': + pan_overlay_toggle(pw); + break; + case GDK_Delete: case GDK_KP_Delete: + break; + case '/': + if (!pw->fs) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), TRUE); + stop_signal = TRUE; + } + break; + case GDK_Escape: + if (pw->fs) + { + pan_fullscreen_toggle(pw, TRUE); + stop_signal = TRUE; + } + else if (GTK_WIDGET_VISIBLE(pw->search_entry)) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE); + stop_signal = TRUE; + } + break; + case GDK_Menu: + case GDK_F10: + menu = pan_popup_menu(pw); + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, pan_window_menu_pos_cb, pw, 0, GDK_CURRENT_TIME); + stop_signal = TRUE; + break; + } + + if (event->state & GDK_CONTROL_MASK) + { + gint n = -1; + switch (event->keyval) + { + case '1': + n = 0; + break; + case '2': + n = 1; + break; + case '3': + n = 2; + break; + case '4': + n = 3; + break; + case '5': + n = 4; + break; + case '6': + n = 5; + break; + case '7': + n = 6; + break; + case '8': + n = 7; + break; + case '9': + n = 8; + break; + case '0': + n = 9; + break; + case 'C': case 'c': + if (path) file_util_copy(path, NULL, NULL, imd->widget); + break; + case 'M': case 'm': + if (path) file_util_move(path, NULL, NULL, imd->widget); + break; + case 'R': case 'r': + if (path) file_util_rename(path, NULL, imd->widget); + break; + case 'D': case 'd': + if (path) file_util_delete(path, NULL, imd->widget); + break; + case 'P': case 'p': + if (path) info_window_new(path, NULL); + break; + case 'W': case 'w': + pan_window_close(pw); + break; + } + if (n != -1 && path) + { + pan_fullscreen_toggle(pw, TRUE); + start_editor_from_file(n, path); + stop_signal = TRUE; + } + } + else if (event->state & GDK_SHIFT_MASK) + { + x *= 3; + y *= 3; + } + else if (!focused) + { + switch (event->keyval) + { + case GDK_Escape: + if (pw->fs) + { + pan_fullscreen_toggle(pw, TRUE); + stop_signal = TRUE; + } + else if (GTK_WIDGET_HAS_FOCUS(pw->search_entry)) + { + gtk_widget_grab_focus(pw->imd->widget); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(pw->search_button), FALSE); + stop_signal = TRUE; + } + break; + default: + break; + } + } + + if (x != 0 || y!= 0) + { + keyboard_scroll_calc(&x, &y, event); + image_scroll(imd, x, y); + } + + return stop_signal; +} + +/* + *----------------------------------------------------------------------------- + * info popup + *----------------------------------------------------------------------------- + */ + +static void pan_info_update(PanWindow *pw, PanItem *pi) +{ + PanItem *pbox; + PanItem *plabel; + PanItem *p; + gchar *buf; + gint x1, y1, x2, y2, x3, y3; + gint x, y, w, h; + + if (pw->click_pi == pi) return; + if (pi && !pi->fd) pi = NULL; + + while ((p = pan_item_find_by_key(pw, ITEM_NONE, "info"))) pan_item_remove(pw, p); + pw->click_pi = pi; + + if (!pi) return; + + printf("info set to %s\n", pi->fd->path); + + pbox = pan_item_new_box(pw, NULL, pi->x + pi->width + 4, pi->y, 10, 10, + PAN_POPUP_BORDER, + PAN_POPUP_COLOR, PAN_POPUP_ALPHA, + PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA); + pan_item_set_key(pbox, "info"); + + if (pi->type == ITEM_THUMB && pi->pixbuf) + { + w = gdk_pixbuf_get_width(pi->pixbuf); + h = gdk_pixbuf_get_height(pi->pixbuf); + + x1 = pi->x + pi->width - (pi->width - w) / 2 - 8; + y1 = pi->y + (pi->height - h) / 2 + 8; + } + else + { + x1 = pi->x + pi->width - 8; + y1 = pi->y + 8; + } + + x2 = pbox->x + 1; + y2 = pbox->y + 36; + x3 = pbox->x + 1; + y3 = pbox->y + 12; + triangle_rect_region(x1, y1, x2, y2, x3, y3, + &x, &y, &w, &h); + + p = pan_item_new_tri(pw, NULL, x, y, w, h, + x1, y1, x2, y2, x3, y3, + PAN_POPUP_COLOR, PAN_POPUP_ALPHA); + pan_item_tri_border(p, BORDER_1 | BORDER_3, PAN_POPUP_BORDER_COLOR, PAN_POPUP_ALPHA); + pan_item_set_key(p, "info"); + pan_item_added(pw, p); + + plabel = pan_item_new_text(pw, pbox->x, pbox->y, + _("Filename:"), TEXT_ATTR_BOLD, + PAN_POPUP_TEXT_COLOR, 255); + pan_item_set_key(plabel, "info"); + p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y, + pi->fd->name, TEXT_ATTR_NONE, + PAN_POPUP_TEXT_COLOR, 255); + pan_item_set_key(p, "info"); + pan_item_size_by_item(pbox, p, 0); + + plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height, + _("Date:"), TEXT_ATTR_BOLD, + PAN_POPUP_TEXT_COLOR, 255); + pan_item_set_key(plabel, "info"); + p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y, + text_from_time(pi->fd->date), TEXT_ATTR_NONE, + PAN_POPUP_TEXT_COLOR, 255); + pan_item_set_key(p, "info"); + pan_item_size_by_item(pbox, p, 0); + + plabel = pan_item_new_text(pw, plabel->x, plabel->y + plabel->height, + _("Size:"), TEXT_ATTR_BOLD, + PAN_POPUP_TEXT_COLOR, 255); + pan_item_set_key(plabel, "info"); + buf = text_from_size(pi->fd->size); + p = pan_item_new_text(pw, plabel->x + plabel->width, plabel->y, + buf, TEXT_ATTR_NONE, + PAN_POPUP_TEXT_COLOR, 255); + g_free(buf); + pan_item_set_key(p, "info"); + pan_item_size_by_item(pbox, p, 0); + + pan_item_box_shadow(pbox, PAN_SHADOW_OFFSET * 2, PAN_SHADOW_FADE * 2); + pan_item_added(pw, pbox); +} + + +/* + *----------------------------------------------------------------------------- + * search + *----------------------------------------------------------------------------- + */ + +static void pan_search_status(PanWindow *pw, const gchar *text) +{ + gtk_label_set_text(GTK_LABEL(pw->search_label), (text) ? text : ""); +} + +static gint pan_search_by_path(PanWindow *pw, const gchar *path) +{ + PanItem *pi; + ItemType type; + + type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB; + + pi = pan_item_find_by_path(pw, type, path, FALSE, FALSE); + if (!pi) return FALSE; + + pan_info_update(pw, pi); + image_scroll_to_point(pw->imd, pi->x - PAN_THUMB_GAP, pi->y - PAN_THUMB_GAP); + + pan_search_status(pw, (path[0] == '/') ? _("path found") : _("filename found")); + + return TRUE; +} + +static gint pan_search_by_partial(PanWindow *pw, const gchar *text) +{ + PanItem *pi; + ItemType type; + + type = (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB; + + pi = pan_item_find_by_path(pw, type, text, TRUE, FALSE); + if (!pi) pi = pan_item_find_by_path(pw, type, text, FALSE, TRUE); + if (!pi) + { + gchar *needle; + + needle = g_utf8_strdown(text, -1); + pi = pan_item_find_by_path(pw, type, needle, TRUE, TRUE); + g_free(needle); + } + if (!pi) return FALSE; + + pan_info_update(pw, pi); + image_scroll_to_point(pw->imd, pi->x - PAN_THUMB_GAP, pi->y - PAN_THUMB_GAP); + + pan_search_status(pw, _("partial match")); + + return TRUE; +} + +static gint valid_date_separator(gchar c) +{ + return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ','); +} + +static PanItem *pan_search_by_date_val(PanWindow *pw, gint year, gint month, gint day) +{ + GList *work; + + work = g_list_last(pw->list); + while (work) + { + PanItem *pi; + + pi = work->data; + work = work->prev; + + if (pi->fd) + { + struct tm *tl; + + tl = localtime(&pi->fd->date); + if (tl) + { + gint match; + + match = (tl->tm_year == year - 1900); + if (match && month >= 0) match = (tl->tm_mon == month - 1); + if (match && day > 0) match = (tl->tm_mday == day); + + if (match) return pi; + } + } + } + + return NULL; +} + +static gint pan_search_by_date(PanWindow *pw, const gchar *text) +{ + PanItem *pi; + gint year; + gint month = -1; + gint day = -1; + gchar *ptr; + gchar *mptr; + struct tm *lt; + time_t t; + gchar *message; + gchar *buf; + + if (!text) return FALSE; + + ptr = (gchar *)text; + while (*ptr != '\0') + { + if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE; + ptr++; + } + + t = time(NULL); + if (t == -1) return FALSE; + lt = localtime(&t); + if (!lt) return FALSE; + + if (valid_date_separator(*text)) + { + year = -1; + mptr = (gchar *)text; + } + else + { + year = (gint)strtol(text, &mptr, 10); + if (mptr == text) return FALSE; + } + + if (*mptr != '\0' && valid_date_separator(*mptr)) + { + gchar *dptr; + + mptr++; + month = strtol(mptr, &dptr, 10); + if (dptr == mptr) + { + if (valid_date_separator(*dptr)) + { + month = lt->tm_mon + 1; + dptr++; + } + else + { + month = -1; + } + } + if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr)) + { + gchar *eptr; + dptr++; + day = strtol(dptr, &eptr, 10); + if (dptr == eptr) + { + day = lt->tm_mday; + } + } + } + + if (year == -1) + { + year = lt->tm_year + 1900; + } + else if (year < 100) + { + if (year > 70) + year+= 1900; + else + year+= 2000; + } + + if (year < 1970 || + month < -1 || month == 0 || month > 12 || + day < -1 || day == 0 || day > 31) return FALSE; + + t = date_to_time(year, month, day); + if (t < 0) return FALSE; + + pi = pan_search_by_date_val(pw, year, month, day); + if (pi) + { + pan_info_update(pw, pi); + image_scroll_to_point(pw->imd, pi->x - PAN_THUMB_GAP, pi->y - PAN_THUMB_GAP); + } + + if (month > 0) + { + buf = date_value_string(t, DATE_LENGTH_MONTH); + if (day > 0) + { + gchar *tmp; + tmp = buf; + buf = g_strdup_printf("%d %s", day, tmp); + g_free(tmp); + } + } + else + { + buf = date_value_string(t, DATE_LENGTH_YEAR); + } + message = g_strdup_printf("%s%s%s%s %s", + (pi) ? "" : "(", (pi) ? "" : _("no match"), (pi) ? "" : ") " , + _("Date:"), buf); + g_free(buf); + pan_search_status(pw, message); + g_free(message); + + return TRUE; +} + +static void pan_search_activate_cb(const gchar *text, gpointer data) +{ + PanWindow *pw = data; + + if (!text) return; + + tab_completion_append_to_history(pw->search_entry, text); + + if (pan_search_by_path(pw, text)) return; + + if (pw->layout == LAYOUT_TIMELINE && pan_search_by_date(pw, text)) return; + + if (pan_search_by_partial(pw, text)) return; + + pan_search_status(pw, _("no match")); +} + +static void pan_search_toggle_cb(GtkWidget *button, gpointer data) +{ + PanWindow *pw = data; + gint visible; + + visible = GTK_WIDGET_VISIBLE(pw->search_box); + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return; + + if (visible) + { + gtk_widget_hide(pw->search_box); + gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE); + } + else + { + gtk_widget_show(pw->search_box); + gtk_arrow_set(GTK_ARROW(pw->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE); + gtk_widget_grab_focus(pw->search_entry); + } +} + + +/* + *----------------------------------------------------------------------------- + * view window main routines + *----------------------------------------------------------------------------- + */ + +static void button_cb(ImageWindow *imd, gint button, guint32 time, + gdouble x, gdouble y, guint state, gpointer data) +{ + PanWindow *pw = data; + PanItem *pi = NULL; + GtkWidget *menu; + + if (pw->imd->scale) + { + pi = pan_item_find_by_coord(pw, (pw->size > LAYOUT_SIZE_THUMB_LARGE) ? ITEM_IMAGE : ITEM_THUMB, + (gint)((double)(pw->imd->x_scroll + x - pw->imd->x_offset) / pw->imd->scale), + (gint)((double)(pw->imd->y_scroll + y - pw->imd->y_offset) / pw->imd->scale)); + } + + switch (button) + { + case 1: + pan_info_update(pw, pi); + break; + case 2: + break; + case 3: + pan_info_update(pw, pi); + menu = pan_popup_menu(pw); + gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, NULL, 3, time); + break; + default: + break; + } +} + +static void scroll_cb(ImageWindow *imd, GdkScrollDirection direction, guint32 time, + gdouble x, gdouble y, guint state, gpointer data) +{ +#if 0 + PanWindow *pw = data; +#endif + + if (state & GDK_CONTROL_MASK) + { + switch (direction) + { + case GDK_SCROLL_UP: + image_zoom_adjust_at_point(imd, ZOOM_INCREMENT, x, y); + break; + case GDK_SCROLL_DOWN: + image_zoom_adjust_at_point(imd, -ZOOM_INCREMENT, x, y); + break; + default: + break; + } + } + else if ( (state & GDK_SHIFT_MASK) != (mousewheel_scrolls)) + { + switch (direction) + { + case GDK_SCROLL_UP: + image_scroll(imd, 0, -MOUSEWHEEL_SCROLL_SIZE); + break; + case GDK_SCROLL_DOWN: + image_scroll(imd, 0, MOUSEWHEEL_SCROLL_SIZE); + break; + case GDK_SCROLL_LEFT: + image_scroll(imd, -MOUSEWHEEL_SCROLL_SIZE, 0); + break; + case GDK_SCROLL_RIGHT: + image_scroll(imd, MOUSEWHEEL_SCROLL_SIZE, 0); + break; + default: + break; + } + } + else + { + switch (direction) + { + case GDK_SCROLL_UP: + break; + case GDK_SCROLL_DOWN: + break; + default: + break; + } + } +} + +static void pan_image_set_buttons(PanWindow *pw, ImageWindow *imd) +{ + image_set_button_func(imd, button_cb, pw); + image_set_scroll_func(imd, scroll_cb, pw); +} + +static void pan_fullscreen_stop_func(FullScreenData *fs, gpointer data) +{ + PanWindow *pw = data; + + pw->fs = NULL; +} + +static void pan_fullscreen_toggle(PanWindow *pw, gint force_off) +{ + if (force_off && !pw->fs) return; + + if (pw->fs) + { + fullscreen_stop(pw->fs); + pw->imd = pw->imd_normal; + } + else + { + pw->fs = fullscreen_start(pw->window, pw->imd, pan_fullscreen_stop_func, pw); + + pan_image_set_buttons(pw, pw->fs->imd); + g_signal_connect(G_OBJECT(pw->fs->window), "key_press_event", + G_CALLBACK(pan_window_key_press_cb), pw); + + pw->imd = pw->fs->imd; + } +} + +static void pan_overlay_toggle(PanWindow *pw) +{ + ImageWindow *imd; + + imd = pan_window_active_image(pw); +#if 0 + if (pw->overlay_id == -1) + { + pw->overlay_id = image_overlay_info_enable(imd); + } + else + { + image_overlay_info_disable(imd, pw->overlay_id); + pw->overlay_id = -1; + } +#endif +} + +static void pan_window_image_update_cb(ImageWindow *imd, gpointer data) +{ + PanWindow *pw = data; + gchar *text; + + text = image_zoom_get_as_text(imd); + gtk_label_set_text(GTK_LABEL(pw->label_zoom), text); + g_free(text); +} + +static void pan_window_image_scroll_notify_cb(ImageWindow *imd, gint x, gint y, + gint width, gint height, gpointer data) +{ + PanWindow *pw = data; + GtkAdjustment *adj; + + adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_h)); + adj->page_size = (gdouble)imd->vis_width / imd->scale; + adj->page_increment = adj->page_size / 2.0; + adj->step_increment = 48.0 / imd->scale; + adj->lower = 0.0; + adj->upper = MAX((gdouble)width + adj->page_size, 1.0); + adj->value = (gdouble)x; + + pref_signal_block_data(pw->scrollbar_h, pw); + gtk_adjustment_changed(adj); + pref_signal_unblock_data(pw->scrollbar_h, pw); + + adj = gtk_range_get_adjustment(GTK_RANGE(pw->scrollbar_v)); + adj->page_size = (gdouble)imd->vis_height / imd->scale; + adj->page_increment = adj->page_size / 2.0; + adj->step_increment = 48.0 / imd->scale; + adj->lower = 0.0; + adj->upper = MAX((gdouble)height + adj->page_size, 1.0); + adj->value = (gdouble)y; + + pref_signal_block_data(pw->scrollbar_v, pw); + gtk_adjustment_changed(adj); + pref_signal_unblock_data(pw->scrollbar_v, pw); + +// printf("scrolled to %d,%d @ %d x %d\n", x, y, width, height); +} + +static void pan_window_scrollbar_h_value_cb(GtkRange *range, gpointer data) +{ + PanWindow *pw = data; + gint x; + + if (!pw->imd->scale) return; + + x = (gint)gtk_range_get_value(range); + + image_scroll_to_point(pw->imd, x, (gint)((gdouble)pw->imd->y_scroll / pw->imd->scale)); +} + +static void pan_window_scrollbar_v_value_cb(GtkRange *range, gpointer data) +{ + PanWindow *pw = data; + gint y; + + if (!pw->imd->scale) return; + + y = (gint)gtk_range_get_value(range); + + image_scroll_to_point(pw->imd, (gint)((gdouble)pw->imd->x_scroll / pw->imd->scale), y); +} + +static void pan_window_layout_change_cb(GtkWidget *combo, gpointer data) +{ + PanWindow *pw = data; + + pw->layout = gtk_combo_box_get_active(GTK_COMBO_BOX(combo)); + pan_window_layout_update_idle(pw); +} + +static void pan_window_layout_size_cb(GtkWidget *combo, gpointer data) +{ + PanWindow *pw = data; + + pw->size = gtk_combo_box_get_active(GTK_COMBO_BOX(combo)); + pan_window_layout_update_idle(pw); +} + +static void pan_window_entry_activate_cb(const gchar *new_text, gpointer data) +{ + PanWindow *pw = data; + gchar *path; + + path = remove_trailing_slash(new_text); + parse_out_relatives(path); + + if (!isdir(path)) + { + warning_dialog(_("Folder not found"), + _("The entered path is not a folder"), + GTK_STOCK_DIALOG_WARNING, pw->path_entry); + return; + } + + tab_completion_append_to_history(pw->path_entry, path); + + g_free(pw->path); + pw->path = g_strdup(path); + + pan_window_layout_update_idle(pw); +} + +static void pan_window_entry_change_cb(GtkWidget *combo, gpointer data) +{ + PanWindow *pw = data; + gchar *text; + + if (gtk_combo_box_get_active(GTK_COMBO_BOX(combo)) < 0) return; + + text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->path_entry))); + pan_window_entry_activate_cb(text, pw); + g_free(text); +} + +static void pan_window_close(PanWindow *pw) +{ + pan_window_list = g_list_remove(pan_window_list, pw); + + if (pw->idle_id != -1) + { + g_source_remove(pw->idle_id); + } + + pan_fullscreen_toggle(pw, TRUE); + gtk_widget_destroy(pw->window); + + pan_window_items_free(pw); + + g_free(pw->path); + + g_free(pw); +} + +static gint pan_window_delete_cb(GtkWidget *w, GdkEventAny *event, gpointer data) +{ + PanWindow *pw = data; + + pan_window_close(pw); + return TRUE; +} + +void pan_window_new(const gchar *path) +{ + PanWindow *pw; + GtkWidget *vbox; + GtkWidget *box; + GtkWidget *combo; + GtkWidget *hbox; + GtkWidget *frame; + GtkWidget *table; + GdkGeometry geometry; + + pw = g_new0(PanWindow, 1); + + pw->path = g_strdup(path); + pw->layout = LAYOUT_TIMELINE; + pw->size = LAYOUT_SIZE_THUMB_NORMAL; + pw->thumb_size = PAN_THUMB_SIZE_NORMAL; + pw->thumb_gap = PAN_THUMB_GAP_NORMAL; + pw->list = NULL; + + pw->fs = NULL; + pw->overlay_id = -1; + pw->idle_id = -1; + + pw->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + + geometry.min_width = 8; + geometry.min_height = 8; + gtk_window_set_geometry_hints(GTK_WINDOW(pw->window), NULL, &geometry, GDK_HINT_MIN_SIZE); + + gtk_window_set_resizable(GTK_WINDOW(pw->window), TRUE); + gtk_window_set_title (GTK_WINDOW(pw->window), "Pan View - GQview"); + gtk_window_set_wmclass(GTK_WINDOW(pw->window), "view", "GQview"); + gtk_container_set_border_width(GTK_CONTAINER(pw->window), 0); + + window_set_icon(pw->window, NULL, NULL); + + vbox = gtk_vbox_new(FALSE, 0); + gtk_container_add(GTK_CONTAINER(pw->window), vbox); + gtk_widget_show(vbox); + + box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE); + + pref_spacer(box, 0); + pref_label_new(box, _("Location:")); + combo = tab_completion_new_with_history(&pw->path_entry, path, "pan_view", -1, + pan_window_entry_activate_cb, pw); + g_signal_connect(G_OBJECT(pw->path_entry->parent), "changed", + G_CALLBACK(pan_window_entry_change_cb), pw); + gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0); + gtk_widget_show(combo); + + combo = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Timeline")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Folders (flower)")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Grid")); + + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->layout); + g_signal_connect(G_OBJECT(combo), "changed", + G_CALLBACK(pan_window_layout_change_cb), pw); + gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0); + gtk_widget_show(combo); + + combo = gtk_combo_box_new_text(); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("No Images")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Small Thumbnails")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Normal Thumbnails")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("Large Thumbnails")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:10 (10%)")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:4 (25%)")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:3 (33%)")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:2 (50%)")); + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), _("1:1 (100%)")); + + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), pw->size); + g_signal_connect(G_OBJECT(combo), "changed", + G_CALLBACK(pan_window_layout_size_cb), pw); + gtk_box_pack_start(GTK_BOX(box), combo, FALSE, FALSE, 0); + gtk_widget_show(combo); + + table = pref_table_new(vbox, 2, 2, FALSE, TRUE); + gtk_table_set_row_spacings(GTK_TABLE(table), 2); + gtk_table_set_col_spacings(GTK_TABLE(table), 2); + + pw->imd = image_new(TRUE); + pw->imd_normal = pw->imd; + + if (black_window_background) image_background_set_black(pw->imd, TRUE); + image_set_update_func(pw->imd, pan_window_image_update_cb, pw); + + image_set_scroll_notify_func(pw->imd, pan_window_image_scroll_notify_cb, pw); + +#if 0 + gtk_box_pack_start(GTK_BOX(vbox), pw->imd->widget, TRUE, TRUE, 0); +#endif + gtk_table_attach(GTK_TABLE(table), pw->imd->widget, 0, 1, 0, 1, + GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 0, 0); + gtk_widget_show(pw->imd->widget); + + pan_window_dnd_init(pw); + + pan_image_set_buttons(pw, pw->imd); + + pw->scrollbar_h = gtk_hscrollbar_new(NULL); + g_signal_connect(G_OBJECT(pw->scrollbar_h), "value_changed", + G_CALLBACK(pan_window_scrollbar_h_value_cb), pw); + gtk_table_attach(GTK_TABLE(table), pw->scrollbar_h, 0, 1, 1, 2, + GTK_FILL | GTK_EXPAND, 0, 0, 0); + gtk_widget_show(pw->scrollbar_h); + + pw->scrollbar_v = gtk_vscrollbar_new(NULL); + g_signal_connect(G_OBJECT(pw->scrollbar_v), "value_changed", + G_CALLBACK(pan_window_scrollbar_v_value_cb), pw); + gtk_table_attach(GTK_TABLE(table), pw->scrollbar_v, 1, 2, 0, 1, + 0, GTK_FILL | GTK_EXPAND, 0, 0); + gtk_widget_show(pw->scrollbar_v); + + /* find bar */ + + pw->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE); + gtk_box_pack_start(GTK_BOX(vbox), pw->search_box, FALSE, FALSE, 2); + + pref_spacer(pw->search_box, 0); + pref_label_new(pw->search_box, _("Find:")); + + hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE); + gtk_box_pack_start(GTK_BOX(pw->search_box), hbox, TRUE, TRUE, 0); + gtk_widget_show(hbox); + + combo = tab_completion_new_with_history(&pw->search_entry, "", "pan_view_search", -1, + pan_search_activate_cb, pw); + gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0); + gtk_widget_show(combo); + + pw->search_label = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(hbox), pw->search_label, TRUE, TRUE, 0); + gtk_widget_show(pw->search_label); + + /* status bar */ + + box = pref_box_new(vbox, FALSE, GTK_ORIENTATION_HORIZONTAL, 0); + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); + gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1); + gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 0); + gtk_widget_show(frame); + + hbox = gtk_hbox_new(FALSE, PREF_PAD_SPACE); + gtk_container_add(GTK_CONTAINER(frame), hbox); + gtk_widget_show(hbox); + + pref_spacer(hbox, PREF_PAD_SPACE); + pw->label_message = pref_label_new(hbox, ""); + + frame = gtk_frame_new(NULL); + gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN); + gtk_widget_set_size_request(frame, ZOOM_LABEL_WIDTH, -1); + gtk_box_pack_end(GTK_BOX(box), frame, FALSE, FALSE, 0); + gtk_widget_show(frame); + + pw->label_zoom = gtk_label_new(""); + gtk_container_add(GTK_CONTAINER(frame), pw->label_zoom); + gtk_widget_show(pw->label_zoom); + + pw->search_button = gtk_toggle_button_new(); + gtk_button_set_relief(GTK_BUTTON(pw->search_button), GTK_RELIEF_NONE); + gtk_button_set_focus_on_click(GTK_BUTTON(pw->search_button), FALSE); + hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP); + gtk_container_add(GTK_CONTAINER(pw->search_button), hbox); + gtk_widget_show(hbox); + pw->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE); + gtk_box_pack_start(GTK_BOX(hbox), pw->search_button_arrow, FALSE, FALSE, 0); + gtk_widget_show(pw->search_button_arrow); + pref_label_new(hbox, _("Find")); + + gtk_box_pack_end(GTK_BOX(box), pw->search_button, FALSE, FALSE, 0); + gtk_widget_show(pw->search_button); + g_signal_connect(G_OBJECT(pw->search_button), "clicked", + G_CALLBACK(pan_search_toggle_cb), pw); + + g_signal_connect(G_OBJECT(pw->window), "delete_event", + G_CALLBACK(pan_window_delete_cb), pw); + g_signal_connect(G_OBJECT(pw->window), "key_press_event", + G_CALLBACK(pan_window_key_press_cb), pw); + + gtk_window_set_default_size(GTK_WINDOW(pw->window), PAN_WINDOW_DEFAULT_WIDTH, PAN_WINDOW_DEFAULT_HEIGHT); + + pan_window_layout_update_idle(pw); + + gtk_widget_grab_focus(pw->imd->widget); + gtk_widget_show(pw->window); + + pan_window_list = g_list_append(pan_window_list, pw); +} + +/* + *----------------------------------------------------------------------------- + * public + *----------------------------------------------------------------------------- + */ + +/* + *----------------------------------------------------------------------------- + * view window menu routines and callbacks + *----------------------------------------------------------------------------- + */ + +static void pan_new_window_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + const gchar *path; + + path = pan_menu_click_path(pw); + if (path) + { + pan_fullscreen_toggle(pw, TRUE); + view_window_new(path); + } +} + +static void pan_edit_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw; + const gchar *path; + gint n; + + pw = submenu_item_get_data(widget); + n = GPOINTER_TO_INT(data); + if (!pw) return; + + path = pan_menu_click_path(pw); + if (path) + { + pan_fullscreen_toggle(pw, TRUE); + start_editor_from_file(n, path); + } +} + +static void pan_info_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + const gchar *path; + + path = pan_menu_click_path(pw); + if (path) info_window_new(path, NULL); +} + +static void pan_zoom_in_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + + image_zoom_adjust(pan_window_active_image(pw), ZOOM_INCREMENT); +} + +static void pan_zoom_out_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + + image_zoom_adjust(pan_window_active_image(pw), -ZOOM_INCREMENT); +} + +static void pan_zoom_1_1_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + + image_zoom_set(pan_window_active_image(pw), 1.0); +} + +static void pan_copy_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + const gchar *path; + + path = pan_menu_click_path(pw); + if (path) file_util_copy(path, NULL, NULL, pw->imd->widget); +} + +static void pan_move_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + const gchar *path; + + path = pan_menu_click_path(pw); + if (path) file_util_move(path, NULL, NULL, pw->imd->widget); +} + +static void pan_rename_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + const gchar *path; + + path = pan_menu_click_path(pw); + if (path) file_util_rename(path, NULL, pw->imd->widget); +} + +static void pan_delete_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + const gchar *path; + + path = pan_menu_click_path(pw); + if (path) file_util_delete(path, NULL, pw->imd->widget); +} + +static void pan_fullscreen_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + + pan_fullscreen_toggle(pw, FALSE); +} + +static void pan_close_cb(GtkWidget *widget, gpointer data) +{ + PanWindow *pw = data; + + pan_window_close(pw); +} + +static GtkWidget *pan_popup_menu(PanWindow *pw) +{ + GtkWidget *menu; + GtkWidget *item; + gint active; + + active = (pw->click_pi != NULL); + + menu = popup_menu_short_lived(); + + menu_item_add_stock(menu, _("Zoom _in"), GTK_STOCK_ZOOM_IN, + G_CALLBACK(pan_zoom_in_cb), pw); + menu_item_add_stock(menu, _("Zoom _out"), GTK_STOCK_ZOOM_OUT, + G_CALLBACK(pan_zoom_out_cb), pw); + menu_item_add_stock(menu, _("Zoom _1:1"), GTK_STOCK_ZOOM_100, + G_CALLBACK(pan_zoom_1_1_cb), pw); + menu_item_add_divider(menu); + + submenu_add_edit(menu, &item, G_CALLBACK(pan_edit_cb), pw); + gtk_widget_set_sensitive(item, active); + + menu_item_add_stock_sensitive(menu, _("_Properties"), GTK_STOCK_PROPERTIES, active, + G_CALLBACK(pan_info_cb), pw); + + menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, active, + G_CALLBACK(pan_new_window_cb), pw); + + menu_item_add_divider(menu); + menu_item_add_stock_sensitive(menu, _("_Copy..."), GTK_STOCK_COPY, active, + G_CALLBACK(pan_copy_cb), pw); + menu_item_add_sensitive(menu, _("_Move..."), active, + G_CALLBACK(pan_move_cb), pw); + menu_item_add_sensitive(menu, _("_Rename..."), active, + G_CALLBACK(pan_rename_cb), pw); + menu_item_add_stock_sensitive(menu, _("_Delete..."), GTK_STOCK_DELETE, active, + G_CALLBACK(pan_delete_cb), pw); + + menu_item_add_divider(menu); + + if (pw->fs) + { + menu_item_add(menu, _("Exit _full screen"), G_CALLBACK(pan_fullscreen_cb), pw); + } + else + { + menu_item_add(menu, _("_Full screen"), G_CALLBACK(pan_fullscreen_cb), pw); + } + + menu_item_add_divider(menu); + menu_item_add_stock(menu, _("C_lose window"), GTK_STOCK_CLOSE, G_CALLBACK(pan_close_cb), pw); + + return menu; +} + +/* + *----------------------------------------------------------------------------- + * image drag and drop routines + *----------------------------------------------------------------------------- + */ + +static void pan_window_get_dnd_data(GtkWidget *widget, GdkDragContext *context, + gint x, gint y, + GtkSelectionData *selection_data, guint info, + guint time, gpointer data) +{ + PanWindow *pw = data; + ImageWindow *imd; + + if (gtk_drag_get_source_widget(context) == pw->imd->image) return; + + imd = pw->imd; + + if (info == TARGET_URI_LIST) + { + GList *list; + + list = uri_list_from_text(selection_data->data, TRUE); + if (list && isdir((gchar *)list->data)) + { + printf("FIXME: change to this folder: %s\n", (gchar *)list->data); + } + + path_list_free(list); + } +} + +static void pan_window_set_dnd_data(GtkWidget *widget, GdkDragContext *context, + GtkSelectionData *selection_data, guint info, + guint time, gpointer data) +{ + printf("FIXME: set dnd data\n"); +} + +static void pan_window_dnd_init(PanWindow *pw) +{ + ImageWindow *imd; + + imd = pw->imd; + + gtk_drag_source_set(imd->image, GDK_BUTTON2_MASK, + dnd_file_drag_types, dnd_file_drag_types_count, + GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK); + g_signal_connect(G_OBJECT(imd->image), "drag_data_get", + G_CALLBACK(pan_window_set_dnd_data), pw); + + gtk_drag_dest_set(imd->image, + GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, + dnd_file_drop_types, dnd_file_drop_types_count, + GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK); + g_signal_connect(G_OBJECT(imd->image), "drag_data_received", + G_CALLBACK(pan_window_get_dnd_data), pw); +} + +/* + *----------------------------------------------------------------------------- + * maintenance (for rename, move, remove) + *----------------------------------------------------------------------------- + */ + diff --git a/src/pan-view.h b/src/pan-view.h new file mode 100644 index 00000000..563f647e --- /dev/null +++ b/src/pan-view.h @@ -0,0 +1,20 @@ +/* + * GQview + * (C) 2005 John Ellis + * + * Author: John Ellis + * + * This software is released under the GNU General Public License (GNU GPL). + * Please read the included file COPYING for more information. + * This software comes with no warranty of any kind, use at your own risk! + */ + + +#ifndef PAN_VIEW_H +#define PAN_VIEW_H + + +void pan_window_new(const gchar *path); + + +#endif diff --git a/src/pixbuf_util.c b/src/pixbuf_util.c index 319a23d6..2690515d 100644 --- a/src/pixbuf_util.c +++ b/src/pixbuf_util.c @@ -617,7 +617,7 @@ void pixbuf_draw_layout(GdkPixbuf *pixbuf, PangoLayout *layout, GtkWidget *widge if (x + w > dw) w = dw - x; if (y + h > dh) h = dh - y; - pixbuf_copy_font(buffer, 0, 0, + pixbuf_copy_font(buffer, sx, sy, pixbuf, x, y, w, h, r, g, b, a); diff --git a/src/typedefs.h b/src/typedefs.h index 9e7e26f8..d1ae8d83 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -210,6 +210,11 @@ struct _CollectWindow CollectionData *cd; }; +typedef gint (* ImageTileRequestFunc)(ImageWindow *imd, gint x, gint y, + gint width, gint height, GdkPixbuf *pixbuf, gpointer); +typedef void (* ImageTileDisposeFunc)(ImageWindow *imd, gint x, gint y, + gint width, gint height, GdkPixbuf *pixbuf, gpointer); + struct _ImageWindow { GtkWidget *widget; /* use this to add it and show it */ @@ -240,6 +245,8 @@ struct _ImageWindow gint x_scroll; /* scroll offset of image (into width, height to start drawing) */ gint y_scroll; + gdouble zoom_min; + gdouble zoom_max; gdouble zoom; /* zoom we want (0 is auto) */ gdouble scale; /* zoom we got (should never be 0) */ @@ -251,6 +258,13 @@ struct _ImageWindow gint tile_cache_size; /* allocated size of pixmaps/pixbufs */ GList *draw_queue; /* list of areas to redraw */ + gint source_tiles_enabled; + gint source_tiles_cache_size; + + GList *source_tiles; /* list of active source tiles */ + gint source_tile_width; + gint source_tile_height; + GList *draw_queue_2pass;/* list when 2 pass is enabled */ ImageLoader *il; @@ -277,10 +291,13 @@ struct _ImageWindow void (*func_update)(ImageWindow *, gpointer); void (*func_complete)(ImageWindow *, gint preload, gpointer); void (*func_new)(ImageWindow *, gpointer); + ImageTileRequestFunc func_tile_request; + ImageTileDisposeFunc func_tile_dispose; gpointer data_update; gpointer data_complete; gpointer data_new; + gpointer data_tile; /* button, scroll functions */ void (*func_button)(ImageWindow *, gint button, @@ -291,6 +308,11 @@ struct _ImageWindow gpointer data_button; gpointer data_scroll; + /* scroll notification (for scroll bar implementation) */ + void (*func_scroll_notify)(ImageWindow *, gint x, gint y, gint width, gint height, gpointer); + + gpointer data_scroll_notify; + /* collection info */ CollectionData *collection; CollectInfo *collection_info; diff --git a/src/ui_tabcomp.c b/src/ui_tabcomp.c index e188d6e2..0eb14841 100644 --- a/src/ui_tabcomp.c +++ b/src/ui_tabcomp.c @@ -241,19 +241,40 @@ static void tab_completion_popup_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolea PangoRectangle strong_pos, weak_pos; gint length; gint xoffset, yoffset; - + GtkRequisition req; + GdkScreen *screen; + gint monitor_num; + GdkRectangle monitor; gdk_window_get_origin(td->entry->window, x, y); - height = MIN(td->entry->requisition.height, td->entry->allocation.height); - *y += height; + screen = gtk_widget_get_screen(GTK_WIDGET(menu)); + monitor_num = gdk_screen_get_monitor_at_window(screen, td->entry->window); + gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor); + + gtk_widget_size_request(GTK_WIDGET(menu), &req); length = strlen(gtk_entry_get_text(GTK_ENTRY(td->entry))); gtk_entry_get_layout_offsets(GTK_ENTRY(td->entry), &xoffset, &yoffset); layout = gtk_entry_get_layout(GTK_ENTRY(td->entry)); pango_layout_get_cursor_pos(layout, length, &strong_pos, &weak_pos); + *x += strong_pos.x / PANGO_SCALE + xoffset; + + height = MIN(td->entry->requisition.height, td->entry->allocation.height); + + if (req.height > monitor.y + monitor.height - *y - height && + *y - monitor.y > monitor.y + monitor.height - *y) + { + height = MIN(*y - monitor.y, req.height); + gtk_widget_set_size_request(GTK_WIDGET(menu), -1, height); + *y -= height; + } + else + { + *y += height; + } } static void tab_completion_popup_list(TabCompData *td, GList *list) -- 2.20.1