/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikacircle.c * Copyright (C) 2014 Michael Natterer * * 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 . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "libpikacolor/pikacolor.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "pikacircle.h" enum { PROP_0, PROP_SIZE, PROP_BORDER_WIDTH, PROP_BACKGROUND }; struct _PikaCirclePrivate { gint size; gint border_width; PikaCircleBackground background; GdkWindow *event_window; cairo_surface_t *surface; gboolean has_grab; gboolean in_widget; }; static void pika_circle_dispose (GObject *object); static void pika_circle_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_circle_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_circle_realize (GtkWidget *widget); static void pika_circle_unrealize (GtkWidget *widget); static void pika_circle_map (GtkWidget *widget); static void pika_circle_unmap (GtkWidget *widget); static void pika_circle_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width); static void pika_circle_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height); static void pika_circle_size_allocate (GtkWidget *widget, GtkAllocation *allocation); static gboolean pika_circle_draw (GtkWidget *widget, cairo_t *cr); static gboolean pika_circle_button_press_event (GtkWidget *widget, GdkEventButton *bevent); static gboolean pika_circle_button_release_event (GtkWidget *widget, GdkEventButton *bevent); static gboolean pika_circle_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event); static gboolean pika_circle_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event); static void pika_circle_real_reset_target (PikaCircle *circle); static void pika_circle_background_hsv (gdouble angle, gdouble distance, guchar *rgb); static void pika_circle_draw_background (PikaCircle *circle, cairo_t *cr, gint size, PikaCircleBackground background); G_DEFINE_TYPE_WITH_PRIVATE (PikaCircle, pika_circle, GTK_TYPE_WIDGET) #define parent_class pika_circle_parent_class static void pika_circle_class_init (PikaCircleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); object_class->dispose = pika_circle_dispose; object_class->get_property = pika_circle_get_property; object_class->set_property = pika_circle_set_property; widget_class->realize = pika_circle_realize; widget_class->unrealize = pika_circle_unrealize; widget_class->map = pika_circle_map; widget_class->unmap = pika_circle_unmap; widget_class->get_preferred_width = pika_circle_get_preferred_width; widget_class->get_preferred_height = pika_circle_get_preferred_height; widget_class->size_allocate = pika_circle_size_allocate; widget_class->draw = pika_circle_draw; widget_class->button_press_event = pika_circle_button_press_event; widget_class->button_release_event = pika_circle_button_release_event; widget_class->enter_notify_event = pika_circle_enter_notify_event; widget_class->leave_notify_event = pika_circle_leave_notify_event; klass->reset_target = pika_circle_real_reset_target; g_object_class_install_property (object_class, PROP_SIZE, g_param_spec_int ("size", NULL, NULL, 32, 1024, 96, PIKA_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, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_BACKGROUND, g_param_spec_enum ("background", NULL, NULL, PIKA_TYPE_CIRCLE_BACKGROUND, PIKA_CIRCLE_BACKGROUND_HSV, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); } static void pika_circle_init (PikaCircle *circle) { circle->priv = pika_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 pika_circle_dispose (GObject *object) { PikaCircle *circle = PIKA_CIRCLE (object); g_clear_pointer (&circle->priv->surface, cairo_surface_destroy); G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_circle_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaCircle *circle = PIKA_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 pika_circle_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaCircle *circle = PIKA_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 pika_circle_realize (GtkWidget *widget) { PikaCircle *circle = PIKA_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 pika_circle_unrealize (GtkWidget *widget) { PikaCircle *circle = PIKA_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 pika_circle_map (GtkWidget *widget) { PikaCircle *circle = PIKA_CIRCLE (widget); GTK_WIDGET_CLASS (parent_class)->map (widget); if (circle->priv->event_window) gdk_window_show (circle->priv->event_window); } static void pika_circle_unmap (GtkWidget *widget) { PikaCircle *circle = PIKA_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 pika_circle_get_preferred_width (GtkWidget *widget, gint *minimum_width, gint *natural_width) { PikaCircle *circle = PIKA_CIRCLE (widget); *minimum_width = *natural_width = 2 * circle->priv->border_width + circle->priv->size; } static void pika_circle_get_preferred_height (GtkWidget *widget, gint *minimum_height, gint *natural_height) { PikaCircle *circle = PIKA_CIRCLE (widget); *minimum_height = *natural_height = 2 * circle->priv->border_width + circle->priv->size; } static void pika_circle_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { PikaCircle *circle = PIKA_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 pika_circle_draw (GtkWidget *widget, cairo_t *cr) { PikaCircle *circle = PIKA_CIRCLE (widget); GtkAllocation allocation; gint size = circle->priv->size; gtk_widget_get_allocation (widget, &allocation); cairo_save (cr); cairo_translate (cr, (allocation.width - size) / 2, (allocation.height - size) / 2); pika_circle_draw_background (circle, cr, size, circle->priv->background); cairo_restore (cr); return FALSE; } static gboolean pika_circle_button_press_event (GtkWidget *widget, GdkEventButton *bevent) { PikaCircle *circle = PIKA_CIRCLE (widget); if (bevent->type == GDK_BUTTON_PRESS && bevent->button == 1) { gtk_grab_add (widget); circle->priv->has_grab = TRUE; } return FALSE; } static gboolean pika_circle_button_release_event (GtkWidget *widget, GdkEventButton *bevent) { PikaCircle *circle = PIKA_CIRCLE (widget); if (bevent->button == 1) { gtk_grab_remove (widget); circle->priv->has_grab = FALSE; if (! circle->priv->in_widget) PIKA_CIRCLE_GET_CLASS (circle)->reset_target (circle); } return FALSE; } static gboolean pika_circle_enter_notify_event (GtkWidget *widget, GdkEventCrossing *event) { PikaCircle *circle = PIKA_CIRCLE (widget); circle->priv->in_widget = TRUE; return FALSE; } static gboolean pika_circle_leave_notify_event (GtkWidget *widget, GdkEventCrossing *event) { PikaCircle *circle = PIKA_CIRCLE (widget); circle->priv->in_widget = FALSE; if (! circle->priv->has_grab) PIKA_CIRCLE_GET_CLASS (circle)->reset_target (circle); return FALSE; } static void pika_circle_real_reset_target (PikaCircle *circle) { } /* public functions */ GtkWidget * pika_circle_new (void) { return g_object_new (PIKA_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 _pika_circle_has_grab (PikaCircle *circle) { g_return_val_if_fail (PIKA_IS_CIRCLE (circle), FALSE); return circle->priv->has_grab; } gdouble _pika_circle_get_angle_and_distance (PikaCircle *circle, gdouble event_x, gdouble event_y, gdouble *distance) { GtkAllocation allocation; gdouble center_x; gdouble center_y; g_return_val_if_fail (PIKA_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 pika_circle_background_hsv (gdouble angle, gdouble distance, guchar *rgb) { PikaHSV hsv; PikaRGB color; pika_hsv_set (&hsv, angle / (2.0 * G_PI), distance, 1 - sqrt (distance) / 4 /* it just looks nicer this way */); pika_hsv_to_rgb (&hsv, &color); pika_rgb_get_uchar (&color, rgb, rgb + 1, rgb + 2); } static void pika_circle_draw_background (PikaCircle *circle, cairo_t *cr, gint size, PikaCircleBackground background) { cairo_save (cr); if (background == PIKA_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 PIKA_CIRCLE_BACKGROUND_HSV: pika_circle_background_hsv (angle, distance, rgb); break; default: break; } PIKA_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); }