summaryrefslogtreecommitdiffstats
path: root/app/widgets/gimpcircle.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/widgets/gimpcircle.c')
-rw-r--r--app/widgets/gimpcircle.c589
1 files changed, 589 insertions, 0 deletions
diff --git a/app/widgets/gimpcircle.c b/app/widgets/gimpcircle.c
new file mode 100644
index 0000000..407d9e7
--- /dev/null
+++ b/app/widgets/gimpcircle.c
@@ -0,0 +1,589 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimpcircle.c
+ * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on code from the color-rotate plug-in
+ * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de)
+ * Based on code from Pavel Grinfeld (pavel@ml.com)
+ *
+ * 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 3 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, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpmath/gimpmath.h"
+#include "libgimpcolor/gimpcolor.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "widgets-types.h"
+
+#include "gimpcircle.h"
+
+
+enum
+{
+ PROP_0,
+ PROP_SIZE,
+ PROP_BORDER_WIDTH,
+ PROP_BACKGROUND
+};
+
+
+struct _GimpCirclePrivate
+{
+ gint size;
+ gint border_width;
+ GimpCircleBackground background;
+
+ GdkWindow *event_window;
+ cairo_surface_t *surface;
+ gboolean has_grab;
+ gboolean in_widget;
+};
+
+
+static void gimp_circle_dispose (GObject *object);
+static void gimp_circle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_circle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_circle_realize (GtkWidget *widget);
+static void gimp_circle_unrealize (GtkWidget *widget);
+static void gimp_circle_map (GtkWidget *widget);
+static void gimp_circle_unmap (GtkWidget *widget);
+static void gimp_circle_size_request (GtkWidget *widget,
+ GtkRequisition *requisition);
+static void gimp_circle_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation);
+static gboolean gimp_circle_expose_event (GtkWidget *widget,
+ GdkEventExpose *event);
+static gboolean gimp_circle_button_press_event (GtkWidget *widget,
+ GdkEventButton *bevent);
+static gboolean gimp_circle_button_release_event (GtkWidget *widget,
+ GdkEventButton *bevent);
+static gboolean gimp_circle_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event);
+static gboolean gimp_circle_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event);
+
+static void gimp_circle_real_reset_target (GimpCircle *circle);
+
+static void gimp_circle_background_hsv (gdouble angle,
+ gdouble distance,
+ guchar *rgb);
+
+static void gimp_circle_draw_background (GimpCircle *circle,
+ cairo_t *cr,
+ gint size,
+ GimpCircleBackground background);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpCircle, gimp_circle, GTK_TYPE_WIDGET)
+
+#define parent_class gimp_circle_parent_class
+
+
+static void
+gimp_circle_class_init (GimpCircleClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->dispose = gimp_circle_dispose;
+ object_class->get_property = gimp_circle_get_property;
+ object_class->set_property = gimp_circle_set_property;
+
+ widget_class->realize = gimp_circle_realize;
+ widget_class->unrealize = gimp_circle_unrealize;
+ widget_class->map = gimp_circle_map;
+ widget_class->unmap = gimp_circle_unmap;
+ widget_class->size_request = gimp_circle_size_request;
+ widget_class->size_allocate = gimp_circle_size_allocate;
+ widget_class->expose_event = gimp_circle_expose_event;
+ widget_class->button_press_event = gimp_circle_button_press_event;
+ widget_class->button_release_event = gimp_circle_button_release_event;
+ widget_class->enter_notify_event = gimp_circle_enter_notify_event;
+ widget_class->leave_notify_event = gimp_circle_leave_notify_event;
+
+ klass->reset_target = gimp_circle_real_reset_target;
+
+ g_object_class_install_property (object_class, PROP_SIZE,
+ g_param_spec_int ("size",
+ NULL, NULL,
+ 32, 1024, 96,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_BORDER_WIDTH,
+ g_param_spec_int ("border-width",
+ NULL, NULL,
+ 0, 64, 0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_BACKGROUND,
+ g_param_spec_enum ("background",
+ NULL, NULL,
+ GIMP_TYPE_CIRCLE_BACKGROUND,
+ GIMP_CIRCLE_BACKGROUND_HSV,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_circle_init (GimpCircle *circle)
+{
+ circle->priv = gimp_circle_get_instance_private (circle);
+
+ gtk_widget_set_has_window (GTK_WIDGET (circle), FALSE);
+ gtk_widget_add_events (GTK_WIDGET (circle),
+ GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK |
+ GDK_BUTTON1_MOTION_MASK |
+ GDK_ENTER_NOTIFY_MASK |
+ GDK_LEAVE_NOTIFY_MASK);
+}
+
+static void
+gimp_circle_dispose (GObject *object)
+{
+ GimpCircle *circle = GIMP_CIRCLE (object);
+
+ g_clear_pointer (&circle->priv->surface, cairo_surface_destroy);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gimp_circle_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCircle *circle = GIMP_CIRCLE (object);
+
+ switch (property_id)
+ {
+ case PROP_SIZE:
+ circle->priv->size = g_value_get_int (value);
+ gtk_widget_queue_resize (GTK_WIDGET (circle));
+ break;
+
+ case PROP_BORDER_WIDTH:
+ circle->priv->border_width = g_value_get_int (value);
+ gtk_widget_queue_resize (GTK_WIDGET (circle));
+ break;
+
+ case PROP_BACKGROUND:
+ circle->priv->background = g_value_get_enum (value);
+ g_clear_pointer (&circle->priv->surface, cairo_surface_destroy);
+ gtk_widget_queue_draw (GTK_WIDGET (circle));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_circle_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpCircle *circle = GIMP_CIRCLE (object);
+
+ switch (property_id)
+ {
+ case PROP_SIZE:
+ g_value_set_int (value, circle->priv->size);
+ break;
+
+ case PROP_BORDER_WIDTH:
+ g_value_set_int (value, circle->priv->border_width);
+ break;
+
+ case PROP_BACKGROUND:
+ g_value_set_enum (value, circle->priv->background);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_circle_realize (GtkWidget *widget)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+ GtkAllocation allocation;
+ GdkWindowAttr attributes;
+ gint attributes_mask;
+
+ GTK_WIDGET_CLASS (parent_class)->realize (widget);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ attributes.window_type = GDK_WINDOW_CHILD;
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_ONLY;
+ attributes.event_mask = gtk_widget_get_events (widget);
+
+ attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+ circle->priv->event_window = gdk_window_new (gtk_widget_get_window (widget),
+ &attributes, attributes_mask);
+ gdk_window_set_user_data (circle->priv->event_window, circle);
+}
+
+static void
+gimp_circle_unrealize (GtkWidget *widget)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ if (circle->priv->event_window)
+ {
+ gdk_window_set_user_data (circle->priv->event_window, NULL);
+ gdk_window_destroy (circle->priv->event_window);
+ circle->priv->event_window = NULL;
+ }
+
+ GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static void
+gimp_circle_map (GtkWidget *widget)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->map (widget);
+
+ if (circle->priv->event_window)
+ gdk_window_show (circle->priv->event_window);
+}
+
+static void
+gimp_circle_unmap (GtkWidget *widget)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ if (circle->priv->has_grab)
+ {
+ gtk_grab_remove (widget);
+ circle->priv->has_grab = FALSE;
+ }
+
+ if (circle->priv->event_window)
+ gdk_window_hide (circle->priv->event_window);
+
+ GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+gimp_circle_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ requisition->width = 2 * circle->priv->border_width + circle->priv->size;
+ requisition->height = 2 * circle->priv->border_width + circle->priv->size;
+}
+
+static void
+gimp_circle_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
+
+ if (gtk_widget_get_realized (widget))
+ gdk_window_move_resize (circle->priv->event_window,
+ allocation->x,
+ allocation->y,
+ allocation->width,
+ allocation->height);
+
+ g_clear_pointer (&circle->priv->surface, cairo_surface_destroy);
+}
+
+static gboolean
+gimp_circle_expose_event (GtkWidget *widget,
+ GdkEventExpose *event)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ if (gtk_widget_is_drawable (widget))
+ {
+ GtkAllocation allocation;
+ gint size = circle->priv->size;
+ cairo_t *cr;
+
+ cr = gdk_cairo_create (event->window);
+ gdk_cairo_region (cr, event->region);
+ cairo_clip (cr);
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ cairo_translate (cr,
+ allocation.x + (allocation.width - size) / 2,
+ allocation.y + (allocation.height - size) / 2);
+
+ gimp_circle_draw_background (circle, cr, size, circle->priv->background);
+
+ cairo_destroy (cr);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_circle_button_press_event (GtkWidget *widget,
+ GdkEventButton *bevent)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ if (bevent->type == GDK_BUTTON_PRESS &&
+ bevent->button == 1)
+ {
+ gtk_grab_add (widget);
+ circle->priv->has_grab = TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_circle_button_release_event (GtkWidget *widget,
+ GdkEventButton *bevent)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ if (bevent->button == 1)
+ {
+ gtk_grab_remove (widget);
+ circle->priv->has_grab = FALSE;
+
+ if (! circle->priv->in_widget)
+ GIMP_CIRCLE_GET_CLASS (circle)->reset_target (circle);
+ }
+
+ return FALSE;
+}
+
+static gboolean
+gimp_circle_enter_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ circle->priv->in_widget = TRUE;
+
+ return FALSE;
+}
+
+static gboolean
+gimp_circle_leave_notify_event (GtkWidget *widget,
+ GdkEventCrossing *event)
+{
+ GimpCircle *circle = GIMP_CIRCLE (widget);
+
+ circle->priv->in_widget = FALSE;
+
+ if (! circle->priv->has_grab)
+ GIMP_CIRCLE_GET_CLASS (circle)->reset_target (circle);
+
+ return FALSE;
+}
+
+static void
+gimp_circle_real_reset_target (GimpCircle *circle)
+{
+}
+
+
+/* public functions */
+
+GtkWidget *
+gimp_circle_new (void)
+{
+ return g_object_new (GIMP_TYPE_CIRCLE, NULL);
+}
+
+
+/* protected functions */
+
+static gdouble
+get_angle_and_distance (gdouble center_x,
+ gdouble center_y,
+ gdouble radius,
+ gdouble x,
+ gdouble y,
+ gdouble *distance)
+{
+ gdouble angle = atan2 (center_y - y,
+ x - center_x);
+
+ if (angle < 0)
+ angle += 2 * G_PI;
+
+ if (distance)
+ *distance = sqrt ((SQR (x - center_x) +
+ SQR (y - center_y)) / SQR (radius));
+
+ return angle;
+}
+
+gboolean
+_gimp_circle_has_grab (GimpCircle *circle)
+{
+ g_return_val_if_fail (GIMP_IS_CIRCLE (circle), FALSE);
+
+ return circle->priv->has_grab;
+}
+
+gdouble
+_gimp_circle_get_angle_and_distance (GimpCircle *circle,
+ gdouble event_x,
+ gdouble event_y,
+ gdouble *distance)
+{
+ GtkAllocation allocation;
+ gdouble center_x;
+ gdouble center_y;
+
+ g_return_val_if_fail (GIMP_IS_CIRCLE (circle), 0.0);
+
+ gtk_widget_get_allocation (GTK_WIDGET (circle), &allocation);
+
+ center_x = allocation.width / 2.0;
+ center_y = allocation.height / 2.0;
+
+ return get_angle_and_distance (center_x, center_y, circle->priv->size / 2.0,
+ event_x, event_y,
+ distance);
+}
+
+
+/* private functions */
+
+static void
+gimp_circle_background_hsv (gdouble angle,
+ gdouble distance,
+ guchar *rgb)
+{
+ GimpHSV hsv;
+ GimpRGB color;
+
+ gimp_hsv_set (&hsv,
+ angle / (2.0 * G_PI),
+ distance,
+ 1 - sqrt (distance) / 4 /* it just looks nicer this way */);
+
+ gimp_hsv_to_rgb (&hsv, &color);
+
+ gimp_rgb_get_uchar (&color, rgb, rgb + 1, rgb + 2);
+}
+
+static void
+gimp_circle_draw_background (GimpCircle *circle,
+ cairo_t *cr,
+ gint size,
+ GimpCircleBackground background)
+{
+ cairo_save (cr);
+
+ if (background == GIMP_CIRCLE_BACKGROUND_PLAIN)
+ {
+ cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0 - 1.5, 0.0, 2 * G_PI);
+
+ cairo_set_line_width (cr, 3.0);
+ 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);
+ }
+ else
+ {
+ if (! circle->priv->surface)
+ {
+ guchar *data;
+ gint stride;
+ gint x, y;
+
+ circle->priv->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ size, size);
+
+ data = cairo_image_surface_get_data (circle->priv->surface);
+ stride = cairo_image_surface_get_stride (circle->priv->surface);
+
+ for (y = 0; y < size; y++)
+ {
+ for (x = 0; x < size; x++)
+ {
+ gdouble angle;
+ gdouble distance;
+ guchar rgb[3] = { 0, };
+
+ angle = get_angle_and_distance (size / 2.0, size / 2.0,
+ size / 2.0,
+ x, y,
+ &distance);
+
+ switch (background)
+ {
+ case GIMP_CIRCLE_BACKGROUND_HSV:
+ gimp_circle_background_hsv (angle, distance, rgb);
+ break;
+
+ default:
+ break;
+ }
+
+ GIMP_CAIRO_ARGB32_SET_PIXEL (data + y * stride + x * 4,
+ rgb[0], rgb[1], rgb[2], 255);
+ }
+ }
+
+ cairo_surface_mark_dirty (circle->priv->surface);
+ }
+
+ cairo_set_source_surface (cr, circle->priv->surface, 0.0, 0.0);
+
+ cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0, 0.0, 2 * G_PI);
+ cairo_clip (cr);
+
+ cairo_paint (cr);
+ }
+
+ cairo_restore (cr);
+}