diff options
Diffstat (limited to 'src/eom-scroll-view.c')
-rw-r--r-- | src/eom-scroll-view.c | 2633 |
1 files changed, 2633 insertions, 0 deletions
diff --git a/src/eom-scroll-view.c b/src/eom-scroll-view.c new file mode 100644 index 0000000..f37561c --- /dev/null +++ b/src/eom-scroll-view.c @@ -0,0 +1,2633 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <math.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk/gdkkeysyms.h> +#ifdef HAVE_RSVG +#include <librsvg/rsvg.h> +#include <librsvg/rsvg-cairo.h> +#endif + +#include "eom-marshal.h" +#include "eom-scroll-view.h" +#include "eom-debug.h" +#include "uta.h" +#include "zoom.h" + +/* Maximum size of delayed repaint rectangles */ +#define PAINT_RECT_WIDTH 128 +#define PAINT_RECT_HEIGHT 128 + +/* Scroll step increment */ +#define SCROLL_STEP_SIZE 32 + +/* Maximum zoom factor */ +#define MAX_ZOOM_FACTOR 20 +#define MIN_ZOOM_FACTOR 0.02 + +#define CHECK_MEDIUM 8 +#define CHECK_BLACK 0x00000000 +#define CHECK_DARK 0x00555555 +#define CHECK_GRAY 0x00808080 +#define CHECK_LIGHT 0x00cccccc +#define CHECK_WHITE 0x00ffffff + +/* Default increment for zooming. The current zoom factor is multiplied or + * divided by this amount on every zooming step. For consistency, you should + * use the same value elsewhere in the program. + */ +#define IMAGE_VIEW_ZOOM_MULTIPLIER 1.05 + +/* States for automatically adjusting the zoom factor */ +typedef enum { + ZOOM_MODE_FIT, /* Image is fitted to scroll view even if the latter changes size */ + ZOOM_MODE_FREE /* The image remains at its current zoom factor even if the scrollview changes size */ +} ZoomMode; + +/* Progressive loading state */ +typedef enum { + PROGRESSIVE_NONE, /* We are not loading an image or it is already loaded */ + PROGRESSIVE_LOADING, /* An image is being loaded */ + PROGRESSIVE_POLISHING /* We have finished loading an image but have not scaled it with interpolation */ +} ProgressiveState; + +/* Signal IDs */ +enum { + SIGNAL_ZOOM_CHANGED, + SIGNAL_LAST +}; +static gint view_signals [SIGNAL_LAST]; + +typedef enum { + EOM_SCROLL_VIEW_CURSOR_NORMAL, + EOM_SCROLL_VIEW_CURSOR_HIDDEN, + EOM_SCROLL_VIEW_CURSOR_DRAG +} EomScrollViewCursor; + +/* Drag 'n Drop */ +static GtkTargetEntry target_table[] = { + { "text/uri-list", 0, 0}, +}; + +enum { + PROP_0, + PROP_USE_BG_COLOR, + PROP_BACKGROUND_COLOR +}; + +/* Private part of the EomScrollView structure */ +struct _EomScrollViewPrivate { + /* some widgets we rely on */ + GtkWidget *display; + GtkAdjustment *hadj; + GtkAdjustment *vadj; + GtkWidget *hbar; + GtkWidget *vbar; + GtkWidget *menu; + + /* actual image */ + EomImage *image; + guint image_changed_id; + guint frame_changed_id; + GdkPixbuf *pixbuf; + + /* zoom mode, either ZOOM_MODE_FIT or ZOOM_MODE_FREE */ + ZoomMode zoom_mode; + + /* whether to allow zoom > 1.0 on zoom fit */ + gboolean upscale; + + /* the actual zoom factor */ + double zoom; + + /* the minimum possible (reasonable) zoom factor */ + double min_zoom; + + /* Current scrolling offsets */ + int xofs, yofs; + + /* Microtile arrays for dirty region. This represents the dirty region + * for interpolated drawing. + */ + EomUta *uta; + + /* handler ID for paint idle callback */ + guint idle_id; + + /* Interpolation type when zoomed in*/ + GdkInterpType interp_type_in; + + /* Interpolation type when zoomed out*/ + GdkInterpType interp_type_out; + + /* Scroll wheel zoom */ + gboolean scroll_wheel_zoom; + + /* Scroll wheel zoom */ + gdouble zoom_multiplier; + + /* dragging stuff */ + int drag_anchor_x, drag_anchor_y; + int drag_ofs_x, drag_ofs_y; + guint dragging : 1; + + /* status of progressive loading */ + ProgressiveState progressive_state; + + /* how to indicate transparency in images */ + EomTransparencyStyle transp_style; + guint32 transp_color; + + /* the type of the cursor we are currently showing */ + EomScrollViewCursor cursor; + + gboolean use_bg_color; + GdkColor *background_color; + GdkColor *override_bg_color; + + cairo_surface_t *background_surface; +}; + +static void scroll_by (EomScrollView *view, int xofs, int yofs); +static void set_zoom_fit (EomScrollView *view); +static void request_paint_area (EomScrollView *view, GdkRectangle *area); +static void set_minimum_zoom_factor (EomScrollView *view); + +#define EOM_SCROLL_VIEW_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_SCROLL_VIEW, EomScrollViewPrivate)) + +G_DEFINE_TYPE (EomScrollView, eom_scroll_view, GTK_TYPE_TABLE) + + +/*=================================== + widget size changing handler & + util functions + ---------------------------------*/ + +/* Disconnects from the EomImage and removes references to it */ +static void +free_image_resources (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + if (priv->image_changed_id > 0) { + g_signal_handler_disconnect (G_OBJECT (priv->image), priv->image_changed_id); + priv->image_changed_id = 0; + } + + if (priv->frame_changed_id > 0) { + g_signal_handler_disconnect (G_OBJECT (priv->image), priv->frame_changed_id); + priv->frame_changed_id = 0; + } + + if (priv->image != NULL) { + eom_image_data_unref (priv->image); + priv->image = NULL; + } + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } +} + +/* Computes the size in pixels of the scaled image */ +static void +compute_scaled_size (EomScrollView *view, double zoom, int *width, int *height) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + if (priv->pixbuf) { + *width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoom + 0.5); + *height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoom + 0.5); + } else + *width = *height = 0; +} + +/* Computes the offsets for the new zoom value so that they keep the image + * centered on the view. + */ +static void +compute_center_zoom_offsets (EomScrollView *view, + double old_zoom, double new_zoom, + int width, int height, + double zoom_x_anchor, double zoom_y_anchor, + int *xofs, int *yofs) +{ + EomScrollViewPrivate *priv; + int old_scaled_width, old_scaled_height; + int new_scaled_width, new_scaled_height; + double view_cx, view_cy; + + priv = view->priv; + + compute_scaled_size (view, old_zoom, + &old_scaled_width, &old_scaled_height); + + if (old_scaled_width < width) + view_cx = (zoom_x_anchor * old_scaled_width) / old_zoom; + else + view_cx = (priv->xofs + zoom_x_anchor * width) / old_zoom; + + if (old_scaled_height < height) + view_cy = (zoom_y_anchor * old_scaled_height) / old_zoom; + else + view_cy = (priv->yofs + zoom_y_anchor * height) / old_zoom; + + compute_scaled_size (view, new_zoom, + &new_scaled_width, &new_scaled_height); + + if (new_scaled_width < width) + *xofs = 0; + else { + *xofs = floor (view_cx * new_zoom - zoom_x_anchor * width + 0.5); + if (*xofs < 0) + *xofs = 0; + } + + if (new_scaled_height < height) + *yofs = 0; + else { + *yofs = floor (view_cy * new_zoom - zoom_y_anchor * height + 0.5); + if (*yofs < 0) + *yofs = 0; + } +} + +/* Sets the scrollbar values based on the current scrolling offset */ +static void +update_scrollbar_values (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + int scaled_width, scaled_height; + int xofs, yofs; + gdouble page_size,page_increment,step_increment; + gdouble lower, upper, value; + GtkAllocation allocation; + + priv = view->priv; + + if (!gtk_widget_get_visible (GTK_WIDGET (priv->hbar)) + && !gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) + return; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + if (gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) { + /* Set scroll increments */ + page_size = MIN (scaled_width, allocation.width); + page_increment = allocation.width / 2; + step_increment = SCROLL_STEP_SIZE; + + /* Set scroll bounds and new offsets */ + lower = 0; + upper = scaled_width; + xofs = CLAMP (priv->xofs, 0, upper - page_size); + if (gtk_adjustment_get_value (priv->hadj) != xofs) { + value = xofs; + priv->xofs = xofs; + + g_signal_handlers_block_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + + gtk_adjustment_configure (priv->hadj, value, lower, + upper, step_increment, + page_increment, page_size); + + g_signal_handlers_unblock_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + } + } + + if (gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) { + page_size = MIN (scaled_height, allocation.height); + page_increment = allocation.height / 2; + step_increment = SCROLL_STEP_SIZE; + + lower = 0; + upper = scaled_height; + yofs = CLAMP (priv->yofs, 0, upper - page_size); + + if (gtk_adjustment_get_value (priv->vadj) != yofs) { + value = yofs; + priv->yofs = yofs; + + g_signal_handlers_block_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + + gtk_adjustment_configure (priv->vadj, value, lower, + upper, step_increment, + page_increment, page_size); + + g_signal_handlers_unblock_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + } + } +} + +static void +eom_scroll_view_set_cursor (EomScrollView *view, EomScrollViewCursor new_cursor) +{ + GdkCursor *cursor = NULL; + GdkDisplay *display; + GtkWidget *widget; + + if (view->priv->cursor == new_cursor) { + return; + } + + widget = gtk_widget_get_toplevel (GTK_WIDGET (view)); + display = gtk_widget_get_display (widget); + view->priv->cursor = new_cursor; + + switch (new_cursor) { + case EOM_SCROLL_VIEW_CURSOR_NORMAL: + gdk_window_set_cursor (gtk_widget_get_window (widget), NULL); + break; + case EOM_SCROLL_VIEW_CURSOR_HIDDEN: + cursor = gdk_cursor_new (GDK_BLANK_CURSOR); + break; + case EOM_SCROLL_VIEW_CURSOR_DRAG: + cursor = gdk_cursor_new_for_display (display, GDK_FLEUR); + break; + } + + if (cursor) { + gdk_window_set_cursor (gtk_widget_get_window (widget), cursor); + gdk_cursor_unref (cursor); + gdk_flush(); + } +} + +/* Changes visibility of the scrollbars based on the zoom factor and the + * specified allocation, or the current allocation if NULL is specified. + */ +static void +check_scrollbar_visibility (EomScrollView *view, GtkAllocation *alloc) +{ + EomScrollViewPrivate *priv; + int bar_height; + int bar_width; + int img_width; + int img_height; + GtkRequisition req; + int width, height; + gboolean hbar_visible, vbar_visible; + + priv = view->priv; + + if (alloc) { + width = alloc->width; + height = alloc->height; + } else { + GtkAllocation allocation; + + gtk_widget_get_allocation (GTK_WIDGET (view), &allocation); + width = allocation.width; + height = allocation.height; + } + + compute_scaled_size (view, priv->zoom, &img_width, &img_height); + + /* this should work fairly well in this special case for scrollbars */ + gtk_widget_size_request (priv->hbar, &req); + bar_height = req.height; + gtk_widget_size_request (priv->vbar, &req); + bar_width = req.width; + + eom_debug_message (DEBUG_WINDOW, "Widget Size allocate: %i, %i Bar: %i, %i\n", + width, height, bar_width, bar_height); + + hbar_visible = vbar_visible = FALSE; + if (priv->zoom_mode == ZOOM_MODE_FIT) + hbar_visible = vbar_visible = FALSE; + else if (img_width <= width && img_height <= height) + hbar_visible = vbar_visible = FALSE; + else if (img_width > width && img_height > height) + hbar_visible = vbar_visible = TRUE; + else if (img_width > width) { + hbar_visible = TRUE; + if (img_height <= (height - bar_height)) + vbar_visible = FALSE; + else + vbar_visible = TRUE; + } + else if (img_height > height) { + vbar_visible = TRUE; + if (img_width <= (width - bar_width)) + hbar_visible = FALSE; + else + hbar_visible = TRUE; + } + + if (hbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->hbar))) + g_object_set (G_OBJECT (priv->hbar), "visible", hbar_visible, NULL); + + if (vbar_visible != gtk_widget_get_visible (GTK_WIDGET (priv->vbar))) + g_object_set (G_OBJECT (priv->vbar), "visible", vbar_visible, NULL); +} + +#define DOUBLE_EQUAL_MAX_DIFF 1e-6 +#define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF) + +/* Returns whether the zoom factor is 1.0 */ +static gboolean +is_unity_zoom (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + return DOUBLE_EQUAL (priv->zoom, 1.0); +} + +/* Returns whether the image is zoomed in */ +static gboolean +is_zoomed_in (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + return priv->zoom - 1.0 > DOUBLE_EQUAL_MAX_DIFF; +} + +/* Returns whether the image is zoomed out */ +static gboolean +is_zoomed_out (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + return DOUBLE_EQUAL_MAX_DIFF + priv->zoom - 1.0 < 0.0; +} + +/* Returns wether the image is movable, that means if it is larger then + * the actual visible area. + */ +static gboolean +is_image_movable (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + return (gtk_widget_get_visible (priv->hbar) || gtk_widget_get_visible (priv->vbar)); +} + + +/* Computes the image offsets with respect to the window */ +/* +static void +get_image_offsets (EomScrollView *view, int *xofs, int *yofs) +{ + EomScrollViewPrivate *priv; + int scaled_width, scaled_height; + int width, height; + + priv = view->priv; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + + width = GTK_WIDGET (priv->display)->allocation.width; + height = GTK_WIDGET (priv->display)->allocation.height; + + // Compute image offsets with respect to the window + if (scaled_width <= width) + *xofs = (width - scaled_width) / 2; + else + *xofs = -priv->xofs; + + if (scaled_height <= height) + *yofs = (height - scaled_height) / 2; + else + *yofs = -priv->yofs; +} +*/ + +/*=================================== + drawing core + ---------------------------------*/ + + +/* Pulls a rectangle from the specified microtile array. The rectangle is the + * first one that would be glommed together by art_rect_list_from_uta(), and its + * size is bounded by max_width and max_height. The rectangle is also removed + * from the microtile array. + */ +static void +pull_rectangle (EomUta *uta, EomIRect *rect, int max_width, int max_height) +{ + uta_find_first_glom_rect (uta, rect, max_width, max_height); + uta_remove_rect (uta, rect->x0, rect->y0, rect->x1, rect->y1); +} + +/* Paints a rectangle with the background color if the specified rectangle + * intersects the dirty rectangle. + */ +static void +paint_background (EomScrollView *view, EomIRect *r, EomIRect *rect) +{ + EomScrollViewPrivate *priv; + EomIRect d; + + priv = view->priv; + + eom_irect_intersect (&d, r, rect); + if (!eom_irect_empty (&d)) { + gdk_window_clear_area (gtk_widget_get_window (priv->display), + d.x0, d.y0, + d.x1 - d.x0, d.y1 - d.y0); + } +} + +static void +get_transparency_params (EomScrollView *view, int *size, guint32 *color1, guint32 *color2) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + /* Compute transparency parameters */ + switch (priv->transp_style) { + case EOM_TRANSP_BACKGROUND: { + GdkColor color = gtk_widget_get_style (GTK_WIDGET (priv->display))->bg[GTK_STATE_NORMAL]; + + *color1 = *color2 = (((color.red & 0xff00) << 8) + | (color.green & 0xff00) + | ((color.blue & 0xff00) >> 8)); + break; } + + case EOM_TRANSP_CHECKED: + *color1 = CHECK_GRAY; + *color2 = CHECK_LIGHT; + break; + + case EOM_TRANSP_COLOR: + *color1 = *color2 = priv->transp_color; + break; + + default: + g_assert_not_reached (); + }; + + *size = CHECK_MEDIUM; +} + +#ifdef HAVE_RSVG +static cairo_surface_t * +create_background_surface (EomScrollView *view) +{ + int check_size; + guint32 check_1 = 0; + guint32 check_2 = 0; + cairo_surface_t *surface; + cairo_t *check_cr; + + get_transparency_params (view, &check_size, &check_1, &check_2); + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, check_size * 2, check_size * 2); + check_cr = cairo_create (surface); + cairo_set_source_rgba (check_cr, + ((check_1 & 0xff0000) >> 16) / 255., + ((check_1 & 0x00ff00) >> 8) / 255., + (check_1 & 0x0000ff) / 255., + 1.); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + cairo_translate (check_cr, check_size, check_size); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + + cairo_set_source_rgba (check_cr, + ((check_2 & 0xff0000) >> 16) / 255., + ((check_2 & 0x00ff00) >> 8) / 255., + (check_2 & 0x0000ff) / 255., + 1.); + cairo_translate (check_cr, -check_size, 0); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + cairo_translate (check_cr, check_size, -check_size); + cairo_rectangle (check_cr, 0., 0., check_size, check_size); + cairo_fill (check_cr); + cairo_destroy (check_cr); + + return surface; +} + +static void +draw_svg_background (EomScrollView *view, cairo_t *cr, EomIRect *render_rect, EomIRect *image_rect) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + if (priv->background_surface == NULL) + priv->background_surface = create_background_surface (view); + + cairo_set_source_surface (cr, priv->background_surface, + - (render_rect->x0 - image_rect->x0) % (CHECK_MEDIUM * 2), + - (render_rect->y0 - image_rect->y0) % (CHECK_MEDIUM * 2)); + cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_REPEAT); + cairo_rectangle (cr, + 0, + 0, + render_rect->x1 - render_rect->x0, + render_rect->y1 - render_rect->y0); + cairo_fill (cr); +} + +static cairo_surface_t * +draw_svg_on_image_surface (EomScrollView *view, EomIRect *render_rect, EomIRect *image_rect) +{ + EomScrollViewPrivate *priv; + cairo_t *cr; + cairo_surface_t *surface; + cairo_matrix_t matrix, translate, scale; + EomTransform *transform; + + priv = view->priv; + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + render_rect->x1 - render_rect->x0, + render_rect->y1 - render_rect->y0); + cr = cairo_create (surface); + + cairo_save (cr); + draw_svg_background (view, cr, render_rect, image_rect); + cairo_restore (cr); + + cairo_matrix_init_identity (&matrix); + transform = eom_image_get_transform (priv->image); + if (transform) { + cairo_matrix_t affine; + double image_offset_x = 0., image_offset_y = 0.; + + eom_transform_get_affine (transform, &affine); + cairo_matrix_multiply (&matrix, &affine, &matrix); + + switch (eom_transform_get_transform_type (transform)) { + case EOM_TRANSFORM_ROT_90: + case EOM_TRANSFORM_FLIP_HORIZONTAL: + image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf); + break; + case EOM_TRANSFORM_ROT_270: + case EOM_TRANSFORM_FLIP_VERTICAL: + image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf); + break; + case EOM_TRANSFORM_ROT_180: + case EOM_TRANSFORM_TRANSPOSE: + case EOM_TRANSFORM_TRANSVERSE: + image_offset_x = (double) gdk_pixbuf_get_width (priv->pixbuf); + image_offset_y = (double) gdk_pixbuf_get_height (priv->pixbuf); + break; + case EOM_TRANSFORM_NONE: + default: + break; + } + + cairo_matrix_init_translate (&translate, image_offset_x, image_offset_y); + cairo_matrix_multiply (&matrix, &matrix, &translate); + } + + cairo_matrix_init_scale (&scale, priv->zoom, priv->zoom); + cairo_matrix_multiply (&matrix, &matrix, &scale); + cairo_matrix_init_translate (&translate, image_rect->x0, image_rect->y0); + cairo_matrix_multiply (&matrix, &matrix, &translate); + cairo_matrix_init_translate (&translate, -render_rect->x0, -render_rect->y0); + cairo_matrix_multiply (&matrix, &matrix, &translate); + + cairo_set_matrix (cr, &matrix); + + rsvg_handle_render_cairo (eom_image_get_svg (priv->image), cr); + cairo_destroy (cr); + + return surface; +} + +static void +draw_svg (EomScrollView *view, EomIRect *render_rect, EomIRect *image_rect) +{ + EomScrollViewPrivate *priv; + cairo_t *cr; + cairo_surface_t *surface; + GdkWindow *window; + + priv = view->priv; + + window = gtk_widget_get_window (GTK_WIDGET (priv->display)); + surface = draw_svg_on_image_surface (view, render_rect, image_rect); + + cr = gdk_cairo_create (window); + cairo_set_source_surface (cr, surface, render_rect->x0, render_rect->y0); + cairo_paint (cr); + cairo_destroy (cr); +} +#endif + +/* Paints a rectangle of the dirty region */ +static void +paint_rectangle (EomScrollView *view, EomIRect *rect, GdkInterpType interp_type) +{ + EomScrollViewPrivate *priv; + GdkPixbuf *tmp; + char *str; + GtkAllocation allocation; + int scaled_width, scaled_height; + int xofs, yofs; + EomIRect r, d; + int check_size; + guint32 check_1 = 0; + guint32 check_2 = 0; + + priv = view->priv; + + if (!gtk_widget_is_drawable (priv->display)) + return; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + if (scaled_width < 1 || scaled_height < 1) + { + r.x0 = 0; + r.y0 = 0; + r.x1 = allocation.width; + r.y1 = allocation.height; + paint_background (view, &r, rect); + return; + } + + /* Compute image offsets with respect to the window */ + + if (scaled_width <= allocation.width) + xofs = (allocation.width - scaled_width) / 2; + else + xofs = -priv->xofs; + + if (scaled_height <= allocation.height) + yofs = (allocation.height - scaled_height) / 2; + else + yofs = -priv->yofs; + + eom_debug_message (DEBUG_WINDOW, "zoom %.2f, xofs: %i, yofs: %i scaled w: %i h: %i\n", + priv->zoom, xofs, yofs, scaled_width, scaled_height); + + /* Draw background if necessary, in four steps */ + + /* Top */ + if (yofs > 0) { + r.x0 = 0; + r.y0 = 0; + r.x1 = allocation.width; + r.y1 = yofs; + paint_background (view, &r, rect); + } + + /* Left */ + if (xofs > 0) { + r.x0 = 0; + r.y0 = yofs; + r.x1 = xofs; + r.y1 = yofs + scaled_height; + paint_background (view, &r, rect); + } + + /* Right */ + if (xofs >= 0) { + r.x0 = xofs + scaled_width; + r.y0 = yofs; + r.x1 = allocation.width; + r.y1 = yofs + scaled_height; + if (r.x0 < r.x1) + paint_background (view, &r, rect); + } + + /* Bottom */ + if (yofs >= 0) { + r.x0 = 0; + r.y0 = yofs + scaled_height; + r.x1 = allocation.width; + r.y1 = allocation.height; + if (r.y0 < r.y1) + paint_background (view, &r, rect); + } + + + /* Draw the scaled image + * + * FIXME: this is not using the color correction tables! + */ + + if (!priv->pixbuf) + return; + + r.x0 = xofs; + r.y0 = yofs; + r.x1 = xofs + scaled_width; + r.y1 = yofs + scaled_height; + + eom_irect_intersect (&d, &r, rect); + if (eom_irect_empty (&d)) + return; + + switch (interp_type) { + case GDK_INTERP_NEAREST: + str = "NEAREST"; + break; + default: + str = "ALIASED"; + } + + eom_debug_message (DEBUG_WINDOW, "%s: x0: %i,\t y0: %i,\t x1: %i,\t y1: %i\n", + str, d.x0, d.y0, d.x1, d.y1); + +#ifdef HAVE_RSVG + if (eom_image_is_svg (view->priv->image) && interp_type != GDK_INTERP_NEAREST) { + draw_svg (view, &d, &r); + return; + } +#endif + /* Short-circuit the fast case to avoid a memcpy() */ + + if (is_unity_zoom (view) + && gdk_pixbuf_get_colorspace (priv->pixbuf) == GDK_COLORSPACE_RGB + && !gdk_pixbuf_get_has_alpha (priv->pixbuf) + && gdk_pixbuf_get_bits_per_sample (priv->pixbuf) == 8) { + guchar *pixels; + int rowstride; + + rowstride = gdk_pixbuf_get_rowstride (priv->pixbuf); + + pixels = (gdk_pixbuf_get_pixels (priv->pixbuf) + + (d.y0 - yofs) * rowstride + + 3 * (d.x0 - xofs)); + + gdk_draw_rgb_image_dithalign (gtk_widget_get_window (GTK_WIDGET (priv->display)), + gtk_widget_get_style (GTK_WIDGET (priv->display))->black_gc, + d.x0, d.y0, + d.x1 - d.x0, d.y1 - d.y0, + GDK_RGB_DITHER_MAX, + pixels, + rowstride, + d.x0 - xofs, d.y0 - yofs); + return; + } + + /* For all other cases, create a temporary pixbuf */ + + tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, d.x1 - d.x0, d.y1 - d.y0); + + if (!tmp) { + g_message ("paint_rectangle(): Could not allocate temporary pixbuf of " + "size (%d, %d); skipping", d.x1 - d.x0, d.y1 - d.y0); + return; + } + + /* Compute transparency parameters */ + get_transparency_params (view, &check_size, &check_1, &check_2); + + /* Draw! */ + gdk_pixbuf_composite_color (priv->pixbuf, + tmp, + 0, 0, + d.x1 - d.x0, d.y1 - d.y0, + -(d.x0 - xofs), -(d.y0 - yofs), + priv->zoom, priv->zoom, + is_unity_zoom (view) ? GDK_INTERP_NEAREST : interp_type, + 255, + d.x0 - xofs, d.y0 - yofs, + check_size, + check_1, check_2); + + gdk_draw_rgb_image_dithalign (gtk_widget_get_window (priv->display), + gtk_widget_get_style (priv->display)->black_gc, + d.x0, d.y0, + d.x1 - d.x0, d.y1 - d.y0, + GDK_RGB_DITHER_MAX, + gdk_pixbuf_get_pixels (tmp), + gdk_pixbuf_get_rowstride (tmp), + d.x0 - xofs, d.y0 - yofs); + + g_object_unref (tmp); +} + + +/* Idle handler for the drawing process. We pull a rectangle from the dirty + * region microtile array, paint it, and leave the rest to the next idle + * iteration. + */ +static gboolean +paint_iteration_idle (gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + EomIRect rect; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + g_assert (priv->uta != NULL); + + pull_rectangle (priv->uta, &rect, PAINT_RECT_WIDTH, PAINT_RECT_HEIGHT); + + if (eom_irect_empty (&rect)) { + eom_uta_free (priv->uta); + priv->uta = NULL; + } else { + if (is_zoomed_in (view)) + paint_rectangle (view, &rect, priv->interp_type_in); + else if (is_zoomed_out (view)) + paint_rectangle (view, &rect, priv->interp_type_out); + else + paint_rectangle (view, &rect, GDK_INTERP_NEAREST); + } + + if (!priv->uta) { + priv->idle_id = 0; + return FALSE; + } + + return TRUE; +} + +/* Paints the requested area in non-interpolated mode. Then, if we are + * configured to use interpolation, we queue an idle handler to redraw the area + * with interpolation. The area is in window coordinates. + */ +static void +request_paint_area (EomScrollView *view, GdkRectangle *area) +{ + EomScrollViewPrivate *priv; + EomIRect r; + GtkAllocation allocation; + + priv = view->priv; + + eom_debug_message (DEBUG_WINDOW, "x: %i, y: %i, width: %i, height: %i\n", + area->x, area->y, area->width, area->height); + + if (!gtk_widget_is_drawable (priv->display)) + return; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + r.x0 = MAX (0, area->x); + r.y0 = MAX (0, area->y); + r.x1 = MIN (allocation.width, area->x + area->width); + r.y1 = MIN (allocation.height, area->y + area->height); + + eom_debug_message (DEBUG_WINDOW, "r: %i, %i, %i, %i\n", r.x0, r.y0, r.x1, r.y1); + + if (r.x0 >= r.x1 || r.y0 >= r.y1) + return; + + /* Do nearest neighbor, 1:1 zoom or active progressive loading synchronously for speed. */ + if ((is_zoomed_in (view) && priv->interp_type_in == GDK_INTERP_NEAREST) || + (is_zoomed_out (view) && priv->interp_type_out == GDK_INTERP_NEAREST) || + is_unity_zoom (view) || + priv->progressive_state == PROGRESSIVE_LOADING) { + paint_rectangle (view, &r, GDK_INTERP_NEAREST); + return; + } + + if (priv->progressive_state == PROGRESSIVE_POLISHING) + /* We have already a complete image with nearest neighbor mode. + * It's sufficient to add only a antitaliased idle update + */ + priv->progressive_state = PROGRESSIVE_NONE; + else if (!priv->image || !eom_image_is_animation (priv->image)) + /* do nearest neigbor before anti aliased version, + except for animations to avoid a "blinking" effect. */ + paint_rectangle (view, &r, GDK_INTERP_NEAREST); + + /* All other interpolation types are delayed. */ + if (priv->uta) + g_assert (priv->idle_id != 0); + else { + g_assert (priv->idle_id == 0); + priv->idle_id = g_idle_add (paint_iteration_idle, view); + } + + priv->uta = uta_add_rect (priv->uta, r.x0, r.y0, r.x1, r.y1); +} + + +/* ======================================= + + scrolling stuff + + --------------------------------------*/ + + +/* Scrolls the view to the specified offsets. */ +static void +scroll_to (EomScrollView *view, int x, int y, gboolean change_adjustments) +{ + EomScrollViewPrivate *priv; + GtkAllocation allocation; + int xofs, yofs; + GdkWindow *window; + int src_x, src_y; + int dest_x, dest_y; + int twidth, theight; + + priv = view->priv; + + /* Check bounds & Compute offsets */ + if (gtk_widget_get_visible (priv->hbar)) { + x = CLAMP (x, 0, gtk_adjustment_get_upper (priv->hadj) + - gtk_adjustment_get_page_size (priv->hadj)); + xofs = x - priv->xofs; + } else + xofs = 0; + + if (gtk_widget_get_visible (priv->vbar)) { + y = CLAMP (y, 0, gtk_adjustment_get_upper (priv->vadj) + - gtk_adjustment_get_page_size (priv->vadj)); + yofs = y - priv->yofs; + } else + yofs = 0; + + if (xofs == 0 && yofs == 0) + return; + + priv->xofs = x; + priv->yofs = y; + + if (!gtk_widget_is_drawable (priv->display)) + goto out; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + if (abs (xofs) >= allocation.width || abs (yofs) >= allocation.height) { + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + goto out; + } + + window = gtk_widget_get_window (GTK_WIDGET (priv->display)); + + /* Ensure that the uta has the full size */ + + twidth = (allocation.width + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + theight = (allocation.height + EOM_UTILE_SIZE - 1) >> EOM_UTILE_SHIFT; + + if (priv->uta) + g_assert (priv->idle_id != 0); + else + priv->idle_id = g_idle_add (paint_iteration_idle, view); + + priv->uta = uta_ensure_size (priv->uta, 0, 0, twidth, theight); + + /* Copy the uta area. Our synchronous handling of expose events, below, + * will queue the new scrolled-in areas. + */ + src_x = xofs < 0 ? 0 : xofs; + src_y = yofs < 0 ? 0 : yofs; + dest_x = xofs < 0 ? -xofs : 0; + dest_y = yofs < 0 ? -yofs : 0; + + uta_copy_area (priv->uta, + src_x, src_y, + dest_x, dest_y, + allocation.width - abs (xofs), + allocation.height - abs (yofs)); + + /* Scroll the window area and process exposure synchronously. */ + + gdk_window_scroll (window, -xofs, -yofs); + gdk_window_process_updates (window, TRUE); + + out: + if (!change_adjustments) + return; + + g_signal_handlers_block_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + g_signal_handlers_block_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + + gtk_adjustment_set_value (priv->hadj, x); + gtk_adjustment_set_value (priv->vadj, y); + + g_signal_handlers_unblock_matched ( + priv->hadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); + g_signal_handlers_unblock_matched ( + priv->vadj, G_SIGNAL_MATCH_DATA, + 0, 0, NULL, NULL, view); +} + +/* Scrolls the image view by the specified offsets. Notifies the adjustments + * about their new values. + */ +static void +scroll_by (EomScrollView *view, int xofs, int yofs) +{ + EomScrollViewPrivate *priv; + + priv = view->priv; + + scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE); +} + + +/* Callback used when an adjustment is changed */ +static void +adjustment_changed_cb (GtkAdjustment *adj, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + scroll_to (view, gtk_adjustment_get_value (priv->hadj), + gtk_adjustment_get_value (priv->vadj), FALSE); +} + + +/* Drags the image to the specified position */ +static void +drag_to (EomScrollView *view, int x, int y) +{ + EomScrollViewPrivate *priv; + int dx, dy; + + priv = view->priv; + + dx = priv->drag_anchor_x - x; + dy = priv->drag_anchor_y - y; + + x = priv->drag_ofs_x + dx; + y = priv->drag_ofs_y + dy; + + scroll_to (view, x, y, TRUE); +} + +static void +set_minimum_zoom_factor (EomScrollView *view) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + view->priv->min_zoom = MAX (1.0 / gdk_pixbuf_get_width (view->priv->pixbuf), + MAX(1.0 / gdk_pixbuf_get_height (view->priv->pixbuf), + MIN_ZOOM_FACTOR) ); + return; +} + +/** + * set_zoom: + * @view: A scroll view. + * @zoom: Zoom factor. + * @have_anchor: Whether the anchor point specified by (@anchorx, @anchory) + * should be used. + * @anchorx: Horizontal anchor point in pixels. + * @anchory: Vertical anchor point in pixels. + * + * Sets the zoom factor for an image view. The anchor point can be used to + * specify the point that stays fixed when the image is zoomed. If @have_anchor + * is %TRUE, then (@anchorx, @anchory) specify the point relative to the image + * view widget's allocation that will stay fixed when zooming. If @have_anchor + * is %FALSE, then the center point of the image view will be used. + **/ +static void +set_zoom (EomScrollView *view, double zoom, + gboolean have_anchor, int anchorx, int anchory) +{ + EomScrollViewPrivate *priv; + GtkAllocation allocation; + int xofs, yofs; + double x_rel, y_rel; + + g_assert (zoom > 0.0); + + priv = view->priv; + + if (priv->pixbuf == NULL) + return; + + if (zoom > MAX_ZOOM_FACTOR) + zoom = MAX_ZOOM_FACTOR; + else if (zoom < MIN_ZOOM_FACTOR) + zoom = MIN_ZOOM_FACTOR; + + if (DOUBLE_EQUAL (priv->zoom, zoom)) + return; + if (DOUBLE_EQUAL (priv->zoom, priv->min_zoom) && zoom < priv->zoom) + return; + + priv->zoom_mode = ZOOM_MODE_FREE; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + /* compute new xofs/yofs values */ + if (have_anchor) { + x_rel = (double) anchorx / allocation.width; + y_rel = (double) anchory / allocation.height; + } else { + x_rel = 0.5; + y_rel = 0.5; + } + + compute_center_zoom_offsets (view, priv->zoom, zoom, + allocation.width, allocation.height, + x_rel, y_rel, + &xofs, &yofs); + + /* set new values */ + priv->xofs = xofs; /* (img_width * x_rel * zoom) - anchorx; */ + priv->yofs = yofs; /* (img_height * y_rel * zoom) - anchory; */ +#if 0 + g_print ("xofs: %i yofs: %i\n", priv->xofs, priv->yofs); +#endif + if (zoom <= priv->min_zoom) + priv->zoom = priv->min_zoom; + else + priv->zoom = zoom; + + /* we make use of the new values here */ + check_scrollbar_visibility (view, NULL); + update_scrollbar_values (view); + + /* repaint the whole image */ + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + + g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom); +} + +/* Zooms the image to fit the available allocation */ +static void +set_zoom_fit (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + GtkAllocation allocation; + double new_zoom; + + priv = view->priv; + + priv->zoom_mode = ZOOM_MODE_FIT; + + if (!gtk_widget_get_mapped (GTK_WIDGET (view))) + return; + + if (priv->pixbuf == NULL) + return; + + gtk_widget_get_allocation (GTK_WIDGET(priv->display), &allocation); + + new_zoom = zoom_fit_scale (allocation.width, allocation.height, + gdk_pixbuf_get_width (priv->pixbuf), + gdk_pixbuf_get_height (priv->pixbuf), + priv->upscale); + + if (new_zoom > MAX_ZOOM_FACTOR) + new_zoom = MAX_ZOOM_FACTOR; + else if (new_zoom < MIN_ZOOM_FACTOR) + new_zoom = MIN_ZOOM_FACTOR; + + priv->zoom = new_zoom; + priv->xofs = 0; + priv->yofs = 0; + + g_signal_emit (view, view_signals [SIGNAL_ZOOM_CHANGED], 0, priv->zoom); +} + +/*=================================== + + internal signal callbacks + + ---------------------------------*/ + +/* Key press event handler for the image view */ +static gboolean +display_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + GtkAllocation allocation; + gboolean do_zoom; + double zoom; + gboolean do_scroll; + int xofs, yofs; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + do_zoom = FALSE; + do_scroll = FALSE; + xofs = yofs = 0; + zoom = 1.0; + + gtk_widget_get_allocation (GTK_WIDGET (priv->display), &allocation); + + /* EomScrollView doesn't handle/have any Alt+Key combos */ + if (event->state & GDK_MOD1_MASK) { + return FALSE; + } + + switch (event->keyval) { + case GDK_Up: + do_scroll = TRUE; + xofs = 0; + yofs = -SCROLL_STEP_SIZE; + break; + + case GDK_Page_Up: + do_scroll = TRUE; + if (event->state & GDK_CONTROL_MASK) { + xofs = -(allocation.width * 3) / 4; + yofs = 0; + } else { + xofs = 0; + yofs = -(allocation.height * 3) / 4; + } + break; + + case GDK_Down: + do_scroll = TRUE; + xofs = 0; + yofs = SCROLL_STEP_SIZE; + break; + + case GDK_Page_Down: + do_scroll = TRUE; + if (event->state & GDK_CONTROL_MASK) { + xofs = (allocation.width * 3) / 4; + yofs = 0; + } else { + xofs = 0; + yofs = (allocation.height * 3) / 4; + } + break; + + case GDK_Left: + do_scroll = TRUE; + xofs = -SCROLL_STEP_SIZE; + yofs = 0; + break; + + case GDK_Right: + do_scroll = TRUE; + xofs = SCROLL_STEP_SIZE; + yofs = 0; + break; + + case GDK_plus: + case GDK_equal: + case GDK_KP_Add: + do_zoom = TRUE; + zoom = priv->zoom * priv->zoom_multiplier; + break; + + case GDK_minus: + case GDK_KP_Subtract: + do_zoom = TRUE; + zoom = priv->zoom / priv->zoom_multiplier; + break; + + case GDK_1: + do_zoom = TRUE; + zoom = 1.0; + break; + + default: + return FALSE; + } + + if (do_zoom) { + gint x, y; + + gdk_window_get_pointer (gtk_widget_get_window (widget), + &x, &y, NULL); + set_zoom (view, zoom, TRUE, x, y); + } + + if (do_scroll) + scroll_by (view, xofs, yofs); + + return TRUE; +} + + +/* Button press event handler for the image view */ +static gboolean +eom_scroll_view_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (!gtk_widget_has_focus (priv->display)) + gtk_widget_grab_focus (GTK_WIDGET (priv->display)); + + if (priv->dragging) + return FALSE; + + switch (event->button) { + case 1: + case 2: + if (event->button == 1 && !priv->scroll_wheel_zoom && + !(event->state & GDK_CONTROL_MASK)) + break; + + if (is_image_movable (view)) { + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_DRAG); + + priv->dragging = TRUE; + priv->drag_anchor_x = event->x; + priv->drag_anchor_y = event->y; + + priv->drag_ofs_x = priv->xofs; + priv->drag_ofs_y = priv->yofs; + + return TRUE; + } + default: + break; + } + + return FALSE; +} + +static void +eom_scroll_view_style_set (GtkWidget *widget, GtkStyle *old_style) +{ + GtkStyle *style; + EomScrollViewPrivate *priv; + + style = gtk_widget_get_style (widget); + priv = EOM_SCROLL_VIEW (widget)->priv; + + gtk_widget_set_style (priv->display, style); +} + + +/* Button release event handler for the image view */ +static gboolean +eom_scroll_view_button_release_event (GtkWidget *widget, GdkEventButton *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (!priv->dragging) + return FALSE; + + switch (event->button) { + case 1: + case 2: + drag_to (view, event->x, event->y); + priv->dragging = FALSE; + + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL); + break; + + default: + break; + } + + return TRUE; +} + +/* Scroll event handler for the image view. We zoom with an event without + * modifiers rather than scroll; we use the Shift modifier to scroll. + * Rationale: images are not primarily vertical, and in EOM you scan scroll by + * dragging the image with button 1 anyways. + */ +static gboolean +eom_scroll_view_scroll_event (GtkWidget *widget, GdkEventScroll *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + double zoom_factor; + int xofs, yofs; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + /* Compute zoom factor and scrolling offsets; we'll only use either of them */ + /* same as in gtkscrolledwindow.c */ + xofs = gtk_adjustment_get_page_increment (priv->hadj) / 2; + yofs = gtk_adjustment_get_page_increment (priv->vadj) / 2; + + switch (event->direction) { + case GDK_SCROLL_UP: + zoom_factor = priv->zoom_multiplier; + xofs = 0; + yofs = -yofs; + break; + + case GDK_SCROLL_LEFT: + zoom_factor = 1.0 / priv->zoom_multiplier; + xofs = -xofs; + yofs = 0; + break; + + case GDK_SCROLL_DOWN: + zoom_factor = 1.0 / priv->zoom_multiplier; + xofs = 0; + yofs = yofs; + break; + + case GDK_SCROLL_RIGHT: + zoom_factor = priv->zoom_multiplier; + xofs = xofs; + yofs = 0; + break; + + default: + g_assert_not_reached (); + return FALSE; + } + + if (priv->scroll_wheel_zoom) { + if (event->state & GDK_SHIFT_MASK) + scroll_by (view, yofs, xofs); + else if (event->state & GDK_CONTROL_MASK) + scroll_by (view, xofs, yofs); + else + set_zoom (view, priv->zoom * zoom_factor, + TRUE, event->x, event->y); + } else { + if (event->state & GDK_SHIFT_MASK) + scroll_by (view, yofs, xofs); + else if (event->state & GDK_CONTROL_MASK) + set_zoom (view, priv->zoom * zoom_factor, + TRUE, event->x, event->y); + else + scroll_by (view, xofs, yofs); + } + + return TRUE; +} + +/* Motion event handler for the image view */ +static gboolean +eom_scroll_view_motion_event (GtkWidget *widget, GdkEventMotion *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + gint x, y; + GdkModifierType mods; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (!priv->dragging) + return FALSE; + + if (event->is_hint) + gdk_window_get_pointer (gtk_widget_get_window (GTK_WIDGET (priv->display)), &x, &y, &mods); + else { + x = event->x; + y = event->y; + } + + drag_to (view, x, y); + return TRUE; +} + +static void +display_map_event (GtkWidget *widget, GdkEvent *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + eom_debug (DEBUG_WINDOW); + + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); +} + +static void +eom_scroll_view_size_allocate (GtkWidget *widget, GtkAllocation *alloc) +{ + EomScrollView *view; + + view = EOM_SCROLL_VIEW (widget); + check_scrollbar_visibility (view, alloc); + + GTK_WIDGET_CLASS (eom_scroll_view_parent_class)->size_allocate (widget + ,alloc); +} + +static void +display_size_change (GtkWidget *widget, GdkEventConfigure *event, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (priv->zoom_mode == ZOOM_MODE_FIT) { + GtkAllocation alloc; + + alloc.width = event->width; + alloc.height = event->height; + + set_zoom_fit (view); + check_scrollbar_visibility (view, &alloc); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } else { + int scaled_width, scaled_height; + int x_offset = 0; + int y_offset = 0; + + compute_scaled_size (view, priv->zoom, &scaled_width, &scaled_height); + + if (priv->xofs + event->width > scaled_width) + x_offset = scaled_width - event->width - priv->xofs; + + if (priv->yofs + event->height > scaled_height) + y_offset = scaled_height - event->height - priv->yofs; + + scroll_by (view, x_offset, y_offset); + } + + update_scrollbar_values (view); +} + + +static gboolean +eom_scroll_view_focus_in_event (GtkWidget *widget, + GdkEventFocus *event, + gpointer data) +{ + g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_in_event"); + return FALSE; +} + +static gboolean +eom_scroll_view_focus_out_event (GtkWidget *widget, + GdkEventFocus *event, + gpointer data) +{ + g_signal_stop_emission_by_name (G_OBJECT (widget), "focus_out_event"); + return FALSE; +} + +/* Expose event handler for the drawing area. First we process the whole dirty + * region by drawing a non-interpolated version, which is "instantaneous", and + * we do this synchronously. Then, if we are set to use interpolation, we queue + * an idle handler to handle interpolated drawing there. + */ +static gboolean +display_expose_event (GtkWidget *widget, GdkEventExpose *event, gpointer data) +{ + EomScrollView *view; + GdkRectangle *rects; + gint n_rects; + int i; + + g_return_val_if_fail (GTK_IS_DRAWING_AREA (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (data), FALSE); + + view = EOM_SCROLL_VIEW (data); + + gdk_region_get_rectangles (event->region, &rects, &n_rects); + + for (i = 0; i < n_rects; i++) { + request_paint_area (view, rects + i); + } + + g_free (rects); + + return TRUE; +} + + +/*================================== + + image loading callbacks + + -----------------------------------*/ +/* +static void +image_loading_update_cb (EomImage *img, int x, int y, int width, int height, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + GdkRectangle area; + int xofs, yofs; + int sx0, sy0, sx1, sy1; + + view = (EomScrollView*) data; + priv = view->priv; + + eom_debug_message (DEBUG_IMAGE_LOAD, "x: %i, y: %i, width: %i, height: %i\n", + x, y, width, height); + + if (priv->pixbuf == NULL) { + priv->pixbuf = eom_image_get_pixbuf (img); + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + } + priv->progressive_state = PROGRESSIVE_LOADING; + + get_image_offsets (view, &xofs, &yofs); + + sx0 = floor (x * priv->zoom + xofs); + sy0 = floor (y * priv->zoom + yofs); + sx1 = ceil ((x + width) * priv->zoom + xofs); + sy1 = ceil ((y + height) * priv->zoom + yofs); + + area.x = sx0; + area.y = sy0; + area.width = sx1 - sx0; + area.height = sy1 - sy0; + + if (GTK_WIDGET_DRAWABLE (priv->display)) + gdk_window_invalidate_rect (GTK_WIDGET (priv->display)->window, &area, FALSE); +} + + +static void +image_loading_finished_cb (EomImage *img, gpointer data) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + view = (EomScrollView*) data; + priv = view->priv; + + if (priv->pixbuf == NULL) { + priv->pixbuf = eom_image_get_pixbuf (img); + priv->progressive_state = PROGRESSIVE_NONE; + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + + } + else if (priv->interp_type != GDK_INTERP_NEAREST && + !is_unity_zoom (view)) + { + // paint antialiased image version + priv->progressive_state = PROGRESSIVE_POLISHING; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +static void +image_loading_failed_cb (EomImage *img, char *msg, gpointer data) +{ + EomScrollViewPrivate *priv; + + priv = EOM_SCROLL_VIEW (data)->priv; + + g_print ("loading failed: %s.\n", msg); + + if (priv->pixbuf != 0) { + g_object_unref (priv->pixbuf); + priv->pixbuf = 0; + } + + if (GTK_WIDGET_DRAWABLE (priv->display)) { + gdk_window_clear (GTK_WIDGET (priv->display)->window); + } +} + +static void +image_loading_cancelled_cb (EomImage *img, gpointer data) +{ + EomScrollViewPrivate *priv; + + priv = EOM_SCROLL_VIEW (data)->priv; + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } + + if (GTK_WIDGET_DRAWABLE (priv->display)) { + gdk_window_clear (GTK_WIDGET (priv->display)->window); + } +} +*/ +static void +image_changed_cb (EomImage *img, gpointer data) +{ + EomScrollViewPrivate *priv; + + priv = EOM_SCROLL_VIEW (data)->priv; + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } + + priv->pixbuf = eom_image_get_pixbuf (img); + + set_zoom_fit (EOM_SCROLL_VIEW (data)); + check_scrollbar_visibility (EOM_SCROLL_VIEW (data), NULL); + + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); +} + +/*=================================== + public API + ---------------------------------*/ + +void +eom_scroll_view_hide_cursor (EomScrollView *view) +{ + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_HIDDEN); +} + +void +eom_scroll_view_show_cursor (EomScrollView *view) +{ + eom_scroll_view_set_cursor (view, EOM_SCROLL_VIEW_CURSOR_NORMAL); +} + +/* general properties */ +void +eom_scroll_view_set_zoom_upscale (EomScrollView *view, gboolean upscale) +{ + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (priv->upscale != upscale) { + priv->upscale = upscale; + + if (priv->zoom_mode == ZOOM_MODE_FIT) { + set_zoom_fit (view); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } + } +} + +void +eom_scroll_view_set_antialiasing_in (EomScrollView *view, gboolean state) +{ + EomScrollViewPrivate *priv; + GdkInterpType new_interp_type; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + new_interp_type = state ? GDK_INTERP_BILINEAR : GDK_INTERP_NEAREST; + + if (priv->interp_type_in != new_interp_type) { + priv->interp_type_in = new_interp_type; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +void +eom_scroll_view_set_antialiasing_out (EomScrollView *view, gboolean state) +{ + EomScrollViewPrivate *priv; + GdkInterpType new_interp_type; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + new_interp_type = state ? GDK_INTERP_BILINEAR : GDK_INTERP_NEAREST; + + if (priv->interp_type_out != new_interp_type) { + priv->interp_type_out = new_interp_type; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +void +eom_scroll_view_set_transparency (EomScrollView *view, EomTransparencyStyle style, GdkColor *color) +{ + EomScrollViewPrivate *priv; + guint32 col = 0; + guint32 red, green, blue; + gboolean changed = FALSE; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (color != NULL) { + red = (color->red >> 8) << 16; + green = (color->green >> 8) << 8; + blue = (color->blue >> 8); + col = red + green + blue; + } + + if (priv->transp_style != style) { + priv->transp_style = style; + changed = TRUE; + } + + if (priv->transp_style == EOM_TRANSP_COLOR && priv->transp_color != col) { + priv->transp_color = col; + changed = TRUE; + } + + if (changed && priv->pixbuf != NULL && gdk_pixbuf_get_has_alpha (priv->pixbuf)) { + if (priv->background_surface) { + cairo_surface_destroy (priv->background_surface); + /* Will be recreated if needed during redraw */ + priv->background_surface = NULL; + } + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } +} + +/* zoom api */ + +static double preferred_zoom_levels[] = { + 1.0 / 100, 1.0 / 50, 1.0 / 20, + 1.0 / 10.0, 1.0 / 5.0, 1.0 / 3.0, 1.0 / 2.0, 1.0 / 1.5, + 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, + 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0 +}; +static const gint n_zoom_levels = (sizeof (preferred_zoom_levels) / sizeof (double)); + +void +eom_scroll_view_zoom_in (EomScrollView *view, gboolean smooth) +{ + EomScrollViewPrivate *priv; + double zoom; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (smooth) { + zoom = priv->zoom * priv->zoom_multiplier; + } + else { + int i; + int index = -1; + + for (i = 0; i < n_zoom_levels; i++) { + if (preferred_zoom_levels [i] - priv->zoom + > DOUBLE_EQUAL_MAX_DIFF) { + index = i; + break; + } + } + + if (index == -1) { + zoom = priv->zoom; + } + else { + zoom = preferred_zoom_levels [i]; + } + } + set_zoom (view, zoom, FALSE, 0, 0); + +} + +void +eom_scroll_view_zoom_out (EomScrollView *view, gboolean smooth) +{ + EomScrollViewPrivate *priv; + double zoom; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (smooth) { + zoom = priv->zoom / priv->zoom_multiplier; + } + else { + int i; + int index = -1; + + for (i = n_zoom_levels - 1; i >= 0; i--) { + if (priv->zoom - preferred_zoom_levels [i] + > DOUBLE_EQUAL_MAX_DIFF) { + index = i; + break; + } + } + if (index == -1) { + zoom = priv->zoom; + } + else { + zoom = preferred_zoom_levels [i]; + } + } + set_zoom (view, zoom, FALSE, 0, 0); +} + +void +eom_scroll_view_zoom_fit (EomScrollView *view) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (view->priv->display)); +} + +void +eom_scroll_view_set_zoom (EomScrollView *view, double zoom) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + set_zoom (view, zoom, FALSE, 0, 0); +} + +double +eom_scroll_view_get_zoom (EomScrollView *view) +{ + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), 0.0); + + return view->priv->zoom; +} + +gboolean +eom_scroll_view_get_zoom_is_min (EomScrollView *view) +{ + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE); + + set_minimum_zoom_factor (view); + + return DOUBLE_EQUAL (view->priv->zoom, MIN_ZOOM_FACTOR) || + DOUBLE_EQUAL (view->priv->zoom, view->priv->min_zoom); +} + +gboolean +eom_scroll_view_get_zoom_is_max (EomScrollView *view) +{ + g_return_val_if_fail (EOM_IS_SCROLL_VIEW (view), FALSE); + + return DOUBLE_EQUAL (view->priv->zoom, MAX_ZOOM_FACTOR); +} + +static void +display_next_frame_cb (EomImage *image, gint delay, gpointer data) +{ + EomScrollViewPrivate *priv; + EomScrollView *view; + + if (!EOM_IS_SCROLL_VIEW (data)) + return; + + view = EOM_SCROLL_VIEW (data); + priv = view->priv; + + if (priv->pixbuf != NULL) { + g_object_unref (priv->pixbuf); + priv->pixbuf = NULL; + } + + priv->pixbuf = eom_image_get_pixbuf (image); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); +} + +void +eom_scroll_view_set_image (EomScrollView *view, EomImage *image) +{ + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (priv->image == image) { + return; + } + + if (priv->image != NULL) { + free_image_resources (view); + if (gtk_widget_is_drawable (priv->display) && image == NULL) { + gdk_window_clear (gtk_widget_get_window (priv->display)); + } + } + g_assert (priv->image == NULL); + g_assert (priv->pixbuf == NULL); + + priv->progressive_state = PROGRESSIVE_NONE; + if (image != NULL) { + eom_image_data_ref (image); + + if (priv->pixbuf == NULL) { + priv->pixbuf = eom_image_get_pixbuf (image); + priv->progressive_state = PROGRESSIVE_NONE; + set_zoom_fit (view); + check_scrollbar_visibility (view, NULL); + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + + } + else if ((is_zoomed_in (view) && priv->interp_type_in != GDK_INTERP_NEAREST) || + (is_zoomed_out (view) && priv->interp_type_out != GDK_INTERP_NEAREST)) + { + /* paint antialiased image version */ + priv->progressive_state = PROGRESSIVE_POLISHING; + gtk_widget_queue_draw (GTK_WIDGET (priv->display)); + } + + priv->image_changed_id = g_signal_connect (image, "changed", + (GCallback) image_changed_cb, view); + if (eom_image_is_animation (image) == TRUE ) { + eom_image_start_animation (image); + priv->frame_changed_id = g_signal_connect (image, "next-frame", + (GCallback) display_next_frame_cb, view); + } + } + + priv->image = image; +} + +gboolean +eom_scroll_view_scrollbars_visible (EomScrollView *view) +{ + if (!gtk_widget_get_visible (GTK_WIDGET (view->priv->hbar)) && + !gtk_widget_get_visible (GTK_WIDGET (view->priv->vbar))) + return FALSE; + + return TRUE; +} + +/*=================================== + object creation/freeing + ---------------------------------*/ + +static void +eom_scroll_view_init (EomScrollView *view) +{ + EomScrollViewPrivate *priv; + + priv = view->priv = EOM_SCROLL_VIEW_GET_PRIVATE (view); + + priv->zoom = 1.0; + priv->min_zoom = MIN_ZOOM_FACTOR; + priv->zoom_mode = ZOOM_MODE_FIT; + priv->upscale = FALSE; + priv->uta = NULL; + priv->interp_type_in = GDK_INTERP_BILINEAR; + priv->interp_type_out = GDK_INTERP_BILINEAR; + priv->scroll_wheel_zoom = FALSE; + priv->zoom_multiplier = IMAGE_VIEW_ZOOM_MULTIPLIER; + priv->image = NULL; + priv->pixbuf = NULL; + priv->progressive_state = PROGRESSIVE_NONE; + priv->transp_style = EOM_TRANSP_BACKGROUND; + priv->transp_color = 0; + priv->cursor = EOM_SCROLL_VIEW_CURSOR_NORMAL; + priv->menu = NULL; + priv->background_color = NULL; + priv->override_bg_color = NULL; + priv->background_surface = NULL; +} + +static void +eom_scroll_view_dispose (GObject *object) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (object)); + + view = EOM_SCROLL_VIEW (object); + priv = view->priv; + + if (priv->uta != NULL) { + eom_uta_free (priv->uta); + priv->uta = NULL; + } + + if (priv->idle_id != 0) { + g_source_remove (priv->idle_id); + priv->idle_id = 0; + } + + if (priv->background_color != NULL) { + gdk_color_free (priv->background_color); + priv->background_color = NULL; + } + + if (priv->override_bg_color != NULL) { + gdk_color_free (priv->override_bg_color); + priv->override_bg_color = NULL; + } + + if (priv->background_surface != NULL) { + cairo_surface_destroy (priv->background_surface); + priv->background_surface = NULL; + } + + free_image_resources (view); + + G_OBJECT_CLASS (eom_scroll_view_parent_class)->dispose (object); +} + +static void +eom_scroll_view_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (object)); + + view = EOM_SCROLL_VIEW (object); + priv = view->priv; + + switch (property_id) { + case PROP_USE_BG_COLOR: + g_value_set_boolean (value, priv->use_bg_color); + break; + case PROP_BACKGROUND_COLOR: + //FIXME: This doesn't really handle the NULL color. + g_value_set_boxed (value, priv->background_color); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + +static void +eom_scroll_view_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec) +{ + EomScrollView *view; + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (object)); + + view = EOM_SCROLL_VIEW (object); + priv = view->priv; + + switch (property_id) { + case PROP_USE_BG_COLOR: + eom_scroll_view_set_use_bg_color (view, g_value_get_boolean (value)); + break; + case PROP_BACKGROUND_COLOR: + { + const GdkColor *color = g_value_get_boxed (value); + eom_scroll_view_set_background_color (view, color); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + } +} + + +static void +eom_scroll_view_class_init (EomScrollViewClass *klass) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + + gobject_class = (GObjectClass*) klass; + widget_class = (GtkWidgetClass*) klass; + + gobject_class->dispose = eom_scroll_view_dispose; + gobject_class->set_property = eom_scroll_view_set_property; + gobject_class->get_property = eom_scroll_view_get_property; + + /** + * EomScrollView:background-color: + * + * This is the default background color used for painting the background + * of the image view. If set to %NULL the color is determined by the + * active GTK theme. + */ + g_object_class_install_property ( + gobject_class, PROP_BACKGROUND_COLOR, + g_param_spec_boxed ("background-color", NULL, NULL, + GDK_TYPE_COLOR, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + g_object_class_install_property ( + gobject_class, PROP_USE_BG_COLOR, + g_param_spec_boolean ("use-background-color", NULL, NULL, FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_NAME)); + + view_signals [SIGNAL_ZOOM_CHANGED] = + g_signal_new ("zoom_changed", + EOM_TYPE_SCROLL_VIEW, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (EomScrollViewClass, zoom_changed), + NULL, NULL, + eom_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + widget_class->size_allocate = eom_scroll_view_size_allocate; + widget_class->style_set = eom_scroll_view_style_set; + + g_type_class_add_private (klass, sizeof (EomScrollViewPrivate)); +} + +static void +view_on_drag_begin_cb (GtkWidget *widget, + GdkDragContext *context, + gpointer user_data) +{ + EomScrollView *view; + EomImage *image; + GdkPixbuf *thumbnail; + gint width, height; + + view = EOM_SCROLL_VIEW (user_data); + image = view->priv->image; + + thumbnail = eom_image_get_thumbnail (image); + + if (thumbnail) { + width = gdk_pixbuf_get_width (thumbnail); + height = gdk_pixbuf_get_height (thumbnail); + gtk_drag_set_icon_pixbuf (context, thumbnail, width/2, height/2); + g_object_unref (thumbnail); + } +} + +static void +view_on_drag_data_get_cb (GtkWidget *widget, + GdkDragContext *drag_context, + GtkSelectionData *data, + guint info, + guint time, + gpointer user_data) +{ + EomScrollView *view; + EomImage *image; + gchar *uris[2]; + GFile *file; + + view = EOM_SCROLL_VIEW (user_data); + + image = view->priv->image; + + file = eom_image_get_file (image); + uris[0] = g_file_get_uri (file); + uris[1] = NULL; + + gtk_selection_data_set_uris (data, uris); + + g_free (uris[0]); + g_object_unref (file); +} + +GtkWidget* +eom_scroll_view_new (void) +{ + GtkWidget *widget; + GtkTable *table; + EomScrollView *view; + EomScrollViewPrivate *priv; + + widget = g_object_new (EOM_TYPE_SCROLL_VIEW, + "can-focus", TRUE, + "n_rows", 2, + "n_columns", 2, + "homogeneous", FALSE, + NULL); + + table = GTK_TABLE (widget); + view = EOM_SCROLL_VIEW (widget); + priv = view->priv; + + priv->hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100)); + g_signal_connect (priv->hadj, "value_changed", + G_CALLBACK (adjustment_changed_cb), + view); + priv->hbar = gtk_hscrollbar_new (priv->hadj); + priv->vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0, 100, 0, 10, 10, 100)); + g_signal_connect (priv->vadj, "value_changed", + G_CALLBACK (adjustment_changed_cb), + view); + priv->vbar = gtk_vscrollbar_new (priv->vadj); + priv->display = g_object_new (GTK_TYPE_DRAWING_AREA, + "can-focus", TRUE, + NULL); + /* We don't want to be double-buffered as we are SuperSmart(tm) */ + gtk_widget_set_double_buffered (GTK_WIDGET (priv->display), FALSE); + + gtk_widget_add_events (GTK_WIDGET (priv->display), + GDK_EXPOSURE_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK + | GDK_SCROLL_MASK + | GDK_KEY_PRESS_MASK); + g_signal_connect (G_OBJECT (priv->display), "configure_event", G_CALLBACK (display_size_change), view); + g_signal_connect (G_OBJECT (priv->display), "expose_event", G_CALLBACK (display_expose_event), view); + g_signal_connect (G_OBJECT (priv->display), "map_event", G_CALLBACK (display_map_event), view); + g_signal_connect (G_OBJECT (priv->display), "button_press_event", G_CALLBACK (eom_scroll_view_button_press_event), view); + g_signal_connect (G_OBJECT (priv->display), "motion_notify_event", G_CALLBACK (eom_scroll_view_motion_event), view); + g_signal_connect (G_OBJECT (priv->display), "button_release_event", G_CALLBACK (eom_scroll_view_button_release_event), view); + g_signal_connect (G_OBJECT (priv->display), "scroll_event", G_CALLBACK (eom_scroll_view_scroll_event), view); + g_signal_connect (G_OBJECT (priv->display), "focus_in_event", G_CALLBACK (eom_scroll_view_focus_in_event), NULL); + g_signal_connect (G_OBJECT (priv->display), "focus_out_event", G_CALLBACK (eom_scroll_view_focus_out_event), NULL); + + g_signal_connect (G_OBJECT (widget), "key_press_event", G_CALLBACK (display_key_press_event), view); + + gtk_drag_source_set (priv->display, GDK_BUTTON1_MASK, + target_table, G_N_ELEMENTS (target_table), + GDK_ACTION_COPY); + g_signal_connect (G_OBJECT (priv->display), "drag-data-get", + G_CALLBACK (view_on_drag_data_get_cb), widget); + g_signal_connect (G_OBJECT (priv->display), "drag-begin", + G_CALLBACK (view_on_drag_begin_cb), widget); + + gtk_table_attach (table, priv->display, + 0, 1, 0, 1, + GTK_EXPAND | GTK_FILL, + GTK_EXPAND | GTK_FILL, + 0,0); + gtk_table_attach (table, priv->hbar, + 0, 1, 1, 2, + GTK_FILL, + GTK_FILL, + 0, 0); + gtk_table_attach (table, priv->vbar, + 1, 2, 0, 1, + GTK_FILL, GTK_FILL, + 0, 0); + + gtk_widget_show_all (widget); + + return widget; +} + +static void +eom_scroll_view_popup_menu (EomScrollView *view, GdkEventButton *event) +{ + GtkWidget *popup; + int button, event_time; + + popup = view->priv->menu; + + if (event) { + button = event->button; + event_time = event->time; + } else { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL, + button, event_time); +} + +static gboolean +view_on_button_press_event_cb (GtkWidget *view, GdkEventButton *event, + gpointer user_data) +{ + /* Ignore double-clicks and triple-clicks */ + if (event->button == 3 && event->type == GDK_BUTTON_PRESS) + { + eom_scroll_view_popup_menu (EOM_SCROLL_VIEW (view), event); + + return TRUE; + } + + return FALSE; +} + +void +eom_scroll_view_set_popup (EomScrollView *view, + GtkMenu *menu) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + g_return_if_fail (view->priv->menu == NULL); + + view->priv->menu = g_object_ref (menu); + + gtk_menu_attach_to_widget (GTK_MENU (view->priv->menu), + GTK_WIDGET (view), + NULL); + + g_signal_connect (G_OBJECT (view), "button_press_event", + G_CALLBACK (view_on_button_press_event_cb), NULL); +} + +static gboolean +_eom_gdk_color_equal0 (const GdkColor *a, const GdkColor *b) +{ + if (a == NULL || b == NULL) + return (a == b); + + return gdk_color_equal (a, b); +} + +static gboolean +_eom_replace_gdk_color (GdkColor **dest, const GdkColor *new) +{ + GdkColor *old = *dest; + + if (_eom_gdk_color_equal0 (old, new)) + return FALSE; + + if (old != NULL) + gdk_color_free (old); + + *dest = (new) ? gdk_color_copy (new) : NULL; + + return TRUE; +} + +static void +_eom_scroll_view_update_bg_color (EomScrollView *view) +{ + const GdkColor *selected; + EomScrollViewPrivate *priv = view->priv; + + if (priv->override_bg_color) + selected = priv->override_bg_color; + else if (priv->use_bg_color) + selected = priv->background_color; + else + selected = NULL; + + if (priv->transp_style == EOM_TRANSP_BACKGROUND + && priv->background_surface != NULL) { + /* Delete the SVG background to have it recreated with + * the correct color during the next SVG redraw */ + cairo_surface_destroy (priv->background_surface); + priv->background_surface = NULL; + } + + gtk_widget_modify_bg (GTK_WIDGET (view), + GTK_STATE_NORMAL, + selected); +} + +void +eom_scroll_view_set_background_color (EomScrollView *view, + const GdkColor *color) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + if (_eom_replace_gdk_color (&view->priv->background_color, color)) + _eom_scroll_view_update_bg_color (view); +} + +void +eom_scroll_view_override_bg_color (EomScrollView *view, + const GdkColor *color) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + if (_eom_replace_gdk_color (&view->priv->override_bg_color, color)) + _eom_scroll_view_update_bg_color (view); +} + +void +eom_scroll_view_set_use_bg_color (EomScrollView *view, gboolean use) +{ + EomScrollViewPrivate *priv; + + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + priv = view->priv; + + if (use != priv->use_bg_color) { + priv->use_bg_color = use; + + _eom_scroll_view_update_bg_color (view); + + g_object_notify (G_OBJECT (view), "use-background-color"); + } +} + +void +eom_scroll_view_set_scroll_wheel_zoom (EomScrollView *view, + gboolean scroll_wheel_zoom) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + view->priv->scroll_wheel_zoom = scroll_wheel_zoom; +} + +void +eom_scroll_view_set_zoom_multiplier (EomScrollView *view, + gdouble zoom_multiplier) +{ + g_return_if_fail (EOM_IS_SCROLL_VIEW (view)); + + view->priv->zoom_multiplier = 1.0 + zoom_multiplier; +} |