/* Eye of Gnome image viewer - image view widget * * Copyright (C) 2000 The Free Software Foundation * * Author: Federico Mena-Quintero * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include "cursors.h" #include "image-view.h" #include "uta.h" #include "lcms.h" /* Checks */ #define CHECK_SMALL 4 #define CHECK_MEDIUM 8 #define CHECK_LARGE 16 #define CHECK_BLACK 0x00000000 #define CHECK_DARK 0x00555555 #define CHECK_GRAY 0x00808080 #define CHECK_LIGHT 0x00aaaaaa #define CHECK_WHITE 0x00ffffff /* 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 10 #define MIN_ZOOM_FACTOR 0.05 /* Private part of the ImageView structure */ struct _ImageViewPrivate { /* Pixbuf being displayed */ GdkPixbuf *pixbuf; /* Current zoom factors */ double zoomx; double zoomy; /* Minimum Zoom Factor for This Pixbuf */ double MIN_ZOOM; /* Previous zoom factor and zoom anchor point stored for size_allocate */ double old_zoomx; double old_zoomy; double zoom_x_anchor; double zoom_y_anchor; /* Adjustments for scrolling */ GtkAdjustment *hadj; GtkAdjustment *vadj; /* Current scrolling offsets */ int xofs, yofs; /* Microtile arrays for dirty region. This represents the dirty region * for interpolated drawing. */ ArtUta *uta; /* Idle handler ID */ guint idle_id; /* Anchor point and offsets for dragging */ int drag_anchor_x, drag_anchor_y; int drag_ofs_x, drag_ofs_y; /* Interpolation type */ GdkInterpType interp_type; /* Check type and size */ CheckType check_type; CheckSize check_size; /* Transparency indicator */ gboolean use_check_pattern; guint32 transparency_color; /* Dither type */ GdkRgbDither dither; /* Whether the image is being dragged */ guint dragging : 1; /* Whether we need to change the zoom factor */ guint need_zoom_change : 1; guint enable_late_drawing : 1; #ifdef LIBEOG_ETTORE_CHANGES float display_brightness; float display_contrast; cmsHTRANSFORM transform; #endif }; /* Signal IDs */ enum { ZOOM_FIT, ZOOM_CHANGED, LAST_SIGNAL }; enum { PROP_0, PROP_INTERP_TYPE, PROP_CHECK_TYPE, PROP_CHECK_SIZE, PROP_DITHER }; static guint image_view_signals[LAST_SIGNAL]; GNOME_CLASS_BOILERPLATE (ImageView, image_view, GtkWidget, GTK_TYPE_WIDGET); /* VOID:OBJECT,OBJECT (gtkmarshalers.list:69) */ void marshal_VOID__OBJECT_OBJECT (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1, gpointer arg_1, gpointer arg_2, gpointer data2); register GMarshalFunc_VOID__OBJECT_OBJECT callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 3); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback); callback (data1, g_value_get_object (param_values + 1), g_value_get_object (param_values + 2), data2); } static void image_view_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { ImageView *image_view = IMAGE_VIEW (object); ImageViewPrivate *priv = image_view->priv; switch (property_id) { case PROP_INTERP_TYPE: g_value_set_int (value, priv->interp_type); break; case PROP_CHECK_TYPE: g_value_set_int (value, priv->check_type); break; case PROP_CHECK_SIZE: g_value_set_int (value, priv->check_size); break; case PROP_DITHER: g_value_set_int (value, priv->dither); break; default: g_warning ("unknown property id `%d'", property_id); break; } } static void image_view_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { ImageView *image_view = IMAGE_VIEW (object); switch (property_id) { case PROP_INTERP_TYPE: image_view_set_interp_type (image_view, g_value_get_int (value)); break; case PROP_CHECK_TYPE: image_view_set_check_type (image_view, g_value_get_int (value)); break; case PROP_CHECK_SIZE: image_view_set_check_size (image_view, g_value_get_int (value)); break; case PROP_DITHER: image_view_set_dither (image_view, g_value_get_int (value)); break; default: g_warning ("unknown property id `%d'", property_id); break; } } /* Object initialization function for the image view */ static void image_view_instance_init (ImageView *view) { ImageViewPrivate *priv; priv = g_new0 (ImageViewPrivate, 1); view->priv = priv; GTK_WIDGET_UNSET_FLAGS (view, GTK_NO_WINDOW); GTK_WIDGET_SET_FLAGS (view, GTK_CAN_FOCUS); priv->pixbuf = NULL; priv->zoomx = priv->zoomy = 1.0; priv->hadj = NULL; priv->vadj = NULL; priv->uta = NULL; /* Defaults for rendering */ priv->interp_type = GDK_INTERP_BILINEAR; priv->check_type = CHECK_TYPE_MIDTONE; priv->check_size = CHECK_SIZE_LARGE; priv->dither = GDK_RGB_DITHER_MAX; priv->use_check_pattern = TRUE; priv->transparency_color = CHECK_BLACK; priv->enable_late_drawing = FALSE; #ifdef LIBEOG_ETTORE_CHANGES priv->display_brightness = 0.0; priv->display_contrast = 0.0; priv->transform = NULL; #endif /* We don't want to be double-buffered as we are SuperSmart(tm) */ gtk_widget_set_double_buffered (GTK_WIDGET (view), FALSE); } /* Frees the dirty region uta and removes the idle handler */ static void remove_dirty_region (ImageView *view) { ImageViewPrivate *priv; priv = view->priv; if (priv->uta) { g_assert (priv->idle_id != 0); art_uta_free (priv->uta); priv->uta = NULL; g_source_remove (priv->idle_id); priv->idle_id = 0; } else g_assert (priv->idle_id == 0); } /* Destroy handler for the image view */ static void image_view_dispose (GObject *object) { ImageView *view; ImageViewPrivate *priv; view = IMAGE_VIEW (object); priv = view->priv; g_signal_handlers_disconnect_matched ( priv->hadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); g_signal_handlers_disconnect_matched ( priv->vadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); /* Clean up */ if (view->priv->pixbuf) g_object_unref (G_OBJECT (view->priv->pixbuf)); view->priv->pixbuf = NULL; remove_dirty_region (view); GNOME_CALL_PARENT (G_OBJECT_CLASS, dispose, (object)); } /* Finalize handler for the image view */ static void image_view_finalize (GObject *object) { ImageView *view; ImageViewPrivate *priv; view = IMAGE_VIEW (object); priv = view->priv; g_object_unref (priv->hadj); g_object_unref (priv->vadj); g_free (priv); GNOME_CALL_PARENT (G_OBJECT_CLASS, finalize, (object)); } /* Drawing core */ /* Computes the size in pixels of the scaled image */ static void compute_scaled_size (ImageView *view, double zoomx, double zoomy, int *width, int *height) { ImageViewPrivate *priv; priv = view->priv; if (priv->pixbuf) { *width = floor (gdk_pixbuf_get_width (priv->pixbuf) * zoomx + 0.5); *height = floor (gdk_pixbuf_get_height (priv->pixbuf) * zoomy + 0.5); } else *width = *height = 0; } /* 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 (ArtUta *uta, ArtIRect *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 (ImageView *view, ArtIRect *r, ArtIRect *rect) { ArtIRect d; art_irect_intersect (&d, r, rect); if (!art_irect_empty (&d)) gdk_draw_rectangle (GTK_WIDGET (view)->window, GTK_WIDGET (view)->style->bg_gc[GTK_STATE_NORMAL], TRUE, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0); } #if 0 #define PACK_RGBA #endif #ifdef PACK_RGBA /* Packs an RGBA pixbuf into RGB scanlines. The rowstride is preserved. NOTE: * This will produce a pixbuf that is NOT usable with any other normal function! * This is just a hack to accommodate the lack of a * gdk_draw_rgb_image_32_dithalign(); the provided * gdk_draw_rgb_image_dithalign() does not take in 32-bit pixels. */ static void pack_pixbuf (GdkPixbuf *pixbuf) { int x, y; int width, height, rowstride; guchar *pixels, *p, *q; g_assert (gdk_pixbuf_get_n_channels (pixbuf) == 4); width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); pixels = gdk_pixbuf_get_pixels (pixbuf); for (y = 0; y < height; y++) { p = pixels; q = pixels; for (x = 0; x < width; x++) { *p++ = *q++; *p++ = *q++; *p++ = *q++; q++; } pixels += rowstride; } } #endif #define DOUBLE_EQUAL(a,b) (fabs (a - b) < 1e-6) static gboolean unity_zoom (ImageViewPrivate *priv) { return (DOUBLE_EQUAL (priv->zoomx, 1.0) && DOUBLE_EQUAL (priv->zoomy, 1.0)); } #ifdef LIBEOG_ETTORE_CHANGES /* Code cut & pasted from GThumb, by Paolo Bacchilega . */ static unsigned char apply_brightness_and_contrast (unsigned char u_value, float brightness, float contrast) { float nvalue; double power; float value; value = (float) u_value / 255.0; /* apply brightness */ if (brightness < 0.0) value = value * (1.0 + brightness); else value = value + ((1.0 - value) * brightness); /* apply contrast */ if (contrast < 0.0) { if (value > 0.5) nvalue = 1.0 - value; else nvalue = value; if (nvalue < 0.0) nvalue = 0.0; nvalue = 0.5 * pow (nvalue * 2.0 , (double) (1.0 + contrast)); if (value > 0.5) value = 1.0 - nvalue; else value = nvalue; } else { if (value > 0.5) nvalue = 1.0 - value; else nvalue = value; if (nvalue < 0.0) nvalue = 0.0; power = (contrast == 1.0) ? 127 : 1.0 / (1.0 - contrast); nvalue = 0.5 * pow (2.0 * nvalue, power); if (value > 0.5) value = 1.0 - nvalue; else value = nvalue; } return (guchar) (value * 255); } static void apply_brightness_and_contrast_to_pixbuf (ImageView *view, GdkPixbuf *pixbuf, int width, int height) { char *p; float display_brightness; float display_contrast; int line; int rowstride; int bytes_per_pixel; display_brightness = view->priv->display_brightness; display_contrast = view->priv->display_contrast; if (DOUBLE_EQUAL (display_brightness, 0.0) && DOUBLE_EQUAL (display_contrast, 0.0)) return; p = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); bytes_per_pixel = gdk_pixbuf_get_has_alpha (pixbuf) ? 4 : 3; for (line = 0; line < height; line ++) { char *q = p; int i; for (i = 0; i < width; i ++) { q[0] = apply_brightness_and_contrast (q[0], display_brightness, display_contrast); q[1] = apply_brightness_and_contrast (q[1], display_brightness, display_contrast); q[2] = apply_brightness_and_contrast (q[2], display_brightness, display_contrast); q += bytes_per_pixel; } p += rowstride; } } static void apply_transform_to_pixbuf (ImageView *view, GdkPixbuf *pixbuf, int x, int y, int width, int height) { char *p; int line; int rowstride; int bytes_per_pixel; p = gdk_pixbuf_get_pixels (pixbuf); rowstride = gdk_pixbuf_get_rowstride (pixbuf); bytes_per_pixel = gdk_pixbuf_get_has_alpha (pixbuf) ? 4 : 3; for (line = 0; line < height; line ++) { cmsDoTransform (view->priv->transform, p, p, width); p += rowstride; } } #endif /* Paints a rectangle of the dirty region */ #ifdef LIBEOG_ETTORE_CHANGES static void paint_extra (ImageView *view, ArtIRect *rect) { ImageViewClass *class = G_TYPE_INSTANCE_GET_CLASS (view, TYPE_IMAGE_VIEW, ImageViewClass); GdkRectangle area; g_assert (rect->x0 < rect->x1); g_assert (rect->y0 < rect->y1); area.x = rect->x0; area.y = rect->y0; area.width = rect->x1 - rect->x0; area.height = rect->y1 - rect->y0; (* class->paint_extra) (view, &area); } #endif #ifdef LIBEOG_ETTORE_CHANGES static void paint_rectangle (ImageView *view, ArtIRect *rect, GdkInterpType interp_type, gboolean apply_brightness_and_contrast) #else static void paint_rectangle (ImageView *view, ArtIRect *rect, GdkInterpType interp_type) #endif { ImageViewPrivate *priv; int scaled_width, scaled_height; int width, height; int xofs, yofs; ArtIRect r, d; GdkPixbuf *tmp; int check_size; guint32 check_1, check_2; priv = view->priv; compute_scaled_size (view, priv->zoomx, priv->zoomy, &scaled_width, &scaled_height); width = GTK_WIDGET (view)->allocation.width; height = GTK_WIDGET (view)->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; /* Draw background if necessary, in four steps */ /* Top */ if (yofs > 0) { r.x0 = 0; r.y0 = 0; r.x1 = 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 = 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 = width; r.y1 = 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; art_irect_intersect (&d, &r, rect); if (art_irect_empty (&d)) return; /* Short-circuit the fast case to avoid a memcpy() */ if (unity_zoom (priv) && 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 && priv->transform == NULL) { 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 (view)->window, GTK_WIDGET (view)->style->black_gc, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0, priv->dither, 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 (gdk_pixbuf_get_has_alpha (priv->pixbuf)) gdk_pixbuf_fill (tmp, 0x00000000); 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 check parameters */ if (priv->use_check_pattern) { switch (priv->check_type) { case CHECK_TYPE_DARK: check_1 = CHECK_BLACK; check_2 = CHECK_DARK; break; case CHECK_TYPE_MIDTONE: check_1 = CHECK_DARK; check_2 = CHECK_LIGHT; break; case CHECK_TYPE_LIGHT: check_1 = CHECK_LIGHT; check_2 = CHECK_WHITE; break; case CHECK_TYPE_BLACK: check_1 = check_2 = CHECK_BLACK; break; case CHECK_TYPE_GRAY: check_1 = check_2 = CHECK_GRAY; break; case CHECK_TYPE_WHITE: check_1 = check_2 = CHECK_WHITE; break; default: g_assert_not_reached (); return; } } else { check_1 = check_2 = priv->transparency_color; } switch (priv->check_size) { case CHECK_SIZE_SMALL: check_size = CHECK_SMALL; break; case CHECK_SIZE_MEDIUM: check_size = CHECK_MEDIUM; break; case CHECK_SIZE_LARGE: check_size = CHECK_LARGE; break; default: g_assert_not_reached (); return; } /* Draw! */ #if 1 gdk_pixbuf_composite_color (priv->pixbuf, tmp, 0, 0, d.x1 - d.x0, d.y1 - d.y0, -(d.x0 - xofs), -(d.y0 - yofs), priv->zoomx, priv->zoomy, unity_zoom (priv) ? GDK_INTERP_NEAREST : interp_type, 255, d.x0 - xofs, d.y0 - yofs, check_size, check_1, check_2); #else gdk_pixbuf_composite (priv->pixbuf, tmp, 0, 0, d.x1 - d.x0, d.y1 - d.y0, -(d.x0 - xofs), -(d.y0 - yofs), priv->zoomx, priv->zoomy, unity_zoom (priv) ? GDK_INTERP_NEAREST : interp_type, 255); #endif #ifdef LIBEOG_ETTORE_CHANGES if (apply_brightness_and_contrast) apply_brightness_and_contrast_to_pixbuf (view, tmp, d.x1 - d.x0, d.y1 - d.y0); if (priv->transform != NULL) apply_transform_to_pixbuf (view, tmp, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0); #endif #ifdef PACK_RGBA pack_pixbuf (tmp); #endif #if 0 gdk_draw_rgb_image_dithalign (GTK_WIDGET (view)->window, GTK_WIDGET (view)->style->black_gc, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0, priv->dither, gdk_pixbuf_get_pixels (tmp), gdk_pixbuf_get_rowstride (tmp), d.x0 - xofs, d.y0 - yofs); #else /* if (gdk_pixbuf_get_has_alpha (priv->pixbuf)) paint_background (view, &d, rect); gdk_pixbuf_render_to_drawable_alpha (tmp, GTK_WIDGET (view)->window, 0, 0, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0, priv->dither, GDK_PIXBUF_ALPHA_FULL, 0, d.x0 - xofs, d.y0 - yofs); */ gdk_pixbuf_render_to_drawable (tmp, GTK_WIDGET (view)->window, GTK_WIDGET (view)->style->black_gc, 0, 0, d.x0, d.y0, d.x1 - d.x0, d.y1 - d.y0, priv->dither, d.x0 - xofs, d.y0 - yofs); #endif g_object_unref (tmp); #if 0 gdk_draw_line (GTK_WIDGET (view)->window, GTK_WIDGET (view)->style->black_gc, d.x0, d.y0, d.x1 - 1, d.y1 - 1); gdk_draw_line (GTK_WIDGET (view)->window, GTK_WIDGET (view)->style->black_gc, d.x1 - 1, d.y0, d.x0, d.y1 - 1); #endif paint_extra (view, &r); } #include /* 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) { ImageView *view; ImageViewPrivate *priv; ArtIRect rect; view = IMAGE_VIEW (data); priv = view->priv; g_assert (priv->uta != NULL); pull_rectangle (priv->uta, &rect, PAINT_RECT_WIDTH, PAINT_RECT_HEIGHT); if (art_irect_empty (&rect)) { art_uta_free (priv->uta); priv->uta = NULL; } else { #ifdef LIBEOG_ETTORE_CHANGES paint_rectangle (view, &rect, priv->interp_type, TRUE); #else paint_rectangle (view, &rect, priv->interp_type); #endif } 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 (ImageView *view, GdkRectangle *area) { ImageViewPrivate *priv; ArtIRect r; priv = view->priv; if (!GTK_WIDGET_DRAWABLE (view)) return; r.x0 = MAX (0, area->x); r.y0 = MAX (0, area->y); r.x1 = MIN (GTK_WIDGET (view)->allocation.width, area->x + area->width); r.y1 = MIN (GTK_WIDGET (view)->allocation.height, area->y + area->height); if (r.x0 >= r.x1 || r.y0 >= r.y1) return; /* Do nearest neighbor or 1:1 zoom synchronously for speed. */ if (!priv->enable_late_drawing || priv->interp_type == GDK_INTERP_NEAREST || unity_zoom (priv)) { #ifdef LIBEOG_ETTORE_CHANGES paint_rectangle (view, &r, priv->interp_type, TRUE); #else paint_rectangle (view, &r, priv->interp_type); #endif return; } /* 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); } #ifdef LIBEOG_ETTORE_CHANGES paint_rectangle (view, &r, GDK_INTERP_NEAREST, FALSE); #else paint_rectangle (view, &r, GDK_INTERP_NEAREST); #endif priv->uta = uta_add_rect (priv->uta, r.x0, r.y0, r.x1, r.y1); } /* Scrolls the view to the specified offsets. */ static void scroll_to (ImageView *view, int x, int y, gboolean change_adjustments) { ImageViewPrivate *priv; int xofs, yofs; GdkWindow *window; int width, height; int src_x, src_y; int dest_x, dest_y; int twidth, theight; priv = view->priv; /* Check bounds */ x = CLAMP (x, 0, priv->hadj->upper - priv->hadj->page_size); y = CLAMP (y, 0, priv->vadj->upper - priv->vadj->page_size); /* Compute offsets */ xofs = x - priv->xofs; yofs = y - priv->yofs; if (xofs == 0 && yofs == 0) return; priv->xofs = x; priv->yofs = y; if (!GTK_WIDGET_DRAWABLE (view)) goto out; width = GTK_WIDGET (view)->allocation.width; height = GTK_WIDGET (view)->allocation.height; if (abs (xofs) >= width || abs (yofs) >= height) { GdkRectangle area; area.x = 0; area.y = 0; area.width = width; area.height = height; request_paint_area (view, &area); goto out; } window = GTK_WIDGET (view)->window; /* Ensure that the uta has the full size */ twidth = (width + ART_UTILE_SIZE - 1) >> ART_UTILE_SHIFT; theight = (height + ART_UTILE_SIZE - 1) >> ART_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, width - abs (xofs), 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); priv->hadj->value = x; priv->vadj->value = y; g_signal_emit_by_name (priv->hadj, "value_changed"); g_signal_emit_by_name (priv->vadj, "value_changed"); 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 (ImageView *view, int xofs, int yofs) { ImageViewPrivate *priv; priv = view->priv; scroll_to (view, priv->xofs + xofs, priv->yofs + yofs, TRUE); } /* Widget methods */ /* Unmap handler for the image view */ static void image_view_unmap (GtkWidget *widget) { g_return_if_fail (widget != NULL); g_return_if_fail (IS_IMAGE_VIEW (widget)); remove_dirty_region (IMAGE_VIEW (widget)); if (GTK_WIDGET_CLASS (parent_class)->unmap) (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget); } /* Realize handler for the image view */ static void image_view_realize (GtkWidget *widget) { GdkWindowAttr attr; int attr_mask; GdkCursor *cursor; g_return_if_fail (widget != NULL); g_return_if_fail (IS_IMAGE_VIEW (widget)); GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); attr.window_type = GDK_WINDOW_CHILD; attr.x = widget->allocation.x; attr.y = widget->allocation.y; attr.width = widget->allocation.width; attr.height = widget->allocation.height; attr.wclass = GDK_INPUT_OUTPUT; attr.visual = gdk_rgb_get_visual (); attr.colormap = gdk_rgb_get_colormap (); attr.event_mask = (gtk_widget_get_events (widget) | 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); attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP; widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attr, attr_mask); gdk_window_set_user_data (widget->window, widget); cursor = cursor_get (widget, CURSOR_HAND_OPEN); gdk_window_set_cursor (widget->window, cursor); gdk_cursor_unref (cursor); widget->style = gtk_style_attach (widget->style, widget->window); gdk_window_set_back_pixmap (widget->window, NULL, FALSE); } /* Unrealize handler for the image view */ static void image_view_unrealize (GtkWidget *widget) { g_return_if_fail (widget != NULL); g_return_if_fail (IS_IMAGE_VIEW (widget)); remove_dirty_region (IMAGE_VIEW (widget)); if (GTK_WIDGET_CLASS (parent_class)->unrealize) (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); } /* Size_request handler for the image view */ static void image_view_size_request (GtkWidget *widget, GtkRequisition *requisition) { ImageView *view; ImageViewPrivate *priv; g_return_if_fail (widget != NULL); g_return_if_fail (IS_IMAGE_VIEW (widget)); g_return_if_fail (requisition != NULL); view = IMAGE_VIEW (widget); priv = view->priv; requisition->width = requisition->height = 0; } /* Sets the zoom anchor point with respect to the specified window position */ static void set_zoom_anchor (ImageView *view, int x, int y) { ImageViewPrivate *priv; priv = view->priv; priv->zoom_x_anchor = (double) x / GTK_WIDGET (view)->allocation.width; priv->zoom_y_anchor = (double) y / GTK_WIDGET (view)->allocation.height; } /* Sets the zoom anchor point to be the middle of the visible area */ static void set_default_zoom_anchor (ImageView *view) { ImageViewPrivate *priv; priv = view->priv; priv->zoom_x_anchor = priv->zoom_y_anchor = 0.5; } /* Computes the offsets for the new zoom value so that they keep the image * centered on the view. */ static void compute_center_zoom_offsets (ImageView *view, int old_width, int old_height, int new_width, int new_height, int *xofs, int *yofs) { ImageViewPrivate *priv; int old_scaled_width, old_scaled_height; int new_scaled_width, new_scaled_height; double view_cx, view_cy; priv = view->priv; g_assert (priv->need_zoom_change); compute_scaled_size (view, priv->old_zoomx, priv->old_zoomy, &old_scaled_width, &old_scaled_height); if (old_scaled_width < old_width) view_cx = (priv->zoom_x_anchor * old_scaled_width) / priv->old_zoomx; else view_cx = (priv->xofs + priv->zoom_x_anchor * old_width) / priv->old_zoomx; if (old_scaled_height < old_height) view_cy = (priv->zoom_y_anchor * old_scaled_height) / priv->old_zoomy; else view_cy = (priv->yofs + priv->zoom_y_anchor * old_height) / priv->old_zoomy; compute_scaled_size (view, priv->zoomx, priv->zoomy, &new_scaled_width, &new_scaled_height); if (new_scaled_width < new_width) *xofs = 0; else *xofs = floor (view_cx * priv->zoomx - priv->zoom_x_anchor * new_width + 0.5); if (new_scaled_height < new_height) *yofs = 0; else *yofs = floor (view_cy * priv->zoomy - priv->zoom_y_anchor * new_height + 0.5); } /* Size_allocate handler for the image view */ static void image_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { ImageView *view; ImageViewPrivate *priv; int xofs, yofs; int scaled_width, scaled_height; g_return_if_fail (widget != NULL); g_return_if_fail (IS_IMAGE_VIEW (widget)); g_return_if_fail (allocation != NULL); view = IMAGE_VIEW (widget); priv = view->priv; /* Compute new scroll offsets */ if (priv->need_zoom_change) { compute_center_zoom_offsets (view, widget->allocation.width, widget->allocation.height, allocation->width, allocation->height, &xofs, &yofs); set_default_zoom_anchor (view); priv->need_zoom_change = FALSE; } else { xofs = priv->xofs; yofs = priv->yofs; } /* Resize the window */ widget->allocation = *allocation; if (GTK_WIDGET_REALIZED (widget)) gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height); /* Set scroll increments */ compute_scaled_size (view, priv->zoomx, priv->zoomy, &scaled_width, &scaled_height); priv->hadj->page_size = MIN (scaled_width, allocation->width); priv->hadj->page_increment = allocation->width / 2; priv->hadj->step_increment = SCROLL_STEP_SIZE; priv->vadj->page_size = MIN (scaled_height, allocation->height); priv->vadj->page_increment = allocation->height / 2; priv->vadj->step_increment = SCROLL_STEP_SIZE; /* Set scroll bounds and new offsets */ priv->hadj->lower = 0; priv->hadj->upper = scaled_width; xofs = CLAMP (xofs, 0, priv->hadj->upper - priv->hadj->page_size); priv->vadj->lower = 0; priv->vadj->upper = scaled_height; yofs = CLAMP (yofs, 0, priv->vadj->upper - priv->vadj->page_size); g_signal_emit_by_name (priv->hadj, "changed"); g_signal_emit_by_name (priv->vadj, "changed"); if (priv->hadj->value != xofs) { priv->hadj->value = xofs; priv->xofs = xofs; g_signal_handlers_block_matched ( priv->hadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); g_signal_emit_by_name (priv->hadj, "value_changed"); g_signal_handlers_unblock_matched ( priv->hadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); } if (priv->vadj->value != yofs) { priv->vadj->value = yofs; priv->yofs = yofs; g_signal_handlers_block_matched ( priv->vadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); g_signal_emit_by_name (priv->vadj, "value_changed"); g_signal_handlers_unblock_matched ( priv->vadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); } } /* Button press event handler for the image view */ static gboolean image_view_button_press_event (GtkWidget *widget, GdkEventButton *event) { ImageView *view; ImageViewPrivate *priv; GdkCursor *cursor; view = IMAGE_VIEW (widget); priv = view->priv; if (event->type != GDK_BUTTON_PRESS) return FALSE; if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget); if (priv->dragging) return FALSE; switch (event->button) { case 1: cursor = cursor_get (widget, CURSOR_HAND_CLOSED); gdk_window_set_cursor (widget->window, cursor); gdk_cursor_unref (cursor); 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; } /* Drags the image to the specified position */ static void drag_to (ImageView *view, int x, int y) { ImageViewPrivate *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); } /* Button release event handler for the image view */ static gboolean image_view_button_release_event (GtkWidget *widget, GdkEventButton *event) { ImageView *view; ImageViewPrivate *priv; GdkCursor *cursor; view = IMAGE_VIEW (widget); priv = view->priv; if (!priv->dragging || event->button != 1) return FALSE; drag_to (view, event->x, event->y); priv->dragging = FALSE; cursor = cursor_get (widget, CURSOR_HAND_OPEN); gdk_window_set_cursor (widget->window, cursor); gdk_cursor_unref (cursor); 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 EOG you scan scroll by * dragging the image with button 1 anyways. */ static gboolean image_view_scroll_event (GtkWidget *widget, GdkEventScroll *event) { ImageView *view; ImageViewPrivate *priv; view = IMAGE_VIEW (widget); priv = view->priv; /* Compute zoom factor and scrolling offsets; we'll only use either of them */ double zoom_factor; int xofs, yofs; xofs = priv->hadj->page_increment / 2; /* same as in gtkscrolledwindow.c */ yofs = priv->vadj->page_increment / 2; switch (event->direction) { case GDK_SCROLL_UP: zoom_factor = IMAGE_VIEW_ZOOM_MULTIPLIER; xofs = 0; yofs = -yofs; break; case GDK_SCROLL_LEFT: zoom_factor = 1.0 / IMAGE_VIEW_ZOOM_MULTIPLIER; xofs = -xofs; yofs = 0; break; case GDK_SCROLL_DOWN: zoom_factor = 1.0 / IMAGE_VIEW_ZOOM_MULTIPLIER; xofs = 0; yofs = yofs; break; case GDK_SCROLL_RIGHT: zoom_factor = IMAGE_VIEW_ZOOM_MULTIPLIER; xofs = xofs; yofs = 0; break; default: g_assert_not_reached (); return FALSE; } if ((event->state & GDK_SHIFT_MASK) == 0) image_view_set_zoom (view, priv->zoomx * zoom_factor, priv->zoomy * zoom_factor, TRUE, event->x, event->y); else if ((event->state & GDK_CONTROL_MASK) == 0) scroll_by (view, xofs, yofs); else scroll_by (view, yofs, xofs); return TRUE; } /* Motion event handler for the image view */ static gboolean image_view_motion_event (GtkWidget *widget, GdkEventMotion *event) { ImageView *view; ImageViewPrivate *priv; gint x, y; GdkModifierType mods; view = IMAGE_VIEW (widget); priv = view->priv; if (!priv->dragging) return FALSE; if (event->is_hint) gdk_window_get_pointer (widget->window, &x, &y, &mods); else { x = event->x; y = event->y; } drag_to (view, x, y); return TRUE; } /* Expose event handler for the image view. 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 image_view_expose_event (GtkWidget *widget, GdkEventExpose *event) { ImageView *view; GdkRectangle *rects; gint n_rects; int i; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (IS_IMAGE_VIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); view = IMAGE_VIEW (widget); 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; } /* Key press event handler for the image view */ static gboolean image_view_key_press_event (GtkWidget *widget, GdkEventKey *event) { ImageView *view; ImageViewPrivate *priv; gboolean handled; gboolean do_zoom; double zoomx, zoomy; gboolean do_scroll; int xofs, yofs; view = IMAGE_VIEW (widget); priv = view->priv; handled = FALSE; do_zoom = FALSE; do_scroll = FALSE; xofs = yofs = 0; zoomx = zoomy = 1.0; if ((event->state & (GDK_MODIFIER_MASK & ~GDK_LOCK_MASK)) != 0) goto out; switch (event->keyval) { case GDK_Up: do_scroll = TRUE; xofs = 0; yofs = -SCROLL_STEP_SIZE; break; case GDK_Down: do_scroll = TRUE; xofs = 0; yofs = SCROLL_STEP_SIZE; 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_KP_Add: do_zoom = TRUE; zoomx = priv->zoomx * IMAGE_VIEW_ZOOM_MULTIPLIER; zoomy = priv->zoomy * IMAGE_VIEW_ZOOM_MULTIPLIER; break; case GDK_minus: case GDK_KP_Subtract: do_zoom = TRUE; zoomx = priv->zoomx / IMAGE_VIEW_ZOOM_MULTIPLIER; zoomy = priv->zoomy / IMAGE_VIEW_ZOOM_MULTIPLIER; break; case GDK_1: do_zoom = TRUE; zoomx = zoomy = 1.0; break; /* NOTE Larry disabled this binding case GDK_F: case GDK_f: g_signal_emit (view, image_view_signals [ZOOM_FIT], 0); break; */ default: goto out; } if (do_zoom) { gint x, y; gdk_window_get_pointer (widget->window, &x, &y, NULL); image_view_set_zoom (view, zoomx, zoomy, TRUE, x, y); } if (do_scroll) scroll_by (view, xofs, yofs); handled = TRUE; out: if (handled) return TRUE; else return (* GTK_WIDGET_CLASS (parent_class)->key_press_event) (widget, event); } /* Callback used when an adjustment is changed */ static void adjustment_changed_cb (GtkAdjustment *adj, gpointer data) { ImageView *view; ImageViewPrivate *priv; view = IMAGE_VIEW (data); priv = view->priv; scroll_to (view, priv->hadj->value, priv->vadj->value, FALSE); } /* Set_scroll_adjustments handler for the image view */ static void image_view_set_scroll_adjustments (GtkWidget *widget, GtkAdjustment *hadj, GtkAdjustment *vadj) { ImageView *view; ImageViewPrivate *priv; gboolean need_adjust; g_return_if_fail (widget != NULL); g_return_if_fail (IS_IMAGE_VIEW (widget)); view = IMAGE_VIEW (widget); priv = view->priv; if (hadj) g_return_if_fail (GTK_IS_ADJUSTMENT (hadj)); else hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); if (vadj) g_return_if_fail (GTK_IS_ADJUSTMENT (vadj)); else vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); if (priv->hadj && priv->hadj != hadj) { g_signal_handlers_disconnect_matched ( priv->hadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); g_object_unref (priv->hadj); } if (priv->vadj && priv->vadj != vadj) { g_signal_handlers_disconnect_matched ( priv->vadj, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, view); g_object_unref (priv->vadj); } need_adjust = FALSE; if (priv->hadj != hadj) { priv->hadj = hadj; g_object_ref (priv->hadj); gtk_object_sink (GTK_OBJECT (priv->hadj)); g_signal_connect (priv->hadj, "value_changed", G_CALLBACK (adjustment_changed_cb), view); need_adjust = TRUE; } if (priv->vadj != vadj) { priv->vadj = vadj; g_object_ref (priv->vadj); gtk_object_sink (GTK_OBJECT (priv->vadj)); g_signal_connect (priv->vadj, "value_changed", G_CALLBACK (adjustment_changed_cb), view); need_adjust = TRUE; } if (need_adjust) adjustment_changed_cb (NULL, view); } static gboolean image_view_focus_in_event (GtkWidget *widget, GdkEventFocus *event) { return FALSE; } static gboolean image_view_focus_out_event (GtkWidget *widget, GdkEventFocus *event) { return FALSE; } /** * image_view_new: * @void: * * Creates a new empty image view widget. * * Return value: A newly-created image view. **/ GtkWidget * image_view_new (void) { return g_object_new (TYPE_IMAGE_VIEW, NULL); } GdkPixbuf * image_view_get_pixbuf (ImageView *view) { g_return_val_if_fail (IS_IMAGE_VIEW (view), NULL); if (view->priv->pixbuf) g_object_ref (view->priv->pixbuf); return view->priv->pixbuf; } /** * image_view_set_pixbuf: * @view: An image view. * @pixbuf: A pixbuf. * * Sets the pixbuf that an image view will display. **/ void image_view_set_pixbuf (ImageView *view, GdkPixbuf *pixbuf) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; if (pixbuf) { g_object_ref (pixbuf); } if (view->priv->pixbuf) g_object_unref (view->priv->pixbuf); view->priv->pixbuf = pixbuf; remove_dirty_region (view); scroll_to (view, 0, 0, TRUE); gtk_widget_queue_draw (GTK_WIDGET (view)); } /** * image_view_set_zoom: * @view: An image view. * @zoomx: Horizontal zoom factor. * @zoomy: Vertical 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. **/ void image_view_set_zoom (ImageView *view, double zoomx, double zoomy, gboolean have_anchor, int anchorx, int anchory) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); g_return_if_fail (zoomx > 0.0); g_return_if_fail (zoomy > 0.0); priv = view->priv; image_view_update_min_zoom (view); if (zoomx > MAX_ZOOM_FACTOR) zoomx = MAX_ZOOM_FACTOR; else if (zoomx < priv->MIN_ZOOM) zoomx = priv->MIN_ZOOM; if (zoomy > MAX_ZOOM_FACTOR) zoomy = MAX_ZOOM_FACTOR; else if (zoomy < priv->MIN_ZOOM) zoomy = priv->MIN_ZOOM; if (DOUBLE_EQUAL (priv->zoomx, zoomx) && DOUBLE_EQUAL (priv->zoomy, zoomy)) return; if (!priv->need_zoom_change) { priv->old_zoomx = priv->zoomx; priv->old_zoomy = priv->zoomy; priv->need_zoom_change = TRUE; } priv->zoomx = zoomx; priv->zoomy = zoomy; g_signal_emit (view, image_view_signals [ZOOM_CHANGED], 0); if (have_anchor) { anchorx = CLAMP (anchorx, 0, GTK_WIDGET (view)->allocation.width); anchory = CLAMP (anchory, 0, GTK_WIDGET (view)->allocation.height); set_zoom_anchor (view, anchorx, anchory); } else set_default_zoom_anchor (view); gtk_widget_queue_resize (GTK_WIDGET (view)); } /** * image_view_get_zoom: * @view: An image view. * @zoomx: If non-NULL, the horizontal zoom factor is returned here. * @zoomy: If non-NULL, the vertical zoom factor is returned here. * * Queries the zoom factor of an image view. **/ void image_view_get_zoom (ImageView *view, double *zoomx, double *zoomy) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; if (zoomx) *zoomx = priv->zoomx; if (zoomy) *zoomy = priv->zoomy; } /** * image_view_set_interp_type: * @view: An image view. * @interp_type: Interpolation type. * * Sets the interpolation type on an image view. **/ void image_view_set_interp_type (ImageView *view, GdkInterpType interp_type) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; if (priv->interp_type == interp_type) return; priv->interp_type = interp_type; gtk_widget_queue_draw (GTK_WIDGET (view)); } /** * image_view_get_interp_type: * @view: An image view. * * Queries the interpolation type of an image view. * * Return value: Interpolation type. **/ GdkInterpType image_view_get_interp_type (ImageView *view) { ImageViewPrivate *priv; g_return_val_if_fail (view != NULL, GDK_INTERP_NEAREST); g_return_val_if_fail (IS_IMAGE_VIEW (view), GDK_INTERP_NEAREST); priv = view->priv; return priv->interp_type; } /** * image_view_set_check_type: * @view: An image view. * @check_type: Check type. * * Sets the check type on an image view. **/ void image_view_set_check_type (ImageView *view, CheckType check_type) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; if (priv->check_type == check_type && priv->use_check_pattern) return; priv->check_type = check_type; priv->use_check_pattern = TRUE; gtk_widget_queue_draw (GTK_WIDGET (view)); } /** * image_view_get_check_type: * @view: An image view. * * Queries the check type of an image view. * * Return value: Check type. **/ CheckType image_view_get_check_type (ImageView *view) { ImageViewPrivate *priv; g_return_val_if_fail (view != NULL, CHECK_TYPE_BLACK); g_return_val_if_fail (IS_IMAGE_VIEW (view), CHECK_TYPE_BLACK); priv = view->priv; return priv->check_type; } /** * image_view_set_check_size: * @view: An image view. * @check_size: Check size. * * Sets the check size on an image view. **/ void image_view_set_check_size (ImageView *view, CheckSize check_size) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; if (priv->check_size == check_size && priv->use_check_pattern) return; priv->check_size = check_size; priv->use_check_pattern = TRUE; gtk_widget_queue_draw (GTK_WIDGET (view)); } /** * image_view_get_check_size: * @view: An image view. * * Queries the check size on an image view. * * Return value: Check size. **/ CheckSize image_view_get_check_size (ImageView *view) { ImageViewPrivate *priv; g_return_val_if_fail (view != NULL, CHECK_SIZE_SMALL); g_return_val_if_fail (IS_IMAGE_VIEW (view), CHECK_SIZE_SMALL); priv = view->priv; return priv->check_size; } /** * image_view_set_dither: * @view: An image view. * @dither: Dither type. * * Sets the dither type on an image view. **/ void image_view_set_dither (ImageView *view, GdkRgbDither dither) { ImageViewPrivate *priv; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; if (priv->dither == dither) return; priv->dither = dither; gtk_widget_queue_draw (GTK_WIDGET (view)); } void image_view_set_transparent_color (ImageView *view, const GdkColor *color) { ImageViewPrivate *priv; guint32 col = 0; guint32 red_part; guint32 green_part; guint32 blue_part; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; red_part = (color->red / 256) << 16; green_part = (color->green / 256) << 8; blue_part = (color->blue / 256); col = red_part + green_part + blue_part; priv->use_check_pattern = FALSE; priv->transparency_color = col; gtk_widget_queue_draw (GTK_WIDGET (view)); } /** * image_view_get_dither: * @view: An image view. * * Queries the dither type of an image view. * * Return value: Dither type. **/ GdkRgbDither image_view_get_dither (ImageView *view) { ImageViewPrivate *priv; g_return_val_if_fail (view != NULL, GDK_RGB_DITHER_NONE); g_return_val_if_fail (IS_IMAGE_VIEW (view), GDK_RGB_DITHER_NONE); priv = view->priv; return priv->dither; } /** * image_view_get_scaled_size * @view: An image view. * @width: Image width result. * @height: Image height result. * * Returns the size of the image after applying the zoom factor. * * Return value: Image size according to zoom factor. **/ void image_view_get_scaled_size (ImageView *view, gint *width, gint *height) { ImageViewPrivate *priv; *width = *height = 0; g_return_if_fail (view != NULL); g_return_if_fail (IS_IMAGE_VIEW (view)); priv = view->priv; compute_scaled_size (view, priv->zoomx, priv->zoomy, width, height); } /* Class initialization function for the image view */ static void image_view_class_init (ImageViewClass *class) { GObjectClass *gobject_class; GtkObjectClass *object_class; GtkWidgetClass *widget_class; gobject_class = (GObjectClass *) class; object_class = (GtkObjectClass *) class; widget_class = (GtkWidgetClass *) class; gobject_class->set_property = image_view_set_property; gobject_class->get_property = image_view_get_property; /* FIXME: we should use the enum types but that's a pain */ g_object_class_install_property ( gobject_class, PROP_INTERP_TYPE, g_param_spec_int ("interp_type", _("interpolation type"), _("the type of interpolation to use"), 0, G_MAXINT, 0, 0)); g_object_class_install_property ( gobject_class, PROP_CHECK_TYPE, g_param_spec_int ("check_type", _("check type"), _("the type of chequering to use"), 0, G_MAXINT, 0, 0)); g_object_class_install_property ( gobject_class, PROP_CHECK_SIZE, g_param_spec_int ("check_size", _("check type"), _("the size of chequers to use"), 0, G_MAXINT, 0, 0)); g_object_class_install_property ( gobject_class, PROP_DITHER, g_param_spec_int ("dither", _("dither"), _("dither type"), 0, G_MAXINT, 0, 0)); image_view_signals[ZOOM_FIT] = g_signal_new ("zoom_fit", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ImageViewClass, zoom_fit), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); image_view_signals[ZOOM_CHANGED] = g_signal_new ("zoom_changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (ImageViewClass, zoom_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); gobject_class->dispose = image_view_dispose; gobject_class->finalize = image_view_finalize; class->set_scroll_adjustments = image_view_set_scroll_adjustments; widget_class->set_scroll_adjustments_signal = g_signal_new ("set_scroll_adjustments", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ImageViewClass, set_scroll_adjustments), NULL, NULL, marshal_VOID__OBJECT_OBJECT, G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); widget_class->unmap = image_view_unmap; widget_class->realize = image_view_realize; widget_class->unrealize = image_view_unrealize; widget_class->size_request = image_view_size_request; widget_class->size_allocate = image_view_size_allocate; widget_class->button_press_event = image_view_button_press_event; widget_class->button_release_event = image_view_button_release_event; widget_class->scroll_event = image_view_scroll_event; widget_class->motion_notify_event = image_view_motion_event; widget_class->expose_event = image_view_expose_event; widget_class->key_press_event = image_view_key_press_event; widget_class->focus_in_event = image_view_focus_in_event; widget_class->focus_out_event = image_view_focus_out_event; } void image_view_update_min_zoom (ImageView *view) { ImageViewPrivate *priv = view->priv; priv->MIN_ZOOM = MIN_ZOOM_FACTOR; if (priv->pixbuf) { double width = (double) gdk_pixbuf_get_width (priv->pixbuf); double height = (double) gdk_pixbuf_get_height (priv->pixbuf); priv->MIN_ZOOM = ((double) GTK_WIDGET (view)->allocation.width)/ width; if (((double) GTK_WIDGET (view)->allocation.height) / height < priv->MIN_ZOOM) priv->MIN_ZOOM = ((double) GTK_WIDGET (view)->allocation.height)/ height; if (priv->MIN_ZOOM > 1.0) priv->MIN_ZOOM = 1.0; } } #ifdef LIBEOG_ETTORE_CHANGES void image_view_get_offsets_and_size (ImageView *view, int *xofs_return, int *yofs_return, int *scaled_width_return, int *scaled_height_return) { ImageViewPrivate *priv = view->priv; int width, height; int scaled_width, scaled_height; int xofs, yofs; /* FIXME duplicate code from paint_rectangle() */ compute_scaled_size (view, priv->zoomx, priv->zoomy, &scaled_width, &scaled_height); width = GTK_WIDGET (view)->allocation.width; height = GTK_WIDGET (view)->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; if (xofs_return != NULL) *xofs_return = xofs; if (yofs_return != NULL) *yofs_return = yofs; if (scaled_width_return != NULL) *scaled_width_return = scaled_width; if (scaled_height_return != NULL) *scaled_height_return = scaled_height; } void image_view_set_display_brightness (ImageView *view, float display_brightness) { view->priv->display_brightness = display_brightness; gtk_widget_queue_draw (GTK_WIDGET (view)); } void image_view_set_display_contrast (ImageView *view, float display_contrast) { view->priv->display_contrast = display_contrast; gtk_widget_queue_draw (GTK_WIDGET (view)); } void image_view_set_display_transform (ImageView *view, cmsHTRANSFORM transform) { view->priv->transform = transform; } void image_view_set_delay_scaling (ImageView *view, gboolean enable) { view->priv->enable_late_drawing = enable; } #endif