/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; coding: utf-8 -*- * * Copyright © 2007 Björn Lindqvist * * 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 2, 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ /** * SECTION:gtkimagetoolselector * @see_also: #GtkIImageTool, #GtkImageToolDragger, gtk_image_view_set_tool() * @short_description: Image tool for selecting rectangular regions * * * * * * Screenshot showing a GtkImageToolSelector with an * active selection on a GtkImageView * * * * * GtkImageToolSelector is a tool for selecting areas of an image. It * is useful for cropping an image, for example. The tool is an * implementor of the #GtkIImageTool inteface which means that it can * be plugged into a #GtkImageView by using the * gtk_image_view_set_tool() method. * * * GtkImageToolSelector changes the default display of the * GtkImageView. It darkens down the unselected region of the image * which provides a nice effect and makes it clearer what part of the * image that is currently selected. Unfortunately, this effect is * somewhat incompatible with how #GtkImageNav behaves because that * widget will show the image without darkening it. * * * The tool also changes the default behaviour of the mouse. When a * GtkImageToolSelector is set on a GtkImageView, mouse presses do not * "grab" the image and you cannot scroll by dragging. Instead mouse * presses and dragging is used to resize and move the selection * rectangle. When the mouse drags the selection rectangle to the * border of the widget, the view autoscrolls which is a convenient way * for a user to position the selection. * * * Please note that GtkImageToolSelector draws the image in two * layers. One darkened and the selection rectangle in normal * luminosity. Because it uses two draw operations instead one one like * #GtkImageToolDragger does, it is significantly slower than that * tool. Therefore, it makes sense for a user of this library to set * the interpolation to %GDK_INTERP_NEAREST when using this tool to * ensure that performance is acceptable to the users of the program. * * * Zoom bug * * There is a small bug in GtkImageToolSelector that becomes apparent * when the zoom factor is greater than about 30. The edge of the * selection rectangle may in that case intersect a pixel: * * * * * * * The selection rectangle intersects the border pixel * * * * The bug is caused by bug 389832 in * gdk-pixbuf. There * is no way to solve this bug on GtkImageView's level (but if * someone knows how, I'd really like to know). * * **/ #include #include "cursors.h" #include "gtkimagetoolselector.h" #define MIN_AUTOSCROLL 6 /*************************************************************/ /***** Private data ******************************************/ /*************************************************************/ static guint gtk_image_tool_selector_signals[1] = {0}; /*************************************************************/ /***** The hotspot list object *******************************/ /*************************************************************/ static void hotspot_list_init (Hotspot *hotspots) { hotspots[0].cursor = cursor_get (CURSOR_HAND_OPEN); hotspots[1].cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER); hotspots[2].cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER); hotspots[3].cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER); hotspots[4].cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER); hotspots[5].cursor = gdk_cursor_new (GDK_TOP_SIDE); hotspots[6].cursor = gdk_cursor_new (GDK_RIGHT_SIDE); hotspots[7].cursor = gdk_cursor_new (GDK_BOTTOM_SIDE); hotspots[8].cursor = gdk_cursor_new (GDK_LEFT_SIDE); hotspots[9].cursor = gdk_cursor_new (GDK_CROSSHAIR); } static void hotspot_list_free (Hotspot *hotspots) { for (int n = 0; n < HOTSPOT_LAST; n++) gdk_cursor_unref (hotspots[n].cursor); } static void hotspot_list_recalculate (Hotspot *hotspots, GdkRectangle rect) { int border = 5; hotspots[HOTSPOT_INSIDE].rect = (GdkRectangle) { rect.x + border, rect.y + border, rect.width - border * 2, rect.height - border * 2 }; hotspots[HOTSPOT_RESIZE_NORTH_WEST].rect = (GdkRectangle) { rect.x - border, rect.y - border, border * 2, border * 2 }; hotspots[HOTSPOT_RESIZE_NORTH_EAST].rect = (GdkRectangle) { rect.x + rect.width - border, rect.y - border, border * 2, border * 2 }; hotspots[HOTSPOT_RESIZE_SOUTH_WEST].rect = (GdkRectangle) { rect.x - border, rect.y + rect.height - border, border * 2, border * 2 }; hotspots[HOTSPOT_RESIZE_SOUTH_EAST].rect = (GdkRectangle) { rect.x + rect.width - border, rect.y + rect.height - border, border * 2, border * 2 }; hotspots[HOTSPOT_RESIZE_NORTH].rect = (GdkRectangle) { rect.x + border, rect.y - border, rect.width - border * 2, border * 2 }; hotspots[HOTSPOT_RESIZE_EAST].rect = (GdkRectangle) { rect.x + rect.width - border, rect.y + border, border * 2, rect.height - border * 2 }; hotspots[HOTSPOT_RESIZE_SOUTH].rect = (GdkRectangle) { rect.x + border, rect.y + rect.height - border, rect.width - border * 2, border * 2 }; hotspots[HOTSPOT_RESIZE_WEST].rect = (GdkRectangle) { rect.x - border, rect.y + border, border * 2, rect.height - border * 2 }; // The sentinel hotspot, this one should catch every mouse event. hotspots[HOTSPOT_OUTSIDE].rect = (GdkRectangle) { -10000, -10000, 20000, 20000 }; } static HotspotType hotspot_list_find_hotspot (Hotspot hotspots[HOTSPOT_LAST], int x, int y) { for (int n = 0; n < HOTSPOT_LAST; n++) if (gdk_rectangle_contains (hotspots[n].rect, x, y)) return n; g_assert_not_reached (); return HOTSPOT_LAST; } /*************************************************************/ /***** Static stuff ******************************************/ /*************************************************************/ /** * gtk_image_tool_selector_image_to_widget_rect: * @rect_out: a #GdkRectangle to fill in with the widget space * coordinates. * @returns: %TRUE if the conversion was successful, %FALSE otherwise. * * Convert a rectangle in image space coordinates to widget space * coordinates. If the view is not realized, or if it contains no * pixbuf, then the conversion was unsuccessful, %FALSE is returned * and @rect_out is left unmodified. **/ static gboolean gtk_image_view_image_to_widget_rect (GtkImageView *view, GdkRectangle *rect_in, GdkRectangle *rect_out) { gdouble zoom = gtk_image_view_get_zoom (view); GdkRectangle zoom_rect = { rect_in->x * zoom, rect_in->y * zoom, rect_in->width * zoom, rect_in->height * zoom }; GdkRectangle image_rect, viewport; if (!gtk_image_view_get_draw_rect (view, &image_rect)) return FALSE; gtk_image_view_get_viewport (view, &viewport); rect_out->x = image_rect.x + zoom_rect.x - viewport.x; rect_out->y = image_rect.y + zoom_rect.y - viewport.y; rect_out->width = zoom_rect.width; rect_out->height = zoom_rect.height; return TRUE; } /** * gtk_image_tool_selector_update_selection: * * Sets the position of the tools selection rectangle. The position is * calculated based on the adjusted mouse drag delta which is * converted to image space coordinates. **/ static void gtk_image_tool_selector_update_selection (GtkImageToolSelector *selector) { int dx, dy; mouse_handler_get_drag_delta (selector->mouse_handler, &dx, &dy); gdouble zoom = gtk_image_view_get_zoom (selector->view); int img_dx = dx / zoom; int img_dy = dy / zoom; GdkRectangle new_sel = selector->sel_drag_start; int x1 = new_sel.x; int y1 = new_sel.y; int x2 = new_sel.x + new_sel.width; int y2 = new_sel.y + new_sel.height; GdkPixbuf *pixbuf = gtk_image_view_get_pixbuf (selector->view); int pb_width = gdk_pixbuf_get_width (pixbuf); int pb_height = gdk_pixbuf_get_height (pixbuf); if (selector->hotspot_type == HOTSPOT_INSIDE) { x1 = CLAMP (x1 - img_dx, 0, pb_width - new_sel.width); y1 = CLAMP (y1 - img_dy, 0, pb_height - new_sel.height); x2 = x1 + new_sel.width; y2 = y1 + new_sel.height; } else if (selector->hotspot_type == HOTSPOT_RESIZE_NORTH) y1 -= img_dy; else if (selector->hotspot_type == HOTSPOT_RESIZE_EAST) x2 -= img_dx; else if (selector->hotspot_type == HOTSPOT_RESIZE_SOUTH) y2 -= img_dy; else if (selector->hotspot_type == HOTSPOT_RESIZE_WEST) x1 -= img_dx; else if (selector->hotspot_type == HOTSPOT_RESIZE_NORTH_WEST) { x1 -= img_dx; y1 -= img_dy; } else if (selector->hotspot_type == HOTSPOT_RESIZE_NORTH_EAST) { x2 -= img_dx; y1 -= img_dy; } else if (selector->hotspot_type == HOTSPOT_RESIZE_SOUTH_WEST) { x1 -= img_dx; y2 -= img_dy; } else if (selector->hotspot_type == HOTSPOT_RESIZE_SOUTH_EAST) { x2 -= img_dx; y2 -= img_dy; } int tmp; if (y1 > y2) { tmp = y1; y1 = y2; y2 = tmp; } if (x1 > x2) { tmp = x1; x1 = x2; x2 = tmp; } // Make it so the coordinates can't go outside the area of the // pixbuf. y1 = MAX (y1, 0); x1 = MAX (x1, 0); x2 = MIN (x2, pb_width); y2 = MIN (y2, pb_height); new_sel.x = x1; new_sel.y = y1; new_sel.width = x2 - x1; new_sel.height = y2 - y1; gtk_image_tool_selector_set_selection (selector, &new_sel); } /** * gtk_image_tool_selector_calc_autoscroll: * @mouse_x: X-coordinate of the mouse * @mouse_y: Y-coordinate of the mouse * @scroll_x: Returned X-coordinate of the calculated autoscroll offset * @scroll_y: Returned Y-coordinate of the calculated autoscroll offset. * * Calculate the best autoscroll offset based on the mouse pointers * position. * * To avoid graphical artifacts, the offset should be a multiple of * the views zoom factor. If zoom is 4, then the offset should be 4n * where 'n' is an integer. autoscroll_cb is the function that does * the autoscrolling, it moves both the selection rectangle and the * viewport. If the offset is not a multiple of the zoom factor, then * the selection rectangles movement will appear jumpy. * * If the zoom factor is not an integer (7.5 for example), then the * autoscrolling will appear jumpy anyway. **/ static void gtk_image_tool_selector_calc_autoscroll (GtkImageToolSelector *selector, int mouse_x, int mouse_y, int *scroll_x, int *scroll_y) { gdouble zoom = gtk_image_view_get_zoom (selector->view); GtkAllocation alloc = GTK_WIDGET (selector->view)->allocation; /* Ensure that we atleast scroll something if the mouse is outside the widget. */ zoom = MAX (zoom, 1.0); *scroll_x = 0; if (mouse_x <= 0) *scroll_x = -zoom - (abs (mouse_x) / 4) * zoom; else if (mouse_x >= alloc.width) *scroll_x = zoom + (abs (mouse_x - alloc.width) / 4) * zoom; *scroll_y = 0; if (mouse_y <= 0) *scroll_y = -zoom - (abs (mouse_y) / 4) * zoom; else if (mouse_y >= alloc.height) *scroll_y = zoom + (abs (mouse_y - alloc.height) / 4) * zoom; } /** * gtk_image_tool_selector_get_hotspot_at_point: * * Return the type of the hotspot at the point @x, @y. If there is no * pixbuf in the view, or if it is realized, or if the point is * outside the views area return %HOTSPOT_LAST. **/ static HotspotType gtk_image_tool_selector_get_hotspot_at_point (GtkImageToolSelector *selector, int x, int y) { GdkRectangle image_rect; gtk_image_view_get_draw_rect (selector->view, &image_rect); if (!gdk_rectangle_contains (image_rect, x, y)) return HOTSPOT_LAST; GdkRectangle wid_sel; if (!gtk_image_view_image_to_widget_rect (selector->view, &selector->sel_rect, &wid_sel)) return HOTSPOT_LAST; hotspot_list_recalculate (selector->hotspots, wid_sel); return hotspot_list_find_hotspot (selector->hotspots, x, y); } /*************************************************************/ /***** Implementation of the GtkIImageTool interface *********/ /*************************************************************/ static GdkCursor* cursor_at_point (GtkIImageTool *tool, int x, int y) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (tool); HotspotType type = gtk_image_tool_selector_get_hotspot_at_point (selector, x, y); return selector->hotspots[type].cursor; } static gboolean button_press (GtkIImageTool *tool, GdkEventButton *ev) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (tool); if (ev->button != 1) return FALSE; selector->hotspot_type = gtk_image_tool_selector_get_hotspot_at_point (selector, ev->x, ev->y); if (selector->hotspot_type == HOTSPOT_LAST) return FALSE; if (selector->hotspot_type == HOTSPOT_OUTSIDE) { GdkRectangle viewport, image_rect; gtk_image_view_get_viewport (selector->view, &viewport); gtk_image_view_get_draw_rect (selector->view, &image_rect); int x = viewport.x - image_rect.x + ev->x; int y = viewport.y - image_rect.y + ev->y; gdouble zoom = gtk_image_view_get_zoom (selector->view); x = (int) ((gdouble) x / zoom); y = (int) ((gdouble) y / zoom); GdkRectangle new_sel = {x, y, 0, 0}; gtk_image_tool_selector_set_selection (selector, &new_sel); selector->hotspot_type = HOTSPOT_RESIZE_SOUTH_EAST; } selector->sel_drag_start = selector->sel_rect; // Set which cursor the mouse handler should use for grabbing. GdkCursor *cursor = selector->hotspots[selector->hotspot_type].cursor; if (selector->hotspot_type == HOTSPOT_INSIDE) cursor = selector->drag_cursor; selector->mouse_handler->grab_cursor = cursor; return mouse_handler_button_press (selector->mouse_handler, ev); } static gboolean button_release (GtkIImageTool *tool, GdkEventButton *ev) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (tool); if (selector->timer_id && ev->button == 1) { g_source_remove (selector->timer_id); selector->timer_id = 0; } return mouse_handler_button_release (selector->mouse_handler, ev); } static gboolean autoscroll_cb (gpointer data) { GtkImageToolSelector *selector = (GtkImageToolSelector *)data; GtkImageView *view = selector->view; GdkRectangle viewport; gtk_image_view_get_viewport (view, &viewport); gtk_image_view_set_offset (view, viewport.x + selector->outside_x, viewport.y + selector->outside_y, TRUE); // How much was the view scrolled? GdkRectangle viewport2; gtk_image_view_get_viewport (view, &viewport2); /* Tweak the base coordinate for the drag in accordance with how much the view really was scrolled. */ selector->mouse_handler->drag_base_x -= viewport2.x - viewport.x; selector->mouse_handler->drag_base_y -= viewport2.y - viewport.y; gtk_image_tool_selector_update_selection (selector); return TRUE; } static gboolean motion_notify (GtkIImageTool *tool, GdkEventMotion *ev) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (tool); mouse_handler_motion_notify (selector->mouse_handler, ev); if (!selector->mouse_handler->dragging) return FALSE; gtk_image_tool_selector_update_selection (selector); /* Check if, and how much the view should be autoscrolled. */ gtk_image_tool_selector_calc_autoscroll (selector, ev->x, ev->y, &selector->outside_x, &selector->outside_y); if (selector->outside_x || selector->outside_y) { if (!selector->timer_id) selector->timer_id = g_timeout_add (30, autoscroll_cb, selector); } else if (selector->timer_id) { g_source_remove (selector->timer_id); selector->timer_id = 0; } return FALSE; } static void pixbuf_changed (GtkIImageTool *tool, gboolean reset_fit) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (tool); if (selector->background) g_object_unref (selector->background); selector->background = NULL; if (reset_fit) selector->sel_rect = (GdkRectangle){0, 0, 0, 0}; GdkPixbuf *pixbuf = gtk_image_view_get_pixbuf (selector->view); if (!pixbuf) return; // Update the background. selector->background = gdk_pixbuf_copy (pixbuf); gdk_pixbuf_shade (selector->background); // Clear caches image_view_drawer_force_scale (selector->bg_drawer); image_view_drawer_force_scale (selector->fg_drawer); } static void paint_image (GtkIImageTool *tool, DrawSettings *ds, GdkDrawable *drawable) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (tool); // Draw the shaded background. DrawSettings bg_ds = *ds; bg_ds.pixbuf = selector->background; image_view_drawer_draw (selector->bg_drawer, &bg_ds, drawable); // Draw the selected area. // Intersect the selection rectangle with the exposed rectangle. GdkRectangle zoom_sel_rect = { selector->sel_rect.x * ds->zoom, selector->sel_rect.y * ds->zoom, selector->sel_rect.width * ds->zoom, selector->sel_rect.height * ds->zoom }; GdkRectangle zoom_paint_rect; if (!gdk_rectangle_intersect (&zoom_sel_rect, &ds->zoom_rect, &zoom_paint_rect)) return; DrawSettings fg_ds = *ds; fg_ds.zoom_rect = zoom_paint_rect; fg_ds.widget_x = ds->widget_x + zoom_paint_rect.x - ds->zoom_rect.x; fg_ds.widget_y = ds->widget_y + zoom_paint_rect.y - ds->zoom_rect.y; image_view_drawer_draw (selector->fg_drawer, &fg_ds, drawable); // Draw the selection rectangle. GdkGC *rect_gc = gdk_gc_new (drawable); gdk_gc_copy (rect_gc, GTK_WIDGET (selector->view)->style->black_gc); gdk_gc_set_line_attributes (rect_gc, 1, GDK_LINE_DOUBLE_DASH, GDK_CAP_BUTT, GDK_JOIN_MITER); GdkRectangle wid_rect; gtk_image_view_image_to_widget_rect (selector->view, &selector->sel_rect, &wid_rect); gdk_draw_rect (drawable, rect_gc, FALSE, &wid_rect); g_object_unref (rect_gc); } /*************************************************************/ /***** Stuff that deals with the type ************************/ /*************************************************************/ static void gtk_iimage_tool_interface_init (gpointer g_iface, gpointer iface_data) { GtkIImageToolClass *klass = (GtkIImageToolClass *) g_iface; klass->cursor_at_point = cursor_at_point; klass->button_press = button_press; klass->button_release = button_release; klass->motion_notify = motion_notify; klass->pixbuf_changed = pixbuf_changed; klass->paint_image = paint_image; } G_DEFINE_TYPE_EXTENDED (GtkImageToolSelector, gtk_image_tool_selector, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (GTK_TYPE_IIMAGE_TOOL, gtk_iimage_tool_interface_init)); static void gtk_image_tool_selector_finalize (GObject *object) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (object); if (selector->background) g_object_unref (selector->background); image_view_drawer_free (selector->bg_drawer); image_view_drawer_free (selector->fg_drawer); gdk_cursor_unref (selector->drag_cursor); hotspot_list_free (selector->hotspots); g_free (selector->mouse_handler); /* Chain up */ G_OBJECT_CLASS (gtk_image_tool_selector_parent_class)->finalize (object); } static void gtk_image_tool_selector_init_signals (GtkImageToolSelectorClass *klass) { /** * GtkImageToolSelector::selection-changed: * @selector: the selector that emitted the signal. * * The ::selection-changed signal is emitted when the selection * rectangle on the selector is moved or resized. It is inteded to * be used by applications that wants to print status * information. For example: * * * * static void * sel_changed_cb (GtkImageToolSelector *selector) * { * GdkRectangle sel; * gtk_image_tool_selector_get_selection (selector, &sel); * if (!sel.width || !sel.height) * printf ("No selection\n"); * else * printf ("The selection is %d, %d - %d, %d\n", * sel.x, sel.y, sel.width, sel.height); * } * ... * // selector is an already initialized GtkImageToolSelector * // object. * g_signal_connect (G_OBJECT(selector), "selection_changed", * G_CALLBACK (sel_changed_cb), NULL); * * **/ gtk_image_tool_selector_signals[0] = g_signal_new ("selection_changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkImageToolSelectorClass, selection_changed), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } enum { PROP_IMAGE_VIEW = 1 }; static void gtk_image_tool_selector_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkImageToolSelector *selector = GTK_IMAGE_TOOL_SELECTOR (object); if (prop_id == PROP_IMAGE_VIEW) selector->view = g_value_get_object (value); else G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } static void gtk_image_tool_selector_class_init (GtkImageToolSelectorClass *klass) { gtk_image_tool_selector_init_signals (klass); GObjectClass *object_class = (GObjectClass *) klass; object_class->finalize = gtk_image_tool_selector_finalize; object_class->set_property = gtk_image_tool_selector_set_property; GParamSpec *pspec = g_param_spec_object ("view", "Image View", "Image View to navigate", GTK_TYPE_IMAGE_VIEW, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE); g_object_class_install_property (object_class, PROP_IMAGE_VIEW, pspec); klass->selection_changed = NULL; } static void gtk_image_tool_selector_init (GtkImageToolSelector *tool) { tool->background = NULL; tool->view = NULL; tool->sel_rect = (GdkRectangle){0, 0, 0, 0}; tool->bg_drawer = image_view_drawer_new (); tool->fg_drawer = image_view_drawer_new (); tool->drag_cursor = cursor_get (CURSOR_HAND_CLOSED); tool->mouse_handler = mouse_handler_new (tool->drag_cursor); tool->timer_id = 0; // Init hotspots cursors. hotspot_list_init (tool->hotspots); } /** * gtk_image_tool_selector_new: * @view: a #GtkImageView * @returns: a new #GtkImageToolSelector * * Creates a new selector tool for the specified view with default * values. The default values are: * * * selection : (0, 0) - [0, 0] * **/ GtkIImageTool* gtk_image_tool_selector_new (GtkImageView *view) { g_return_val_if_fail (view, NULL); gpointer data = g_object_new (GTK_TYPE_IMAGE_TOOL_SELECTOR, "view", view, NULL); return GTK_IIMAGE_TOOL (data); } /*************************************************************/ /***** Read-write properties *********************************/ /*************************************************************/ /** * gtk_image_tool_selector_get_selection: * @selector: a #GtkImageToolSelector * @rect: a #GdkRectangle to fill in with the current selection * rectangle. * * Fills in @rect with the current selection rectangle. If either the * width or the height of @rect is zero, then nothing is selected and * the selection should be considered inactive. See * #GtkImageToolSelector::selection-changed for an example. **/ void gtk_image_tool_selector_get_selection (GtkImageToolSelector *selector, GdkRectangle *rect) { *rect = selector->sel_rect; } /** * gtk_image_tool_selector_set_selection: * @selector: a #GtkImageToolSelector * @rect: Selection rectangle in image space coordinates. * * Sets the selection rectangle for the tool. Setting this attribute * will cause the widget to immidiately repaint itself if its view is * realized. * * This method does nothing under the following circumstances: * * * If the views pixbuf is %NULL. * If @rect is wider or taller than the size of the * pixbuf * If @rect equals the current selection * rectangle. * * * If the selection falls outside the pixbufs area, its position is * moved so that it is within the pixbuf. * * Calling this method causes the ::selection-changed signal to be * emitted. * * The default selection is (0,0) - [0,0]. **/ void gtk_image_tool_selector_set_selection (GtkImageToolSelector *selector, GdkRectangle *rect) { GtkImageView *view = selector->view; GdkPixbuf *pixbuf = gtk_image_view_get_pixbuf (view); if (!pixbuf) return; int width = gdk_pixbuf_get_width (pixbuf); int height = gdk_pixbuf_get_height (pixbuf); if (rect->width > width || rect->height > height) return; rect->x = CLAMP (rect->x, 0, width - rect->width); rect->y = CLAMP (rect->y, 0, height - rect->height); if (gdk_rectangle_eq (*rect, selector->sel_rect)) return; GdkRectangle wid_old = {0}, wid_new = {0}; gtk_image_view_image_to_widget_rect (view, &selector->sel_rect, &wid_old); gtk_image_view_image_to_widget_rect (view, rect, &wid_new); selector->sel_rect = *rect; gtk_widget_queue_draw_area (GTK_WIDGET (view), wid_old.x, wid_old.y, wid_old.width, wid_old.height); gtk_widget_queue_draw_area (GTK_WIDGET (view), wid_new.x, wid_new.y, wid_new.width, wid_new.height); g_signal_emit (G_OBJECT (selector), gtk_image_tool_selector_signals[0], 0); }