/* LIBGIMP - The GIMP Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * gimpzoompreview.c * Copyright (C) 2005 David Odin * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include "config.h" #include #include #include "libgimpwidgets/gimpwidgets.h" #include "gimpuitypes.h" #include "gimp.h" #include "gimpdrawablepreview.h" #include "gimpzoompreview.h" /** * SECTION: gimpzoompreview * @title: GimpZoomPreview * @short_description: A drawable preview with zooming capabilities. * * A drawable preview with zooming capabilities. **/ enum { PROP_0, PROP_DRAWABLE, PROP_MODEL }; typedef struct GimpZoomPreviewPrivate { GimpDrawable *drawable; GimpZoomModel *model; GdkRectangle extents; } GimpZoomPreviewPrivate; typedef struct { gboolean update; } PreviewSettings; #define GIMP_ZOOM_PREVIEW_GET_PRIVATE(obj) \ ((GimpZoomPreviewPrivate *) ((GimpZoomPreview *) (obj))->priv) static void gimp_zoom_preview_constructed (GObject *object); static void gimp_zoom_preview_finalize (GObject *object); static void gimp_zoom_preview_dispose (GObject *object); static void gimp_zoom_preview_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gimp_zoom_preview_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gimp_zoom_preview_set_adjustments (GimpZoomPreview *preview, gdouble old_factor, gdouble new_factor); static void gimp_zoom_preview_size_allocate (GtkWidget *widget, GtkAllocation *allocation, GimpZoomPreview *preview); static void gimp_zoom_preview_style_set (GtkWidget *widget, GtkStyle *prev_style); static gboolean gimp_zoom_preview_scroll_event (GtkWidget *widget, GdkEventScroll *event, GimpZoomPreview *preview); static void gimp_zoom_preview_draw (GimpPreview *preview); static void gimp_zoom_preview_draw_buffer (GimpPreview *preview, const guchar *buffer, gint rowstride); static void gimp_zoom_preview_draw_thumb (GimpPreview *preview, GimpPreviewArea *area, gint width, gint height); static void gimp_zoom_preview_set_cursor (GimpPreview *preview); static void gimp_zoom_preview_transform (GimpPreview *preview, gint src_x, gint src_y, gint *dest_x, gint *dest_y); static void gimp_zoom_preview_untransform (GimpPreview *preview, gint src_x, gint src_y, gint *dest_x, gint *dest_y); static void gimp_zoom_preview_set_drawable (GimpZoomPreview *preview, GimpDrawable *drawable); static void gimp_zoom_preview_set_model (GimpZoomPreview *preview, GimpZoomModel *model); static void gimp_zoom_preview_get_source_area (GimpPreview *preview, gint *x, gint *y, gint *w, gint *h); G_DEFINE_TYPE (GimpZoomPreview, gimp_zoom_preview, GIMP_TYPE_SCROLLED_PREVIEW) #define parent_class gimp_zoom_preview_parent_class static gint gimp_zoom_preview_counter = 0; static void gimp_zoom_preview_class_init (GimpZoomPreviewClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GimpPreviewClass *preview_class = GIMP_PREVIEW_CLASS (klass); object_class->constructed = gimp_zoom_preview_constructed; object_class->finalize = gimp_zoom_preview_finalize; object_class->dispose = gimp_zoom_preview_dispose; object_class->get_property = gimp_zoom_preview_get_property; object_class->set_property = gimp_zoom_preview_set_property; widget_class->style_set = gimp_zoom_preview_style_set; preview_class->draw = gimp_zoom_preview_draw; preview_class->draw_buffer = gimp_zoom_preview_draw_buffer; preview_class->draw_thumb = gimp_zoom_preview_draw_thumb; preview_class->set_cursor = gimp_zoom_preview_set_cursor; preview_class->transform = gimp_zoom_preview_transform; preview_class->untransform = gimp_zoom_preview_untransform; g_type_class_add_private (object_class, sizeof (GimpZoomPreviewPrivate)); /** * GimpZoomPreview:drawable: * * The drawable the #GimpZoomPreview is attached to. * * Since: 2.4 */ g_object_class_install_property (object_class, PROP_DRAWABLE, g_param_spec_pointer ("drawable", NULL, NULL, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); /** * GimpZoomPreview:model: * * The #GimpZoomModel used by this #GimpZoomPreview. * * Since: 2.4 */ g_object_class_install_property (object_class, PROP_MODEL, g_param_spec_object ("model", NULL, NULL, GIMP_TYPE_ZOOM_MODEL, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void gimp_zoom_preview_init (GimpZoomPreview *preview) { preview->priv = G_TYPE_INSTANCE_GET_PRIVATE (preview, GIMP_TYPE_ZOOM_PREVIEW, GimpZoomPreviewPrivate); g_signal_connect (GIMP_PREVIEW (preview)->area, "size-allocate", G_CALLBACK (gimp_zoom_preview_size_allocate), preview); g_signal_connect (GIMP_PREVIEW (preview)->area, "scroll-event", G_CALLBACK (gimp_zoom_preview_scroll_event), preview); g_object_set (GIMP_PREVIEW (preview)->area, "check-size", gimp_check_size (), "check-type", gimp_check_type (), NULL); gimp_scrolled_preview_set_policy (GIMP_SCROLLED_PREVIEW (preview), GTK_POLICY_ALWAYS, GTK_POLICY_ALWAYS); } static void gimp_zoom_preview_constructed (GObject *object) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (object); gchar *data_name; PreviewSettings settings; G_OBJECT_CLASS (parent_class)->constructed (object); data_name = g_strdup_printf ("%s-zoom-preview-%d", g_get_prgname (), gimp_zoom_preview_counter++); if (gimp_get_data (data_name, &settings)) { gimp_preview_set_update (GIMP_PREVIEW (object), settings.update); } g_object_set_data_full (object, "gimp-zoom-preview-data-name", data_name, (GDestroyNotify) g_free); if (! priv->model) { GimpZoomModel *model = gimp_zoom_model_new (); gimp_zoom_model_set_range (model, 1.0, 256.0); gimp_zoom_preview_set_model (GIMP_ZOOM_PREVIEW (object), model); g_object_unref (model); } gimp_zoom_preview_set_adjustments (GIMP_ZOOM_PREVIEW (object), 1.0, 1.0); } static void gimp_zoom_preview_finalize (GObject *object) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (object); if (priv->model) { g_object_unref (priv->model); priv->model = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void gimp_zoom_preview_dispose (GObject *object) { const gchar *data_name = g_object_get_data (G_OBJECT (object), "gimp-zoom-preview-data-name"); if (data_name) { GimpPreview *preview = GIMP_PREVIEW (object); PreviewSettings settings; settings.update = gimp_preview_get_update (preview); gimp_set_data (data_name, &settings, sizeof (PreviewSettings)); } G_OBJECT_CLASS (parent_class)->dispose (object); } static void gimp_zoom_preview_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GimpZoomPreview *preview = GIMP_ZOOM_PREVIEW (object); switch (property_id) { case PROP_DRAWABLE: g_value_set_pointer (value, gimp_zoom_preview_get_drawable (preview)); break; case PROP_MODEL: g_value_set_object (value, gimp_zoom_preview_get_model (preview)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_zoom_preview_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { GimpZoomPreview *preview = GIMP_ZOOM_PREVIEW (object); switch (property_id) { case PROP_DRAWABLE: gimp_zoom_preview_set_drawable (preview, g_value_get_pointer (value)); break; case PROP_MODEL: gimp_zoom_preview_set_model (preview, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_zoom_preview_set_adjustments (GimpZoomPreview *preview, gdouble old_factor, gdouble new_factor) { GimpScrolledPreview *scrolled_preview = GIMP_SCROLLED_PREVIEW (preview); GtkAdjustment *adj; gdouble width; gdouble height; gdouble ratio; gimp_scrolled_preview_freeze (scrolled_preview); width = GIMP_PREVIEW (preview)->width; height = GIMP_PREVIEW (preview)->height; ratio = new_factor / old_factor; adj = gtk_range_get_adjustment (GTK_RANGE (scrolled_preview->hscr)); gtk_adjustment_configure (adj, (gtk_adjustment_get_value (adj) + width / 2.0) * ratio - width / 2.0, 0, width * new_factor, new_factor, MAX (width / 2.0, new_factor), width); adj = gtk_range_get_adjustment (GTK_RANGE (scrolled_preview->vscr)); gtk_adjustment_configure (adj, (gtk_adjustment_get_value (adj) + height / 2.0) * ratio - height / 2.0, 0, height * new_factor, new_factor, MAX (height / 2.0, new_factor), height); gimp_scrolled_preview_thaw (scrolled_preview); } static void gimp_zoom_preview_size_allocate (GtkWidget *widget, GtkAllocation *allocation, GimpZoomPreview *preview) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); gdouble zoom; gint width = GIMP_PREVIEW (preview)->xmax - GIMP_PREVIEW (preview)->xmin; gint height = GIMP_PREVIEW (preview)->ymax - GIMP_PREVIEW (preview)->ymin; GIMP_PREVIEW (preview)->width = MIN (width, allocation->width); GIMP_PREVIEW (preview)->height = MIN (height, allocation->height); zoom = gimp_zoom_model_get_factor (priv->model); gimp_zoom_preview_set_adjustments (preview, zoom, zoom); } static void gimp_zoom_preview_style_set (GtkWidget *widget, GtkStyle *prev_style) { GimpPreview *preview = GIMP_PREVIEW (widget); GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); GimpDrawable *drawable = priv->drawable; gint size; gint width, height; gint x1, y1; gint x2, y2; if (GTK_WIDGET_CLASS (parent_class)->style_set) GTK_WIDGET_CLASS (parent_class)->style_set (widget, prev_style); gtk_widget_style_get (widget, "size", &size, NULL); if (_gimp_drawable_preview_get_bounds (drawable, &x1, &y1, &x2, &y2)) { width = x2 - x1; height = y2 - y1; } else { width = gimp_drawable_width (drawable->drawable_id); height = gimp_drawable_height (drawable->drawable_id); } if (width > height) { preview->width = MIN (width, size); preview->height = (height * preview->width) / width; } else { preview->height = MIN (height, size); preview->width = (width * preview->height) / height; } gtk_widget_set_size_request (preview->area, preview->width, preview->height); } static gboolean gimp_zoom_preview_scroll_event (GtkWidget *widget, GdkEventScroll *event, GimpZoomPreview *preview) { if (event->state & GDK_CONTROL_MASK) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); gimp_scrolled_preview_freeze (GIMP_SCROLLED_PREVIEW (preview)); switch (event->direction) { case GDK_SCROLL_UP: gimp_zoom_model_zoom (priv->model, GIMP_ZOOM_IN, 0.0); break; case GDK_SCROLL_DOWN: gimp_zoom_model_zoom (priv->model, GIMP_ZOOM_OUT, 0.0); break; default: break; } gimp_scrolled_preview_thaw (GIMP_SCROLLED_PREVIEW (preview)); } return FALSE; } static void gimp_zoom_preview_draw (GimpPreview *preview) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); GimpDrawable *drawable; guchar *data; gint width; gint height; gint bpp; if (! priv->model) return; drawable = priv->drawable; if (! drawable) return; data = gimp_zoom_preview_get_source (GIMP_ZOOM_PREVIEW (preview), &width, &height, &bpp); if (data) { gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->area), 0, 0, width, height, gimp_drawable_type (drawable->drawable_id), data, width * bpp); g_free (data); } } static void gimp_zoom_preview_draw_buffer (GimpPreview *preview, const guchar *buffer, gint rowstride) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); GimpDrawable *drawable = priv->drawable; gint32 image_id; image_id = gimp_item_get_image (drawable->drawable_id); if (gimp_selection_is_empty (image_id)) { gimp_preview_area_draw (GIMP_PREVIEW_AREA (preview->area), 0, 0, preview->width, preview->height, gimp_drawable_type (drawable->drawable_id), buffer, rowstride); } else { guchar *sel; guchar *src; gint selection_id; gint width, height; gint bpp; gint src_x; gint src_y; gint src_width; gint src_height; gint offsx = 0; gint offsy = 0; selection_id = gimp_image_get_selection (image_id); width = preview->width; height = preview->height; gimp_zoom_preview_get_source_area (preview, &src_x, &src_y, &src_width, &src_height); src = gimp_drawable_get_sub_thumbnail_data (drawable->drawable_id, src_x, src_y, src_width, src_height, &width, &height, &bpp); gimp_drawable_offsets (drawable->drawable_id, &offsx, &offsy); sel = gimp_drawable_get_sub_thumbnail_data (selection_id, src_x + offsx, src_y + offsy, src_width, src_height, &width, &height, &bpp); gimp_preview_area_mask (GIMP_PREVIEW_AREA (preview->area), 0, 0, preview->width, preview->height, gimp_drawable_type (drawable->drawable_id), src, width * drawable->bpp, buffer, rowstride, sel, width); g_free (sel); g_free (src); } } static void gimp_zoom_preview_draw_thumb (GimpPreview *preview, GimpPreviewArea *area, gint width, gint height) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); GimpDrawable *drawable = priv->drawable; if (drawable) _gimp_drawable_preview_area_draw_thumb (area, drawable, width, height); } static void gimp_zoom_preview_set_cursor (GimpPreview *preview) { if (! gtk_widget_get_realized (preview->area)) return; if (gimp_zoom_preview_get_factor (GIMP_ZOOM_PREVIEW (preview)) > 1.0) { gdk_window_set_cursor (gtk_widget_get_window (preview->area), GIMP_SCROLLED_PREVIEW (preview)->cursor_move); } else { gdk_window_set_cursor (gtk_widget_get_window (preview->area), preview->default_cursor); } } static void gimp_zoom_preview_transform (GimpPreview *preview, gint src_x, gint src_y, gint *dest_x, gint *dest_y) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); gdouble zoom = gimp_zoom_preview_get_factor (GIMP_ZOOM_PREVIEW (preview)); *dest_x = ((gdouble) (src_x - priv->extents.x) * preview->width / priv->extents.width * zoom) - preview->xoff; *dest_y = ((gdouble) (src_y - priv->extents.y) * preview->height / priv->extents.height * zoom) - preview->yoff; } static void gimp_zoom_preview_untransform (GimpPreview *preview, gint src_x, gint src_y, gint *dest_x, gint *dest_y) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); gdouble zoom = gimp_zoom_preview_get_factor (GIMP_ZOOM_PREVIEW (preview)); *dest_x = (priv->extents.x + ((gdouble) (src_x + preview->xoff) * priv->extents.width / preview->width / zoom)); *dest_y = (priv->extents.y + ((gdouble) (src_y + preview->yoff) * priv->extents.height / preview->height / zoom)); } static void gimp_zoom_preview_set_drawable (GimpZoomPreview *preview, GimpDrawable *drawable) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); gint x, y; gint width, height; gint max_width, max_height; g_return_if_fail (priv->drawable == NULL); priv->drawable = drawable; if (gimp_drawable_mask_intersect (drawable->drawable_id, &x, &y, &width, &height)) { priv->extents.x = x; priv->extents.y = y; } else { width = gimp_drawable_width (drawable->drawable_id); height = gimp_drawable_height (drawable->drawable_id); priv->extents.x = 0; priv->extents.y = 0; } priv->extents.width = width; priv->extents.height = height; if (width > height) { max_width = MIN (width, 512); max_height = (height * max_width) / width; } else { max_height = MIN (height, 512); max_width = (width * max_height) / height; } gimp_preview_set_bounds (GIMP_PREVIEW (preview), 0, 0, max_width, max_height); g_object_set (GIMP_PREVIEW (preview)->frame, "ratio", (gdouble) width / (gdouble) height, NULL); } static void gimp_zoom_preview_set_model (GimpZoomPreview *preview, GimpZoomModel *model) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); GtkWidget *button_bar; GtkWidget *button; GtkWidget *box; g_return_if_fail (priv->model == NULL); if (! model) return; priv->model = g_object_ref (model); g_signal_connect_swapped (priv->model, "zoomed", G_CALLBACK (gimp_zoom_preview_set_adjustments), preview); box = gimp_preview_get_controls (GIMP_PREVIEW (preview)); g_return_if_fail (GTK_IS_BOX (box)); button_bar = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); gtk_box_pack_end (GTK_BOX (box), button_bar, FALSE, FALSE, 0); gtk_widget_show (button_bar); /* zoom out */ button = gimp_zoom_button_new (priv->model, GIMP_ZOOM_OUT, GTK_ICON_SIZE_SMALL_TOOLBAR); gtk_box_pack_start (GTK_BOX (button_bar), button, FALSE, FALSE, 0); gtk_widget_show (button); /* zoom in */ button = gimp_zoom_button_new (priv->model, GIMP_ZOOM_IN, GTK_ICON_SIZE_SMALL_TOOLBAR); gtk_box_pack_start (GTK_BOX (button_bar), button, FALSE, FALSE, 0); gtk_widget_show (button); } static void gimp_zoom_preview_get_source_area (GimpPreview *preview, gint *x, gint *y, gint *w, gint *h) { GimpZoomPreviewPrivate *priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); gdouble zoom = gimp_zoom_model_get_factor (priv->model); gimp_zoom_preview_untransform (preview, 0, 0, x, y); *w = priv->extents.width / zoom; *h = priv->extents.height / zoom; } /** * gimp_zoom_preview_new: * @drawable: a #GimpDrawable * * Creates a new #GimpZoomPreview widget for @drawable. * * Since: 2.4 * * Returns: a new #GimpZoomPreview. **/ GtkWidget * gimp_zoom_preview_new (GimpDrawable *drawable) { g_return_val_if_fail (drawable != NULL, NULL); return g_object_new (GIMP_TYPE_ZOOM_PREVIEW, "drawable", drawable, NULL); } /** * gimp_zoom_preview_new_with_model: * @drawable: a #GimpDrawable * @model: a #GimpZoomModel * * Creates a new #GimpZoomPreview widget for @drawable using the * given @model. * * This variant of gimp_zoom_preview_new() allows you to create a * preview using an existing zoom model. This may be useful if for * example you want to have two zoom previews that keep their zoom * factor in sync. * * Since: 2.4 * * Returns: a new #GimpZoomPreview. **/ GtkWidget * gimp_zoom_preview_new_with_model (GimpDrawable *drawable, GimpZoomModel *model) { g_return_val_if_fail (drawable != NULL, NULL); g_return_val_if_fail (GIMP_IS_ZOOM_MODEL (model), NULL); return g_object_new (GIMP_TYPE_ZOOM_PREVIEW, "drawable", drawable, "model", model, NULL); } /** * gimp_zoom_preview_get_drawable: * @preview: a #GimpZoomPreview widget * * Returns the #GimpDrawable the #GimpZoomPreview is attached to. * * Return Value: the #GimpDrawable that was passed to gimp_zoom_preview_new(). * * Since: 2.4 **/ GimpDrawable * gimp_zoom_preview_get_drawable (GimpZoomPreview *preview) { g_return_val_if_fail (GIMP_IS_ZOOM_PREVIEW (preview), NULL); return GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview)->drawable; } /** * gimp_zoom_preview_get_model: * @preview: a #GimpZoomPreview widget * * Returns the #GimpZoomModel the preview is using. * * Return Value: a pointer to the #GimpZoomModel owned by the @preview * * Since: 2.4 **/ GimpZoomModel * gimp_zoom_preview_get_model (GimpZoomPreview *preview) { g_return_val_if_fail (GIMP_IS_ZOOM_PREVIEW (preview), NULL); return GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview)->model; } /** * gimp_zoom_preview_get_factor: * @preview: a #GimpZoomPreview widget * * Returns the zoom factor the preview is currently using. * * Return Value: the current zoom factor * * Since: 2.4 **/ gdouble gimp_zoom_preview_get_factor (GimpZoomPreview *preview) { GimpZoomPreviewPrivate *priv; g_return_val_if_fail (GIMP_IS_ZOOM_PREVIEW (preview), 1.0); priv = GIMP_ZOOM_PREVIEW_GET_PRIVATE (preview); return priv->model ? gimp_zoom_model_get_factor (priv->model) : 1.0; } /** * gimp_zoom_preview_get_source: * @preview: a #GimpZoomPreview widget * @width: a pointer to an int where the current width of the zoom widget * will be put. * @height: a pointer to an int where the current width of the zoom widget * will be put. * @bpp: return location for the number of bytes per pixel * * Returns the scaled image data of the part of the drawable the * #GimpZoomPreview is currently showing, as a newly allocated array of guchar. * This function also allow to get the current width, height and bpp of the * #GimpZoomPreview. * * Return Value: newly allocated data that should be released using g_free() * when it is not any longer needed * * Since: 2.4 */ guchar * gimp_zoom_preview_get_source (GimpZoomPreview *preview, gint *width, gint *height, gint *bpp) { GimpDrawable *drawable; g_return_val_if_fail (GIMP_IS_ZOOM_PREVIEW (preview), NULL); g_return_val_if_fail (width != NULL && height != NULL && bpp != NULL, NULL); drawable = gimp_zoom_preview_get_drawable (preview); if (drawable) { GimpPreview *gimp_preview = GIMP_PREVIEW (preview); gint src_x; gint src_y; gint src_width; gint src_height; *width = gimp_preview->width; *height = gimp_preview->height; gimp_zoom_preview_get_source_area (gimp_preview, &src_x, &src_y, &src_width, &src_height); return gimp_drawable_get_sub_thumbnail_data (drawable->drawable_id, src_x, src_y, src_width, src_height, width, height, bpp); } else { *width = 0; *height = 0; *bpp = 0; return NULL; } }