summaryrefslogtreecommitdiffstats
path: root/libgimpwidgets/gimpoffsetarea.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libgimpwidgets/gimpoffsetarea.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/libgimpwidgets/gimpoffsetarea.c b/libgimpwidgets/gimpoffsetarea.c
new file mode 100644
index 0000000..3cf9521
--- /dev/null
+++ b/libgimpwidgets/gimpoffsetarea.c
@@ -0,0 +1,506 @@
+/* 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
+ * <https://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;
+}