gimp/libgimpwidgets/gimpoffsetarea.c
Michael Natterer 8005eea835 Remove the "GIMP" from all "Since: GIMP 2.x" API doc comments
because it confuses gtk-doc and breaks some links. Also change the
"Index of new symbols in GIMP 2.x" sections to be what seems to be the
modern standard (looked at the GLib and GTK+ docs), and update some
other stuff.
2015-05-31 21:18:09 +02:00

506 lines
14 KiB
C

/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* gimpoffsetarea.c
* Copyright (C) 2001 Sven Neumann <sven@gimp.org>
*
* 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
* <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gtk/gtk.h>
#include "gimpwidgetstypes.h"
#include "gimpwidgetsmarshal.h"
#include "gimpoffsetarea.h"
/**
* SECTION: gimpoffsetarea
* @title: GimpOffsetArea
* @short_description: Widget to control image offsets.
*
* Widget to control image offsets.
**/
#define DRAWING_AREA_SIZE 200
enum
{
OFFSETS_CHANGED,
LAST_SIGNAL
};
static void gimp_offset_area_resize (GimpOffsetArea *area);
static void gimp_offset_area_realize (GtkWidget *widget);
static void gimp_offset_area_size_allocate (GtkWidget *widget,
GtkAllocation *allocation);
static gboolean gimp_offset_area_event (GtkWidget *widget,
GdkEvent *event);
static gboolean gimp_offset_area_expose_event (GtkWidget *widget,
GdkEventExpose *eevent);
G_DEFINE_TYPE (GimpOffsetArea, gimp_offset_area, GTK_TYPE_DRAWING_AREA)
#define parent_class gimp_offset_area_parent_class
static guint gimp_offset_area_signals[LAST_SIGNAL] = { 0 };
static void
gimp_offset_area_class_init (GimpOffsetAreaClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gimp_offset_area_signals[OFFSETS_CHANGED] =
g_signal_new ("offsets-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GimpOffsetAreaClass, offsets_changed),
NULL, NULL,
_gimp_widgets_marshal_VOID__INT_INT,
G_TYPE_NONE, 2,
G_TYPE_INT,
G_TYPE_INT);
widget_class->size_allocate = gimp_offset_area_size_allocate;
widget_class->realize = gimp_offset_area_realize;
widget_class->event = gimp_offset_area_event;
widget_class->expose_event = gimp_offset_area_expose_event;
}
static void
gimp_offset_area_init (GimpOffsetArea *area)
{
area->orig_width = 0;
area->orig_height = 0;
area->width = 0;
area->height = 0;
area->offset_x = 0;
area->offset_y = 0;
area->display_ratio_x = 1.0;
area->display_ratio_y = 1.0;
gtk_widget_add_events (GTK_WIDGET (area),
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_BUTTON1_MOTION_MASK);
}
/**
* gimp_offset_area_new:
* @orig_width: the original width
* @orig_height: the original height
*
* Creates a new #GimpOffsetArea widget. A #GimpOffsetArea can be used
* when resizing an image or a drawable to allow the user to interactively
* specify the new offsets.
*
* Return value: the new #GimpOffsetArea widget.
**/
GtkWidget *
gimp_offset_area_new (gint orig_width,
gint orig_height)
{
GimpOffsetArea *area;
g_return_val_if_fail (orig_width > 0, NULL);
g_return_val_if_fail (orig_height > 0, NULL);
area = g_object_new (GIMP_TYPE_OFFSET_AREA, NULL);
area->orig_width = area->width = orig_width;
area->orig_height = area->height = orig_height;
gimp_offset_area_resize (area);
return GTK_WIDGET (area);
}
/**
* gimp_offset_area_set_pixbuf:
* @offset_area: a #GimpOffsetArea.
* @pixbuf: a #GdkPixbuf.
*
* Sets the pixbuf which represents the original image/drawable which
* is being offset.
*
* Since: 2.2
**/
void
gimp_offset_area_set_pixbuf (GimpOffsetArea *area,
GdkPixbuf *pixbuf)
{
g_return_if_fail (GIMP_IS_OFFSET_AREA (area));
g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
g_object_set_data_full (G_OBJECT (area), "pixbuf",
gdk_pixbuf_copy (pixbuf),
(GDestroyNotify) g_object_unref);
gtk_widget_queue_draw (GTK_WIDGET (area));
}
/**
* gimp_offset_area_set_size:
* @offset_area: a #GimpOffsetArea.
* @width: the new width
* @height: the new height
*
* Sets the size of the image/drawable displayed by the #GimpOffsetArea.
* If the offsets change as a result of this change, the "offsets-changed"
* signal is emitted.
**/
void
gimp_offset_area_set_size (GimpOffsetArea *area,
gint width,
gint height)
{
g_return_if_fail (GIMP_IS_OFFSET_AREA (area));
g_return_if_fail (width > 0 && height > 0);
if (area->width != width || area->height != height)
{
gint offset_x;
gint offset_y;
area->width = width;
area->height = height;
if (area->orig_width <= area->width)
offset_x = CLAMP (area->offset_x, 0, area->width - area->orig_width);
else
offset_x = CLAMP (area->offset_x, area->width - area->orig_width, 0);
if (area->orig_height <= area->height)
offset_y = CLAMP (area->offset_y, 0, area->height - area->orig_height);
else
offset_y = CLAMP (area->offset_y, area->height - area->orig_height, 0);
if (offset_x != area->offset_x || offset_y != area->offset_y)
{
area->offset_x = offset_x;
area->offset_y = offset_y;
g_signal_emit (area,
gimp_offset_area_signals[OFFSETS_CHANGED], 0,
offset_x, offset_y);
}
gimp_offset_area_resize (area);
}
}
/**
* gimp_offset_area_set_offsets:
* @offset_area: a #GimpOffsetArea.
* @offset_x: the X offset
* @offset_y: the Y offset
*
* Sets the offsets of the image/drawable displayed by the #GimpOffsetArea.
* It does not emit the "offsets-changed" signal.
**/
void
gimp_offset_area_set_offsets (GimpOffsetArea *area,
gint offset_x,
gint offset_y)
{
g_return_if_fail (GIMP_IS_OFFSET_AREA (area));
if (area->offset_x != offset_x || area->offset_y != offset_y)
{
if (area->orig_width <= area->width)
area->offset_x = CLAMP (offset_x, 0, area->width - area->orig_width);
else
area->offset_x = CLAMP (offset_x, area->width - area->orig_width, 0);
if (area->orig_height <= area->height)
area->offset_y = CLAMP (offset_y, 0, area->height - area->orig_height);
else
area->offset_y = CLAMP (offset_y, area->height - area->orig_height, 0);
gtk_widget_queue_draw (GTK_WIDGET (area));
}
}
static void
gimp_offset_area_resize (GimpOffsetArea *area)
{
gint width;
gint height;
gdouble ratio;
if (area->orig_width == 0 || area->orig_height == 0)
return;
if (area->orig_width <= area->width)
width = area->width;
else
width = area->orig_width * 2 - area->width;
if (area->orig_height <= area->height)
height = area->height;
else
height = area->orig_height * 2 - area->height;
ratio = (gdouble) DRAWING_AREA_SIZE / (gdouble) MAX (width, height);
width = ratio * (gdouble) width;
height = ratio * (gdouble) height;
gtk_widget_set_size_request (GTK_WIDGET (area), width, height);
gtk_widget_queue_resize (GTK_WIDGET (area));
}
static void
gimp_offset_area_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GimpOffsetArea *area = GIMP_OFFSET_AREA (widget);
GdkPixbuf *pixbuf;
GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
area->display_ratio_x = ((gdouble) allocation->width /
((area->orig_width <= area->width) ?
area->width :
area->orig_width * 2 - area->width));
area->display_ratio_y = ((gdouble) allocation->height /
((area->orig_height <= area->height) ?
area->height :
area->orig_height * 2 - area->height));
pixbuf = g_object_get_data (G_OBJECT (area), "pixbuf");
if (pixbuf)
{
GdkPixbuf *copy;
gint pixbuf_width;
gint pixbuf_height;
pixbuf_width = area->display_ratio_x * area->orig_width;
pixbuf_width = MAX (pixbuf_width, 1);
pixbuf_height = area->display_ratio_y * area->orig_height;
pixbuf_height = MAX (pixbuf_height, 1);
copy = g_object_get_data (G_OBJECT (area), "pixbuf-copy");
if (copy &&
(pixbuf_width != gdk_pixbuf_get_width (copy) ||
pixbuf_height != gdk_pixbuf_get_height (copy)))
{
copy = NULL;
}
if (! copy)
{
copy = gdk_pixbuf_scale_simple (pixbuf, pixbuf_width, pixbuf_height,
GDK_INTERP_NEAREST);
g_object_set_data_full (G_OBJECT (area), "pixbuf-copy",
copy, (GDestroyNotify) g_object_unref);
}
}
}
static void
gimp_offset_area_realize (GtkWidget *widget)
{
GdkCursor *cursor;
GTK_WIDGET_CLASS (parent_class)->realize (widget);
cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget),
GDK_FLEUR);
gdk_window_set_cursor (gtk_widget_get_window (widget), cursor);
gdk_cursor_unref (cursor);
}
static gboolean
gimp_offset_area_event (GtkWidget *widget,
GdkEvent *event)
{
static gint orig_offset_x = 0;
static gint orig_offset_y = 0;
static gint start_x = 0;
static gint start_y = 0;
GimpOffsetArea *area = GIMP_OFFSET_AREA (widget);
gint offset_x;
gint offset_y;
if (area->orig_width == 0 || area->orig_height == 0)
return FALSE;
switch (event->type)
{
case GDK_BUTTON_PRESS:
if (event->button.button == 1)
{
gtk_grab_add (widget);
orig_offset_x = area->offset_x;
orig_offset_y = area->offset_y;
start_x = event->button.x;
start_y = event->button.y;
}
break;
case GDK_MOTION_NOTIFY:
offset_x = (orig_offset_x +
(event->motion.x - start_x) / area->display_ratio_x);
offset_y = (orig_offset_y +
(event->motion.y - start_y) / area->display_ratio_y);
if (area->offset_x != offset_x || area->offset_y != offset_y)
{
gimp_offset_area_set_offsets (area, offset_x, offset_y);
g_signal_emit (area,
gimp_offset_area_signals[OFFSETS_CHANGED], 0,
area->offset_x, area->offset_y);
}
break;
case GDK_BUTTON_RELEASE:
if (event->button.button == 1)
{
gtk_grab_remove (widget);
start_x = start_y = 0;
}
break;
default:
return FALSE;
}
return TRUE;
}
static gboolean
gimp_offset_area_expose_event (GtkWidget *widget,
GdkEventExpose *eevent)
{
GimpOffsetArea *area = GIMP_OFFSET_AREA (widget);
GtkStyle *style = gtk_widget_get_style (widget);
GdkWindow *window = gtk_widget_get_window (widget);
cairo_t *cr;
GtkAllocation allocation;
GdkPixbuf *pixbuf;
gint w, h;
gint x, y;
cr = gdk_cairo_create (eevent->window);
gdk_cairo_region (cr, eevent->region);
cairo_clip (cr);
gtk_widget_get_allocation (widget, &allocation);
x = (area->display_ratio_x *
((area->orig_width <= area->width) ?
area->offset_x :
area->offset_x + area->orig_width - area->width));
y = (area->display_ratio_y *
((area->orig_height <= area->height) ?
area->offset_y :
area->offset_y + area->orig_height - area->height));
w = area->display_ratio_x * area->orig_width;
w = MAX (w, 1);
h = area->display_ratio_y * area->orig_height;
h = MAX (h, 1);
pixbuf = g_object_get_data (G_OBJECT (widget), "pixbuf-copy");
if (pixbuf)
{
gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
cairo_paint (cr);
cairo_rectangle (cr, x + 0.5, y + 0.5, w - 1, h - 1);
cairo_set_line_width (cr, 1.0);
gdk_cairo_set_source_color (cr, &style->black);
cairo_stroke (cr);
}
else
{
gtk_paint_shadow (style, window, GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
NULL, widget, NULL,
x, y, w, h);
}
if (area->orig_width > area->width || area->orig_height > area->height)
{
gint line_width;
if (area->orig_width > area->width)
{
x = area->display_ratio_x * (area->orig_width - area->width);
w = area->display_ratio_x * area->width;
}
else
{
x = -1;
w = allocation.width + 2;
}
if (area->orig_height > area->height)
{
y = area->display_ratio_y * (area->orig_height - area->height);
h = area->display_ratio_y * area->height;
}
else
{
y = -1;
h = allocation.height + 2;
}
w = MAX (w, 1);
h = MAX (h, 1);
line_width = MIN (3, MIN (w, h));
cairo_rectangle (cr,
x + line_width / 2.0,
y + line_width / 2.0,
MAX (w - line_width, 1),
MAX (h - line_width, 1));
cairo_set_line_width (cr, line_width);
cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6);
cairo_stroke_preserve (cr);
cairo_set_line_width (cr, 1.0);
cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8);
cairo_stroke (cr);
}
cairo_destroy (cr);
return FALSE;
}