1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpcircle.c
5 * Copyright (C) 2014 Michael Natterer <mitch@gimp.org>
6 *
7 * Based on code from the color-rotate plug-in
8 * Copyright (C) 1997-1999 Sven Anders (anderss@fmi.uni-passau.de)
9 * Based on code from Pavel Grinfeld (pavel@ml.com)
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25 #include "config.h"
26
27 #include <gegl.h>
28 #include <gtk/gtk.h>
29
30 #include "libgimpmath/gimpmath.h"
31 #include "libgimpcolor/gimpcolor.h"
32 #include "libgimpwidgets/gimpwidgets.h"
33
34 #include "widgets-types.h"
35
36 #include "gimpcircle.h"
37
38
39 enum
40 {
41 PROP_0,
42 PROP_SIZE,
43 PROP_BORDER_WIDTH,
44 PROP_BACKGROUND
45 };
46
47
48 struct _GimpCirclePrivate
49 {
50 gint size;
51 gint border_width;
52 GimpCircleBackground background;
53
54 GdkWindow *event_window;
55 cairo_surface_t *surface;
56 gboolean has_grab;
57 gboolean in_widget;
58 };
59
60
61 static void gimp_circle_dispose (GObject *object);
62 static void gimp_circle_set_property (GObject *object,
63 guint property_id,
64 const GValue *value,
65 GParamSpec *pspec);
66 static void gimp_circle_get_property (GObject *object,
67 guint property_id,
68 GValue *value,
69 GParamSpec *pspec);
70
71 static void gimp_circle_realize (GtkWidget *widget);
72 static void gimp_circle_unrealize (GtkWidget *widget);
73 static void gimp_circle_map (GtkWidget *widget);
74 static void gimp_circle_unmap (GtkWidget *widget);
75 static void gimp_circle_size_request (GtkWidget *widget,
76 GtkRequisition *requisition);
77 static void gimp_circle_size_allocate (GtkWidget *widget,
78 GtkAllocation *allocation);
79 static gboolean gimp_circle_expose_event (GtkWidget *widget,
80 GdkEventExpose *event);
81 static gboolean gimp_circle_button_press_event (GtkWidget *widget,
82 GdkEventButton *bevent);
83 static gboolean gimp_circle_button_release_event (GtkWidget *widget,
84 GdkEventButton *bevent);
85 static gboolean gimp_circle_enter_notify_event (GtkWidget *widget,
86 GdkEventCrossing *event);
87 static gboolean gimp_circle_leave_notify_event (GtkWidget *widget,
88 GdkEventCrossing *event);
89
90 static void gimp_circle_real_reset_target (GimpCircle *circle);
91
92 static void gimp_circle_background_hsv (gdouble angle,
93 gdouble distance,
94 guchar *rgb);
95
96 static void gimp_circle_draw_background (GimpCircle *circle,
97 cairo_t *cr,
98 gint size,
99 GimpCircleBackground background);
100
101
G_DEFINE_TYPE_WITH_PRIVATE(GimpCircle,gimp_circle,GTK_TYPE_WIDGET)102 G_DEFINE_TYPE_WITH_PRIVATE (GimpCircle, gimp_circle, GTK_TYPE_WIDGET)
103
104 #define parent_class gimp_circle_parent_class
105
106
107 static void
108 gimp_circle_class_init (GimpCircleClass *klass)
109 {
110 GObjectClass *object_class = G_OBJECT_CLASS (klass);
111 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
112
113 object_class->dispose = gimp_circle_dispose;
114 object_class->get_property = gimp_circle_get_property;
115 object_class->set_property = gimp_circle_set_property;
116
117 widget_class->realize = gimp_circle_realize;
118 widget_class->unrealize = gimp_circle_unrealize;
119 widget_class->map = gimp_circle_map;
120 widget_class->unmap = gimp_circle_unmap;
121 widget_class->size_request = gimp_circle_size_request;
122 widget_class->size_allocate = gimp_circle_size_allocate;
123 widget_class->expose_event = gimp_circle_expose_event;
124 widget_class->button_press_event = gimp_circle_button_press_event;
125 widget_class->button_release_event = gimp_circle_button_release_event;
126 widget_class->enter_notify_event = gimp_circle_enter_notify_event;
127 widget_class->leave_notify_event = gimp_circle_leave_notify_event;
128
129 klass->reset_target = gimp_circle_real_reset_target;
130
131 g_object_class_install_property (object_class, PROP_SIZE,
132 g_param_spec_int ("size",
133 NULL, NULL,
134 32, 1024, 96,
135 GIMP_PARAM_READWRITE |
136 G_PARAM_CONSTRUCT));
137
138 g_object_class_install_property (object_class, PROP_BORDER_WIDTH,
139 g_param_spec_int ("border-width",
140 NULL, NULL,
141 0, 64, 0,
142 GIMP_PARAM_READWRITE |
143 G_PARAM_CONSTRUCT));
144
145 g_object_class_install_property (object_class, PROP_BACKGROUND,
146 g_param_spec_enum ("background",
147 NULL, NULL,
148 GIMP_TYPE_CIRCLE_BACKGROUND,
149 GIMP_CIRCLE_BACKGROUND_HSV,
150 GIMP_PARAM_READWRITE |
151 G_PARAM_CONSTRUCT));
152 }
153
154 static void
gimp_circle_init(GimpCircle * circle)155 gimp_circle_init (GimpCircle *circle)
156 {
157 circle->priv = gimp_circle_get_instance_private (circle);
158
159 gtk_widget_set_has_window (GTK_WIDGET (circle), FALSE);
160 gtk_widget_add_events (GTK_WIDGET (circle),
161 GDK_POINTER_MOTION_MASK |
162 GDK_BUTTON_PRESS_MASK |
163 GDK_BUTTON_RELEASE_MASK |
164 GDK_BUTTON1_MOTION_MASK |
165 GDK_ENTER_NOTIFY_MASK |
166 GDK_LEAVE_NOTIFY_MASK);
167 }
168
169 static void
gimp_circle_dispose(GObject * object)170 gimp_circle_dispose (GObject *object)
171 {
172 GimpCircle *circle = GIMP_CIRCLE (object);
173
174 g_clear_pointer (&circle->priv->surface, cairo_surface_destroy);
175
176 G_OBJECT_CLASS (parent_class)->dispose (object);
177 }
178
179 static void
gimp_circle_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)180 gimp_circle_set_property (GObject *object,
181 guint property_id,
182 const GValue *value,
183 GParamSpec *pspec)
184 {
185 GimpCircle *circle = GIMP_CIRCLE (object);
186
187 switch (property_id)
188 {
189 case PROP_SIZE:
190 circle->priv->size = g_value_get_int (value);
191 gtk_widget_queue_resize (GTK_WIDGET (circle));
192 break;
193
194 case PROP_BORDER_WIDTH:
195 circle->priv->border_width = g_value_get_int (value);
196 gtk_widget_queue_resize (GTK_WIDGET (circle));
197 break;
198
199 case PROP_BACKGROUND:
200 circle->priv->background = g_value_get_enum (value);
201 g_clear_pointer (&circle->priv->surface, cairo_surface_destroy);
202 gtk_widget_queue_draw (GTK_WIDGET (circle));
203 break;
204
205 default:
206 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
207 break;
208 }
209 }
210
211 static void
gimp_circle_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)212 gimp_circle_get_property (GObject *object,
213 guint property_id,
214 GValue *value,
215 GParamSpec *pspec)
216 {
217 GimpCircle *circle = GIMP_CIRCLE (object);
218
219 switch (property_id)
220 {
221 case PROP_SIZE:
222 g_value_set_int (value, circle->priv->size);
223 break;
224
225 case PROP_BORDER_WIDTH:
226 g_value_set_int (value, circle->priv->border_width);
227 break;
228
229 case PROP_BACKGROUND:
230 g_value_set_enum (value, circle->priv->background);
231 break;
232
233 default:
234 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
235 break;
236 }
237 }
238
239 static void
gimp_circle_realize(GtkWidget * widget)240 gimp_circle_realize (GtkWidget *widget)
241 {
242 GimpCircle *circle = GIMP_CIRCLE (widget);
243 GtkAllocation allocation;
244 GdkWindowAttr attributes;
245 gint attributes_mask;
246
247 GTK_WIDGET_CLASS (parent_class)->realize (widget);
248
249 gtk_widget_get_allocation (widget, &allocation);
250
251 attributes.window_type = GDK_WINDOW_CHILD;
252 attributes.x = allocation.x;
253 attributes.y = allocation.y;
254 attributes.width = allocation.width;
255 attributes.height = allocation.height;
256 attributes.wclass = GDK_INPUT_ONLY;
257 attributes.event_mask = gtk_widget_get_events (widget);
258
259 attributes_mask = GDK_WA_X | GDK_WA_Y;
260
261 circle->priv->event_window = gdk_window_new (gtk_widget_get_window (widget),
262 &attributes, attributes_mask);
263 gdk_window_set_user_data (circle->priv->event_window, circle);
264 }
265
266 static void
gimp_circle_unrealize(GtkWidget * widget)267 gimp_circle_unrealize (GtkWidget *widget)
268 {
269 GimpCircle *circle = GIMP_CIRCLE (widget);
270
271 if (circle->priv->event_window)
272 {
273 gdk_window_set_user_data (circle->priv->event_window, NULL);
274 gdk_window_destroy (circle->priv->event_window);
275 circle->priv->event_window = NULL;
276 }
277
278 GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
279 }
280
281 static void
gimp_circle_map(GtkWidget * widget)282 gimp_circle_map (GtkWidget *widget)
283 {
284 GimpCircle *circle = GIMP_CIRCLE (widget);
285
286 GTK_WIDGET_CLASS (parent_class)->map (widget);
287
288 if (circle->priv->event_window)
289 gdk_window_show (circle->priv->event_window);
290 }
291
292 static void
gimp_circle_unmap(GtkWidget * widget)293 gimp_circle_unmap (GtkWidget *widget)
294 {
295 GimpCircle *circle = GIMP_CIRCLE (widget);
296
297 if (circle->priv->has_grab)
298 {
299 gtk_grab_remove (widget);
300 circle->priv->has_grab = FALSE;
301 }
302
303 if (circle->priv->event_window)
304 gdk_window_hide (circle->priv->event_window);
305
306 GTK_WIDGET_CLASS (parent_class)->unmap (widget);
307 }
308
309 static void
gimp_circle_size_request(GtkWidget * widget,GtkRequisition * requisition)310 gimp_circle_size_request (GtkWidget *widget,
311 GtkRequisition *requisition)
312 {
313 GimpCircle *circle = GIMP_CIRCLE (widget);
314
315 requisition->width = 2 * circle->priv->border_width + circle->priv->size;
316 requisition->height = 2 * circle->priv->border_width + circle->priv->size;
317 }
318
319 static void
gimp_circle_size_allocate(GtkWidget * widget,GtkAllocation * allocation)320 gimp_circle_size_allocate (GtkWidget *widget,
321 GtkAllocation *allocation)
322 {
323 GimpCircle *circle = GIMP_CIRCLE (widget);
324
325 GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
326
327 if (gtk_widget_get_realized (widget))
328 gdk_window_move_resize (circle->priv->event_window,
329 allocation->x,
330 allocation->y,
331 allocation->width,
332 allocation->height);
333
334 g_clear_pointer (&circle->priv->surface, cairo_surface_destroy);
335 }
336
337 static gboolean
gimp_circle_expose_event(GtkWidget * widget,GdkEventExpose * event)338 gimp_circle_expose_event (GtkWidget *widget,
339 GdkEventExpose *event)
340 {
341 GimpCircle *circle = GIMP_CIRCLE (widget);
342
343 if (gtk_widget_is_drawable (widget))
344 {
345 GtkAllocation allocation;
346 gint size = circle->priv->size;
347 cairo_t *cr;
348
349 cr = gdk_cairo_create (event->window);
350 gdk_cairo_region (cr, event->region);
351 cairo_clip (cr);
352
353 gtk_widget_get_allocation (widget, &allocation);
354
355 cairo_translate (cr,
356 allocation.x + (allocation.width - size) / 2,
357 allocation.y + (allocation.height - size) / 2);
358
359 gimp_circle_draw_background (circle, cr, size, circle->priv->background);
360
361 cairo_destroy (cr);
362 }
363
364 return FALSE;
365 }
366
367 static gboolean
gimp_circle_button_press_event(GtkWidget * widget,GdkEventButton * bevent)368 gimp_circle_button_press_event (GtkWidget *widget,
369 GdkEventButton *bevent)
370 {
371 GimpCircle *circle = GIMP_CIRCLE (widget);
372
373 if (bevent->type == GDK_BUTTON_PRESS &&
374 bevent->button == 1)
375 {
376 gtk_grab_add (widget);
377 circle->priv->has_grab = TRUE;
378 }
379
380 return FALSE;
381 }
382
383 static gboolean
gimp_circle_button_release_event(GtkWidget * widget,GdkEventButton * bevent)384 gimp_circle_button_release_event (GtkWidget *widget,
385 GdkEventButton *bevent)
386 {
387 GimpCircle *circle = GIMP_CIRCLE (widget);
388
389 if (bevent->button == 1)
390 {
391 gtk_grab_remove (widget);
392 circle->priv->has_grab = FALSE;
393
394 if (! circle->priv->in_widget)
395 GIMP_CIRCLE_GET_CLASS (circle)->reset_target (circle);
396 }
397
398 return FALSE;
399 }
400
401 static gboolean
gimp_circle_enter_notify_event(GtkWidget * widget,GdkEventCrossing * event)402 gimp_circle_enter_notify_event (GtkWidget *widget,
403 GdkEventCrossing *event)
404 {
405 GimpCircle *circle = GIMP_CIRCLE (widget);
406
407 circle->priv->in_widget = TRUE;
408
409 return FALSE;
410 }
411
412 static gboolean
gimp_circle_leave_notify_event(GtkWidget * widget,GdkEventCrossing * event)413 gimp_circle_leave_notify_event (GtkWidget *widget,
414 GdkEventCrossing *event)
415 {
416 GimpCircle *circle = GIMP_CIRCLE (widget);
417
418 circle->priv->in_widget = FALSE;
419
420 if (! circle->priv->has_grab)
421 GIMP_CIRCLE_GET_CLASS (circle)->reset_target (circle);
422
423 return FALSE;
424 }
425
426 static void
gimp_circle_real_reset_target(GimpCircle * circle)427 gimp_circle_real_reset_target (GimpCircle *circle)
428 {
429 }
430
431
432 /* public functions */
433
434 GtkWidget *
gimp_circle_new(void)435 gimp_circle_new (void)
436 {
437 return g_object_new (GIMP_TYPE_CIRCLE, NULL);
438 }
439
440
441 /* protected functions */
442
443 static gdouble
get_angle_and_distance(gdouble center_x,gdouble center_y,gdouble radius,gdouble x,gdouble y,gdouble * distance)444 get_angle_and_distance (gdouble center_x,
445 gdouble center_y,
446 gdouble radius,
447 gdouble x,
448 gdouble y,
449 gdouble *distance)
450 {
451 gdouble angle = atan2 (center_y - y,
452 x - center_x);
453
454 if (angle < 0)
455 angle += 2 * G_PI;
456
457 if (distance)
458 *distance = sqrt ((SQR (x - center_x) +
459 SQR (y - center_y)) / SQR (radius));
460
461 return angle;
462 }
463
464 gboolean
_gimp_circle_has_grab(GimpCircle * circle)465 _gimp_circle_has_grab (GimpCircle *circle)
466 {
467 g_return_val_if_fail (GIMP_IS_CIRCLE (circle), FALSE);
468
469 return circle->priv->has_grab;
470 }
471
472 gdouble
_gimp_circle_get_angle_and_distance(GimpCircle * circle,gdouble event_x,gdouble event_y,gdouble * distance)473 _gimp_circle_get_angle_and_distance (GimpCircle *circle,
474 gdouble event_x,
475 gdouble event_y,
476 gdouble *distance)
477 {
478 GtkAllocation allocation;
479 gdouble center_x;
480 gdouble center_y;
481
482 g_return_val_if_fail (GIMP_IS_CIRCLE (circle), 0.0);
483
484 gtk_widget_get_allocation (GTK_WIDGET (circle), &allocation);
485
486 center_x = allocation.width / 2.0;
487 center_y = allocation.height / 2.0;
488
489 return get_angle_and_distance (center_x, center_y, circle->priv->size / 2.0,
490 event_x, event_y,
491 distance);
492 }
493
494
495 /* private functions */
496
497 static void
gimp_circle_background_hsv(gdouble angle,gdouble distance,guchar * rgb)498 gimp_circle_background_hsv (gdouble angle,
499 gdouble distance,
500 guchar *rgb)
501 {
502 GimpHSV hsv;
503 GimpRGB color;
504
505 gimp_hsv_set (&hsv,
506 angle / (2.0 * G_PI),
507 distance,
508 1 - sqrt (distance) / 4 /* it just looks nicer this way */);
509
510 gimp_hsv_to_rgb (&hsv, &color);
511
512 gimp_rgb_get_uchar (&color, rgb, rgb + 1, rgb + 2);
513 }
514
515 static void
gimp_circle_draw_background(GimpCircle * circle,cairo_t * cr,gint size,GimpCircleBackground background)516 gimp_circle_draw_background (GimpCircle *circle,
517 cairo_t *cr,
518 gint size,
519 GimpCircleBackground background)
520 {
521 cairo_save (cr);
522
523 if (background == GIMP_CIRCLE_BACKGROUND_PLAIN)
524 {
525 cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0 - 1.5, 0.0, 2 * G_PI);
526
527 cairo_set_line_width (cr, 3.0);
528 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6);
529 cairo_stroke_preserve (cr);
530
531 cairo_set_line_width (cr, 1.0);
532 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8);
533 cairo_stroke (cr);
534 }
535 else
536 {
537 if (! circle->priv->surface)
538 {
539 guchar *data;
540 gint stride;
541 gint x, y;
542
543 circle->priv->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
544 size, size);
545
546 data = cairo_image_surface_get_data (circle->priv->surface);
547 stride = cairo_image_surface_get_stride (circle->priv->surface);
548
549 for (y = 0; y < size; y++)
550 {
551 for (x = 0; x < size; x++)
552 {
553 gdouble angle;
554 gdouble distance;
555 guchar rgb[3] = { 0, };
556
557 angle = get_angle_and_distance (size / 2.0, size / 2.0,
558 size / 2.0,
559 x, y,
560 &distance);
561
562 switch (background)
563 {
564 case GIMP_CIRCLE_BACKGROUND_HSV:
565 gimp_circle_background_hsv (angle, distance, rgb);
566 break;
567
568 default:
569 break;
570 }
571
572 GIMP_CAIRO_ARGB32_SET_PIXEL (data + y * stride + x * 4,
573 rgb[0], rgb[1], rgb[2], 255);
574 }
575 }
576
577 cairo_surface_mark_dirty (circle->priv->surface);
578 }
579
580 cairo_set_source_surface (cr, circle->priv->surface, 0.0, 0.0);
581
582 cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0, 0.0, 2 * G_PI);
583 cairo_clip (cr);
584
585 cairo_paint (cr);
586 }
587
588 cairo_restore (cr);
589 }
590