1 /* HSV color selector for GTK+
2  *
3  * Copyright (C) 1999 The Free Software Foundation
4  *
5  * Authors: Simon Budig <Simon.Budig@unix-ag.org> (original code)
6  *          Federico Mena-Quintero <federico@gimp.org> (cleanup for GTK+)
7  *          Jonathan Blandford <jrb@redhat.com> (cleanup for GTK+)
8  *          Michael Natterer <mitch@gimp.org> (ported back to GIMP)
9  *
10  * This program is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
22  */
23 
24 /*
25  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
26  * file for a list of people on the GTK+ Team.  See the ChangeLog
27  * files for a list of changes.  These files are distributed with
28  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
29  */
30 
31 #include "config.h"
32 
33 #include <gegl.h>
34 
35 #include <gtk/gtk.h>
36 #include <gdk/gdkkeysyms.h>
37 
38 #include <libgimpconfig/gimpconfig.h>
39 #include <libgimpcolor/gimpcolor.h>
40 #include <libgimpmath/gimpmath.h>
41 #include <libgimpwidgets/gimpwidgets.h>
42 
43 #include "gimpcolorwheel.h"
44 
45 
46 /* Default ring fraction */
47 #define DEFAULT_FRACTION 0.1
48 
49 /* Default width/height */
50 #define DEFAULT_SIZE 100
51 
52 /* Default ring width */
53 #define DEFAULT_RING_WIDTH 10
54 
55 
56 /* Dragging modes */
57 typedef enum
58 {
59   DRAG_NONE,
60   DRAG_H,
61   DRAG_SV
62 } DragMode;
63 
64 /* Private part of the GimpColorWheel structure */
65 typedef struct
66 {
67   /* Color value */
68   gdouble h;
69   gdouble s;
70   gdouble v;
71 
72   /* ring_width is this fraction of size */
73   gdouble ring_fraction;
74 
75   /* Size and ring width */
76   gint size;
77   gint ring_width;
78 
79   /* Window for capturing events */
80   GdkWindow *window;
81 
82   /* Dragging mode */
83   DragMode mode;
84 
85   guint focus_on_ring : 1;
86 
87   GimpColorConfig    *config;
88   GimpColorTransform *transform;
89 } GimpColorWheelPrivate;
90 
91 enum
92 {
93   CHANGED,
94   MOVE,
95   LAST_SIGNAL
96 };
97 
98 static void     gimp_color_wheel_dispose           (GObject            *object);
99 
100 static void     gimp_color_wheel_map               (GtkWidget          *widget);
101 static void     gimp_color_wheel_unmap             (GtkWidget          *widget);
102 static void     gimp_color_wheel_realize           (GtkWidget          *widget);
103 static void     gimp_color_wheel_unrealize         (GtkWidget          *widget);
104 static void     gimp_color_wheel_size_request      (GtkWidget          *widget,
105                                                     GtkRequisition     *requisition);
106 static void     gimp_color_wheel_size_allocate     (GtkWidget          *widget,
107                                                     GtkAllocation      *allocation);
108 static gboolean gimp_color_wheel_button_press      (GtkWidget          *widget,
109                                                     GdkEventButton     *event);
110 static gboolean gimp_color_wheel_button_release    (GtkWidget          *widget,
111                                                     GdkEventButton     *event);
112 static gboolean gimp_color_wheel_motion            (GtkWidget          *widget,
113                                                     GdkEventMotion     *event);
114 static gboolean gimp_color_wheel_expose            (GtkWidget          *widget,
115                                                     GdkEventExpose     *event);
116 static gboolean gimp_color_wheel_grab_broken       (GtkWidget          *widget,
117                                                     GdkEventGrabBroken *event);
118 static gboolean gimp_color_wheel_focus             (GtkWidget          *widget,
119                                                     GtkDirectionType    direction);
120 static void     gimp_color_wheel_move              (GimpColorWheel     *wheel,
121                                                     GtkDirectionType    dir);
122 
123 static void     gimp_color_wheel_create_transform  (GimpColorWheel     *wheel);
124 static void     gimp_color_wheel_destroy_transform (GimpColorWheel     *wheel);
125 
126 
127 static guint wheel_signals[LAST_SIGNAL];
128 
129 G_DEFINE_DYNAMIC_TYPE_EXTENDED (GimpColorWheel, gimp_color_wheel,
130                                 GTK_TYPE_WIDGET, 0,
131                                 G_ADD_PRIVATE_DYNAMIC (GimpColorWheel))
132 
133 #define parent_class gimp_color_wheel_parent_class
134 
135 
136 void
color_wheel_register_type(GTypeModule * module)137 color_wheel_register_type (GTypeModule *module)
138 {
139   gimp_color_wheel_register_type (module);
140 }
141 
142 static void
gimp_color_wheel_class_init(GimpColorWheelClass * class)143 gimp_color_wheel_class_init (GimpColorWheelClass *class)
144 {
145   GObjectClass        *object_class = G_OBJECT_CLASS (class);
146   GtkWidgetClass      *widget_class = GTK_WIDGET_CLASS (class);
147   GimpColorWheelClass *wheel_class  = GIMP_COLOR_WHEEL_CLASS (class);
148   GtkBindingSet       *binding_set;
149 
150   object_class->dispose              = gimp_color_wheel_dispose;
151 
152   widget_class->map                  = gimp_color_wheel_map;
153   widget_class->unmap                = gimp_color_wheel_unmap;
154   widget_class->realize              = gimp_color_wheel_realize;
155   widget_class->unrealize            = gimp_color_wheel_unrealize;
156   widget_class->size_request         = gimp_color_wheel_size_request;
157   widget_class->size_allocate        = gimp_color_wheel_size_allocate;
158   widget_class->button_press_event   = gimp_color_wheel_button_press;
159   widget_class->button_release_event = gimp_color_wheel_button_release;
160   widget_class->motion_notify_event  = gimp_color_wheel_motion;
161   widget_class->expose_event         = gimp_color_wheel_expose;
162   widget_class->focus                = gimp_color_wheel_focus;
163   widget_class->grab_broken_event    = gimp_color_wheel_grab_broken;
164 
165   wheel_class->move                  = gimp_color_wheel_move;
166 
167   wheel_signals[CHANGED] =
168     g_signal_new ("changed",
169                   G_OBJECT_CLASS_TYPE (object_class),
170                   G_SIGNAL_RUN_FIRST,
171                   G_STRUCT_OFFSET (GimpColorWheelClass, changed),
172                   NULL, NULL,
173                   g_cclosure_marshal_VOID__VOID,
174                   G_TYPE_NONE, 0);
175 
176   wheel_signals[MOVE] =
177     g_signal_new ("move",
178                   G_OBJECT_CLASS_TYPE (object_class),
179                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
180                   G_STRUCT_OFFSET (GimpColorWheelClass, move),
181                   NULL, NULL,
182                   g_cclosure_marshal_VOID__ENUM,
183                   G_TYPE_NONE, 1,
184                   GTK_TYPE_DIRECTION_TYPE);
185 
186   binding_set = gtk_binding_set_by_class (class);
187 
188   gtk_binding_entry_add_signal (binding_set, GDK_Up, 0,
189                                 "move", 1,
190                                 G_TYPE_ENUM, GTK_DIR_UP);
191   gtk_binding_entry_add_signal (binding_set, GDK_KP_Up, 0,
192                                 "move", 1,
193                                 G_TYPE_ENUM, GTK_DIR_UP);
194 
195   gtk_binding_entry_add_signal (binding_set, GDK_Down, 0,
196                                 "move", 1,
197                                 G_TYPE_ENUM, GTK_DIR_DOWN);
198   gtk_binding_entry_add_signal (binding_set, GDK_KP_Down, 0,
199                                 "move", 1,
200                                 G_TYPE_ENUM, GTK_DIR_DOWN);
201 
202 
203   gtk_binding_entry_add_signal (binding_set, GDK_Right, 0,
204                                 "move", 1,
205                                 G_TYPE_ENUM, GTK_DIR_RIGHT);
206   gtk_binding_entry_add_signal (binding_set, GDK_KP_Right, 0,
207                                 "move", 1,
208                                 G_TYPE_ENUM, GTK_DIR_RIGHT);
209 
210   gtk_binding_entry_add_signal (binding_set, GDK_Left, 0,
211                                 "move", 1,
212                                 G_TYPE_ENUM, GTK_DIR_LEFT);
213   gtk_binding_entry_add_signal (binding_set, GDK_KP_Left, 0,
214                                 "move", 1,
215                                 G_TYPE_ENUM, GTK_DIR_LEFT);
216 }
217 
218 static void
gimp_color_wheel_class_finalize(GimpColorWheelClass * klass)219 gimp_color_wheel_class_finalize (GimpColorWheelClass *klass)
220 {
221 }
222 
223 static void
gimp_color_wheel_init(GimpColorWheel * wheel)224 gimp_color_wheel_init (GimpColorWheel *wheel)
225 {
226   GimpColorWheelPrivate *priv = gimp_color_wheel_get_instance_private (wheel);
227 
228   wheel->priv = priv;
229 
230   gtk_widget_set_has_window (GTK_WIDGET (wheel), FALSE);
231   gtk_widget_set_can_focus (GTK_WIDGET (wheel), TRUE);
232 
233   priv->ring_fraction = DEFAULT_FRACTION;
234   priv->size          = DEFAULT_SIZE;
235   priv->ring_width    = DEFAULT_RING_WIDTH;
236 
237   gimp_widget_track_monitor (GTK_WIDGET (wheel),
238                              G_CALLBACK (gimp_color_wheel_destroy_transform),
239                              NULL);
240 }
241 
242 static void
gimp_color_wheel_dispose(GObject * object)243 gimp_color_wheel_dispose (GObject *object)
244 {
245   GimpColorWheel *wheel = GIMP_COLOR_WHEEL (object);
246 
247   gimp_color_wheel_set_color_config (wheel, NULL);
248 
249   G_OBJECT_CLASS (parent_class)->dispose (object);
250 }
251 
252 static void
gimp_color_wheel_map(GtkWidget * widget)253 gimp_color_wheel_map (GtkWidget *widget)
254 {
255   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
256   GimpColorWheelPrivate *priv  = wheel->priv;
257 
258   GTK_WIDGET_CLASS (parent_class)->map (widget);
259 
260   gdk_window_show (priv->window);
261 }
262 
263 static void
gimp_color_wheel_unmap(GtkWidget * widget)264 gimp_color_wheel_unmap (GtkWidget *widget)
265 {
266   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
267   GimpColorWheelPrivate *priv  = wheel->priv;
268 
269   gdk_window_hide (priv->window);
270 
271   GTK_WIDGET_CLASS (parent_class)->unmap (widget);
272 }
273 
274 static void
gimp_color_wheel_realize(GtkWidget * widget)275 gimp_color_wheel_realize (GtkWidget *widget)
276 {
277   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
278   GimpColorWheelPrivate *priv  = wheel->priv;
279   GtkAllocation          allocation;
280   GdkWindowAttr          attr;
281   gint                   attr_mask;
282   GdkWindow             *parent_window;
283 
284   gtk_widget_get_allocation (widget, &allocation);
285 
286   gtk_widget_set_realized (widget, TRUE);
287 
288   attr.window_type = GDK_WINDOW_CHILD;
289   attr.x           = allocation.x;
290   attr.y           = allocation.y;
291   attr.width       = allocation.width;
292   attr.height      = allocation.height;
293   attr.wclass      = GDK_INPUT_ONLY;
294   attr.event_mask  = (gtk_widget_get_events (widget) |
295                       GDK_KEY_PRESS_MASK      |
296                       GDK_BUTTON_PRESS_MASK   |
297                       GDK_BUTTON_RELEASE_MASK |
298                       GDK_POINTER_MOTION_MASK |
299                       GDK_ENTER_NOTIFY_MASK   |
300                       GDK_LEAVE_NOTIFY_MASK);
301 
302   attr_mask = GDK_WA_X | GDK_WA_Y;
303 
304   parent_window = gtk_widget_get_parent_window (widget);
305 
306   gtk_widget_set_window (widget, parent_window);
307   g_object_ref (parent_window);
308 
309   priv->window = gdk_window_new (parent_window, &attr, attr_mask);
310   gdk_window_set_user_data (priv->window, wheel);
311 
312   gtk_widget_style_attach (widget);
313 }
314 
315 static void
gimp_color_wheel_unrealize(GtkWidget * widget)316 gimp_color_wheel_unrealize (GtkWidget *widget)
317 {
318   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
319   GimpColorWheelPrivate *priv  = wheel->priv;
320 
321   gdk_window_set_user_data (priv->window, NULL);
322   gdk_window_destroy (priv->window);
323   priv->window = NULL;
324 
325   GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
326 }
327 
328 static void
gimp_color_wheel_size_request(GtkWidget * widget,GtkRequisition * requisition)329 gimp_color_wheel_size_request (GtkWidget      *widget,
330                                GtkRequisition *requisition)
331 {
332   gint focus_width;
333   gint focus_pad;
334 
335   gtk_widget_style_get (widget,
336                         "focus-line-width", &focus_width,
337                         "focus-padding", &focus_pad,
338                         NULL);
339 
340   requisition->width  = DEFAULT_SIZE + 2 * (focus_width + focus_pad);
341   requisition->height = DEFAULT_SIZE + 2 * (focus_width + focus_pad);
342 }
343 
344 static void
gimp_color_wheel_size_allocate(GtkWidget * widget,GtkAllocation * allocation)345 gimp_color_wheel_size_allocate (GtkWidget     *widget,
346                                 GtkAllocation *allocation)
347 {
348   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
349   GimpColorWheelPrivate *priv  = wheel->priv;
350   gint                   focus_width;
351   gint                   focus_pad;
352 
353   GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
354 
355   gtk_widget_style_get (widget,
356                         "focus-line-width", &focus_width,
357                         "focus-padding",    &focus_pad,
358                         NULL);
359 
360   priv->size = MIN (allocation->width  - 2 * (focus_width + focus_pad),
361                     allocation->height - 2 * (focus_width + focus_pad));
362 
363   priv->ring_width = priv->size * priv->ring_fraction;
364 
365   if (gtk_widget_get_realized (widget))
366     gdk_window_move_resize (priv->window,
367                             allocation->x,
368                             allocation->y,
369                             allocation->width,
370                             allocation->height);
371 }
372 
373 
374 /* Utility functions */
375 
376 /* Converts from HSV to RGB */
377 static void
hsv_to_rgb(gdouble * h,gdouble * s,gdouble * v)378 hsv_to_rgb (gdouble *h,
379             gdouble *s,
380             gdouble *v)
381 {
382   gdouble hue, saturation, value;
383   gdouble f, p, q, t;
384 
385   if (*s == 0.0)
386     {
387       *h = *v;
388       *s = *v;
389    /* *v = *v; -- heh */
390     }
391   else
392     {
393       hue = *h * 6.0;
394       saturation = *s;
395       value = *v;
396 
397       if (hue == 6.0)
398         hue = 0.0;
399 
400       f = hue - (int) hue;
401       p = value * (1.0 - saturation);
402       q = value * (1.0 - saturation * f);
403       t = value * (1.0 - saturation * (1.0 - f));
404 
405       switch ((int) hue)
406         {
407         case 0:
408           *h = value;
409           *s = t;
410           *v = p;
411           break;
412 
413         case 1:
414           *h = q;
415           *s = value;
416           *v = p;
417           break;
418 
419         case 2:
420           *h = p;
421           *s = value;
422           *v = t;
423           break;
424 
425         case 3:
426           *h = p;
427           *s = q;
428           *v = value;
429           break;
430 
431         case 4:
432           *h = t;
433           *s = p;
434           *v = value;
435           break;
436 
437         case 5:
438           *h = value;
439           *s = p;
440           *v = q;
441           break;
442 
443         default:
444           g_assert_not_reached ();
445         }
446     }
447 }
448 
449 /* Computes the vertices of the saturation/value triangle */
450 static void
compute_triangle(GimpColorWheel * wheel,gint * hx,gint * hy,gint * sx,gint * sy,gint * vx,gint * vy)451 compute_triangle (GimpColorWheel *wheel,
452                   gint           *hx,
453                   gint           *hy,
454                   gint           *sx,
455                   gint           *sy,
456                   gint           *vx,
457                   gint           *vy)
458 {
459   GimpColorWheelPrivate *priv = wheel->priv;
460   GtkAllocation          allocation;
461   gdouble                center_x;
462   gdouble                center_y;
463   gdouble                inner, outer;
464   gdouble                angle;
465 
466   gtk_widget_get_allocation (GTK_WIDGET (wheel), &allocation);
467 
468   center_x = allocation.width / 2.0;
469   center_y = allocation.height / 2.0;
470 
471   outer = priv->size / 2.0;
472   inner = outer - priv->ring_width;
473   angle = priv->h * 2.0 * G_PI;
474 
475   *hx = floor (center_x + cos (angle) * inner + 0.5);
476   *hy = floor (center_y - sin (angle) * inner + 0.5);
477   *sx = floor (center_x + cos (angle + 2.0 * G_PI / 3.0) * inner + 0.5);
478   *sy = floor (center_y - sin (angle + 2.0 * G_PI / 3.0) * inner + 0.5);
479   *vx = floor (center_x + cos (angle + 4.0 * G_PI / 3.0) * inner + 0.5);
480   *vy = floor (center_y - sin (angle + 4.0 * G_PI / 3.0) * inner + 0.5);
481 }
482 
483 /* Computes whether a point is inside the hue ring */
484 static gboolean
is_in_ring(GimpColorWheel * wheel,gdouble x,gdouble y)485 is_in_ring (GimpColorWheel *wheel,
486             gdouble         x,
487             gdouble         y)
488 {
489   GimpColorWheelPrivate *priv = wheel->priv;
490   GtkAllocation          allocation;
491   gdouble                dx, dy, dist;
492   gdouble                center_x;
493   gdouble                center_y;
494   gdouble                inner, outer;
495 
496   gtk_widget_get_allocation (GTK_WIDGET (wheel), &allocation);
497 
498   center_x = allocation.width / 2.0;
499   center_y = allocation.height / 2.0;
500 
501   outer = priv->size / 2.0;
502   inner = outer - priv->ring_width;
503 
504   dx = x - center_x;
505   dy = center_y - y;
506   dist = dx * dx + dy * dy;
507 
508   return (dist >= inner * inner && dist <= outer * outer);
509 }
510 
511 /* Computes a saturation/value pair based on the mouse coordinates */
512 static void
compute_sv(GimpColorWheel * wheel,gdouble x,gdouble y,gdouble * s,gdouble * v)513 compute_sv (GimpColorWheel *wheel,
514             gdouble         x,
515             gdouble         y,
516             gdouble        *s,
517             gdouble        *v)
518 {
519   GtkAllocation allocation;
520   gint          ihx, ihy, isx, isy, ivx, ivy;
521   gdouble       hx, hy, sx, sy, vx, vy;
522   gdouble       center_x;
523   gdouble       center_y;
524 
525   gtk_widget_get_allocation (GTK_WIDGET (wheel), &allocation);
526 
527   compute_triangle (wheel, &ihx, &ihy, &isx, &isy, &ivx, &ivy);
528 
529   center_x = allocation.width / 2.0;
530   center_y = allocation.height / 2.0;
531 
532   hx = ihx - center_x;
533   hy = center_y - ihy;
534   sx = isx - center_x;
535   sy = center_y - isy;
536   vx = ivx - center_x;
537   vy = center_y - ivy;
538   x -= center_x;
539   y = center_y - y;
540 
541   if (vx * (x - sx) + vy * (y - sy) < 0.0)
542     {
543       *s = 1.0;
544       *v = (((x - sx) * (hx - sx) + (y - sy) * (hy-sy))
545             / ((hx - sx) * (hx - sx) + (hy - sy) * (hy - sy)));
546 
547       if (*v < 0.0)
548         *v = 0.0;
549       else if (*v > 1.0)
550         *v = 1.0;
551     }
552   else if (hx * (x - sx) + hy * (y - sy) < 0.0)
553     {
554       *s = 0.0;
555       *v = (((x - sx) * (vx - sx) + (y - sy) * (vy - sy))
556             / ((vx - sx) * (vx - sx) + (vy - sy) * (vy - sy)));
557 
558       if (*v < 0.0)
559         *v = 0.0;
560       else if (*v > 1.0)
561         *v = 1.0;
562     }
563   else if (sx * (x - hx) + sy * (y - hy) < 0.0)
564     {
565       *v = 1.0;
566       *s = (((x - vx) * (hx - vx) + (y - vy) * (hy - vy)) /
567             ((hx - vx) * (hx - vx) + (hy - vy) * (hy - vy)));
568 
569       if (*s < 0.0)
570         *s = 0.0;
571       else if (*s > 1.0)
572         *s = 1.0;
573     }
574   else
575     {
576       *v = (((x - sx) * (hy - vy) - (y - sy) * (hx - vx))
577             / ((vx - sx) * (hy - vy) - (vy - sy) * (hx - vx)));
578 
579       if (*v<= 0.0)
580         {
581           *v = 0.0;
582           *s = 0.0;
583         }
584       else
585         {
586           if (*v > 1.0)
587             *v = 1.0;
588 
589           if (fabs (hy - vy) < fabs (hx - vx))
590             *s = (x - sx - *v * (vx - sx)) / (*v * (hx - vx));
591           else
592             *s = (y - sy - *v * (vy - sy)) / (*v * (hy - vy));
593 
594           if (*s < 0.0)
595             *s = 0.0;
596           else if (*s > 1.0)
597             *s = 1.0;
598         }
599     }
600 }
601 
602 /* Computes whether a point is inside the saturation/value triangle */
603 static gboolean
is_in_triangle(GimpColorWheel * wheel,gdouble x,gdouble y)604 is_in_triangle (GimpColorWheel *wheel,
605                 gdouble         x,
606                 gdouble         y)
607 {
608   gint    hx, hy, sx, sy, vx, vy;
609   gdouble det, s, v;
610 
611   compute_triangle (wheel, &hx, &hy, &sx, &sy, &vx, &vy);
612 
613   det = (vx - sx) * (hy - sy) - (vy - sy) * (hx - sx);
614 
615   s = ((x - sx) * (hy - sy) - (y - sy) * (hx - sx)) / det;
616   v = ((vx - sx) * (y - sy) - (vy - sy) * (x - sx)) / det;
617 
618   return (s >= 0.0 && v >= 0.0 && s + v <= 1.0);
619 }
620 
621 /* Computes a value based on the mouse coordinates */
622 static double
compute_v(GimpColorWheel * wheel,gdouble x,gdouble y)623 compute_v (GimpColorWheel *wheel,
624            gdouble         x,
625            gdouble         y)
626 {
627   GtkAllocation allocation;
628   gdouble       center_x;
629   gdouble       center_y;
630   gdouble       dx, dy;
631   gdouble       angle;
632 
633   gtk_widget_get_allocation (GTK_WIDGET (wheel), &allocation);
634 
635   center_x = allocation.width / 2.0;
636   center_y = allocation.height / 2.0;
637 
638   dx = x - center_x;
639   dy = center_y - y;
640 
641   angle = atan2 (dy, dx);
642   if (angle < 0.0)
643     angle += 2.0 * G_PI;
644 
645   return angle / (2.0 * G_PI);
646 }
647 
648 static void
set_cross_grab(GimpColorWheel * wheel,guint32 time)649 set_cross_grab (GimpColorWheel *wheel,
650                 guint32         time)
651 {
652   GimpColorWheelPrivate *priv = wheel->priv;
653   GdkCursor             *cursor;
654 
655   cursor =
656     gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (wheel)),
657                                 GDK_CROSSHAIR);
658 
659   gdk_pointer_grab (priv->window, FALSE,
660                     GDK_POINTER_MOTION_MASK      |
661                     GDK_POINTER_MOTION_HINT_MASK |
662                     GDK_BUTTON_RELEASE_MASK,
663                     NULL, cursor, time);
664   gdk_cursor_unref (cursor);
665 }
666 
667 static gboolean
gimp_color_wheel_grab_broken(GtkWidget * widget,GdkEventGrabBroken * event)668 gimp_color_wheel_grab_broken (GtkWidget          *widget,
669                               GdkEventGrabBroken *event)
670 {
671   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
672   GimpColorWheelPrivate *priv  = wheel->priv;
673 
674   priv->mode = DRAG_NONE;
675 
676   return TRUE;
677 }
678 
679 static gboolean
gimp_color_wheel_button_press(GtkWidget * widget,GdkEventButton * event)680 gimp_color_wheel_button_press (GtkWidget      *widget,
681                                GdkEventButton *event)
682 {
683   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
684   GimpColorWheelPrivate *priv  = wheel->priv;
685   gdouble                x, y;
686 
687   if (priv->mode != DRAG_NONE || event->button != 1)
688     return FALSE;
689 
690   x = event->x;
691   y = event->y;
692 
693   if (is_in_ring (wheel, x, y))
694     {
695       priv->mode = DRAG_H;
696       set_cross_grab (wheel, event->time);
697 
698       gimp_color_wheel_set_color (wheel,
699                                   compute_v (wheel, x, y),
700                                   priv->s,
701                                   priv->v);
702 
703       gtk_widget_grab_focus (widget);
704       priv->focus_on_ring = TRUE;
705 
706       return TRUE;
707     }
708 
709   if (is_in_triangle (wheel, x, y))
710     {
711       gdouble s, v;
712 
713       priv->mode = DRAG_SV;
714       set_cross_grab (wheel, event->time);
715 
716       compute_sv (wheel, x, y, &s, &v);
717       gimp_color_wheel_set_color (wheel, priv->h, s, v);
718 
719       gtk_widget_grab_focus (widget);
720       priv->focus_on_ring = FALSE;
721 
722       return TRUE;
723     }
724 
725   return FALSE;
726 }
727 
728 static gboolean
gimp_color_wheel_button_release(GtkWidget * widget,GdkEventButton * event)729 gimp_color_wheel_button_release (GtkWidget      *widget,
730                                  GdkEventButton *event)
731 {
732   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
733   GimpColorWheelPrivate *priv  = wheel->priv;
734   DragMode               mode;
735   gdouble                x, y;
736 
737   if (priv->mode == DRAG_NONE || event->button != 1)
738     return FALSE;
739 
740   /* Set the drag mode to DRAG_NONE so that signal handlers for "catched"
741    * can see that this is the final color state.
742    */
743 
744   mode = priv->mode;
745   priv->mode = DRAG_NONE;
746 
747   x = event->x;
748   y = event->y;
749 
750   if (mode == DRAG_H)
751     {
752       gimp_color_wheel_set_color (wheel,
753                                   compute_v (wheel, x, y), priv->s, priv->v);
754     }
755   else if (mode == DRAG_SV)
756     {
757       gdouble s, v;
758 
759       compute_sv (wheel, x, y, &s, &v);
760       gimp_color_wheel_set_color (wheel, priv->h, s, v);
761     }
762   else
763     g_assert_not_reached ();
764 
765   gdk_display_pointer_ungrab (gdk_window_get_display (event->window),
766                               event->time);
767 
768   return TRUE;
769 }
770 
771 static gboolean
gimp_color_wheel_motion(GtkWidget * widget,GdkEventMotion * event)772 gimp_color_wheel_motion (GtkWidget      *widget,
773                          GdkEventMotion *event)
774 {
775   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
776   GimpColorWheelPrivate *priv  = wheel->priv;
777   gdouble                x, y;
778 
779   if (priv->mode == DRAG_NONE)
780     return FALSE;
781 
782   gdk_event_request_motions (event);
783   x = event->x;
784   y = event->y;
785 
786   if (priv->mode == DRAG_H)
787     {
788       gimp_color_wheel_set_color (wheel,
789                                   compute_v (wheel, x, y), priv->s, priv->v);
790       return TRUE;
791     }
792   else if (priv->mode == DRAG_SV)
793     {
794       gdouble s, v;
795 
796       compute_sv (wheel, x, y, &s, &v);
797       gimp_color_wheel_set_color (wheel, priv->h, s, v);
798       return TRUE;
799     }
800 
801   g_assert_not_reached ();
802 
803   return FALSE;
804 }
805 
806 
807 /* Redrawing */
808 
809 /* Paints the hue ring */
810 static void
paint_ring(GimpColorWheel * wheel,cairo_t * cr,gint x,gint y,gint width,gint height)811 paint_ring (GimpColorWheel *wheel,
812             cairo_t        *cr,
813             gint            x,
814             gint            y,
815             gint            width,
816             gint            height)
817 {
818   GtkWidget             *widget = GTK_WIDGET (wheel);
819   GimpColorWheelPrivate *priv   = wheel->priv;
820   GtkAllocation          allocation;
821   gint                   xx, yy;
822   gdouble                dx, dy, dist;
823   gdouble                center_x;
824   gdouble                center_y;
825   gdouble                inner, outer;
826   guint32               *buf, *p;
827   gdouble                angle;
828   gdouble                hue;
829   gdouble                r, g, b;
830   cairo_surface_t       *source;
831   cairo_t               *source_cr;
832   gint                   stride;
833   gint                   focus_width;
834   gint                   focus_pad;
835 
836   gtk_widget_get_allocation (GTK_WIDGET (wheel), &allocation);
837 
838   gtk_widget_style_get (widget,
839                         "focus-line-width", &focus_width,
840                         "focus-padding", &focus_pad,
841                         NULL);
842 
843   center_x = allocation.width / 2.0;
844   center_y = allocation.height / 2.0;
845 
846   outer = priv->size / 2.0;
847   inner = outer - priv->ring_width;
848 
849   /* Create an image initialized with the ring colors */
850 
851   stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
852   buf = g_new (guint32, height * stride / 4);
853 
854   for (yy = 0; yy < height; yy++)
855     {
856       p = buf + yy * width;
857 
858       dy = -(yy + y - center_y);
859 
860       for (xx = 0; xx < width; xx++)
861         {
862           dx = xx + x - center_x;
863 
864           dist = dx * dx + dy * dy;
865           if (dist < ((inner-1) * (inner-1)) || dist > ((outer+1) * (outer+1)))
866             {
867               *p++ = 0;
868               continue;
869             }
870 
871           angle = atan2 (dy, dx);
872           if (angle < 0.0)
873             angle += 2.0 * G_PI;
874 
875           hue = angle / (2.0 * G_PI);
876 
877           r = hue;
878           g = 1.0;
879           b = 1.0;
880           hsv_to_rgb (&r, &g, &b);
881 
882           *p++ = (((int)floor (r * 255 + 0.5) << 16) |
883                   ((int)floor (g * 255 + 0.5) << 8) |
884                   (int)floor (b * 255 + 0.5));
885         }
886     }
887 
888   if (priv->transform)
889     {
890       const Babl *format = babl_format ("cairo-RGB24");
891       guchar     *b      = (guchar *) buf;
892       gint        i;
893 
894       for (i = 0; i < height; i++)
895         {
896           gimp_color_transform_process_pixels (priv->transform,
897                                                format, b,
898                                                format, b,
899                                                width);
900 
901           b  += stride;
902         }
903     }
904 
905   source = cairo_image_surface_create_for_data ((guchar *) buf,
906                                                 CAIRO_FORMAT_RGB24,
907                                                 width, height, stride);
908 
909   /* Now draw the value marker onto the source image, so that it
910    * will get properly clipped at the edges of the ring
911    */
912   source_cr = cairo_create (source);
913 
914   r = priv->h;
915   g = 1.0;
916   b = 1.0;
917   hsv_to_rgb (&r, &g, &b);
918 
919   if (GIMP_RGB_LUMINANCE (r, g, b) > 0.5)
920     cairo_set_source_rgb (source_cr, 0., 0., 0.);
921   else
922     cairo_set_source_rgb (source_cr, 1., 1., 1.);
923 
924   cairo_move_to (source_cr, -x + center_x, - y + center_y);
925   cairo_line_to (source_cr,
926                  -x + center_x + cos (priv->h * 2.0 * G_PI) * priv->size / 2,
927                  -y + center_y - sin (priv->h * 2.0 * G_PI) * priv->size / 2);
928   cairo_stroke (source_cr);
929   cairo_destroy (source_cr);
930 
931   /* Draw the ring using the source image */
932 
933   cairo_save (cr);
934 
935   cairo_set_source_surface (cr, source, x, y);
936   cairo_surface_destroy (source);
937 
938   cairo_set_line_width (cr, priv->ring_width);
939   cairo_new_path (cr);
940   cairo_arc (cr,
941              center_x, center_y,
942              priv->size / 2. - priv->ring_width / 2.,
943              0, 2 * G_PI);
944   cairo_stroke (cr);
945 
946   cairo_restore (cr);
947 
948   g_free (buf);
949 }
950 
951 /* Converts an HSV triplet to an integer RGB triplet */
952 static void
get_color(gdouble h,gdouble s,gdouble v,gint * r,gint * g,gint * b)953 get_color (gdouble  h,
954            gdouble  s,
955            gdouble  v,
956            gint    *r,
957            gint    *g,
958            gint    *b)
959 {
960   hsv_to_rgb (&h, &s, &v);
961 
962   *r = floor (h * 255 + 0.5);
963   *g = floor (s * 255 + 0.5);
964   *b = floor (v * 255 + 0.5);
965 }
966 
967 #define SWAP(a, b, t) ((t) = (a), (a) = (b), (b) = (t))
968 
969 #define LERP(a, b, v1, v2, i) (((v2) - (v1) != 0)                                       \
970                                ? ((a) + ((b) - (a)) * ((i) - (v1)) / ((v2) - (v1)))     \
971                                : (a))
972 
973 /* Number of pixels we extend out from the edges when creating
974  * color source to avoid artifacts
975  */
976 #define PAD 3
977 
978 /* Paints the HSV triangle */
979 static void
paint_triangle(GimpColorWheel * wheel,cairo_t * cr,gint x,gint y,gint width,gint height)980 paint_triangle (GimpColorWheel *wheel,
981                 cairo_t        *cr,
982                 gint            x,
983                 gint            y,
984                 gint            width,
985                 gint            height)
986 {
987   GtkWidget             *widget = GTK_WIDGET (wheel);
988   GimpColorWheelPrivate *priv   = wheel->priv;
989   gint                   hx, hy, sx, sy, vx, vy; /* HSV vertices */
990   gint                   x1, y1, r1, g1, b1; /* First vertex in scanline order */
991   gint                   x2, y2, r2, g2, b2; /* Second vertex */
992   gint                   x3, y3, r3, g3, b3; /* Third vertex */
993   gint                   t;
994   guint32               *buf, *p, c;
995   gint                   xl, xr, rl, rr, gl, gr, bl, br; /* Scanline data */
996   gint                   xx, yy;
997   gint                   x_interp, y_interp;
998   gint                   x_start, x_end;
999   cairo_surface_t       *source;
1000   gdouble                r, g, b;
1001   gchar                 *detail;
1002   gint                   stride;
1003 
1004   /* Compute triangle's vertices */
1005 
1006   compute_triangle (wheel, &hx, &hy, &sx, &sy, &vx, &vy);
1007 
1008   x1 = hx;
1009   y1 = hy;
1010   get_color (priv->h, 1.0, 1.0, &r1, &g1, &b1);
1011 
1012   x2 = sx;
1013   y2 = sy;
1014   get_color (priv->h, 1.0, 0.0, &r2, &g2, &b2);
1015 
1016   x3 = vx;
1017   y3 = vy;
1018   get_color (priv->h, 0.0, 1.0, &r3, &g3, &b3);
1019 
1020   if (y2 > y3)
1021     {
1022       SWAP (x2, x3, t);
1023       SWAP (y2, y3, t);
1024       SWAP (r2, r3, t);
1025       SWAP (g2, g3, t);
1026       SWAP (b2, b3, t);
1027     }
1028 
1029   if (y1 > y3)
1030     {
1031       SWAP (x1, x3, t);
1032       SWAP (y1, y3, t);
1033       SWAP (r1, r3, t);
1034       SWAP (g1, g3, t);
1035       SWAP (b1, b3, t);
1036     }
1037 
1038   if (y1 > y2)
1039     {
1040       SWAP (x1, x2, t);
1041       SWAP (y1, y2, t);
1042       SWAP (r1, r2, t);
1043       SWAP (g1, g2, t);
1044       SWAP (b1, b2, t);
1045     }
1046 
1047   /* Shade the triangle */
1048 
1049   stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
1050   buf = g_new (guint32, height * stride / 4);
1051 
1052   for (yy = 0; yy < height; yy++)
1053     {
1054       p = buf + yy * width;
1055 
1056       if (yy + y >= y1 - PAD && yy + y < y3 + PAD)
1057         {
1058           y_interp = CLAMP (yy + y, y1, y3);
1059 
1060           if (y_interp < y2)
1061             {
1062               xl = LERP (x1, x2, y1, y2, y_interp);
1063 
1064               rl = LERP (r1, r2, y1, y2, y_interp);
1065               gl = LERP (g1, g2, y1, y2, y_interp);
1066               bl = LERP (b1, b2, y1, y2, y_interp);
1067             }
1068           else
1069             {
1070               xl = LERP (x2, x3, y2, y3, y_interp);
1071 
1072               rl = LERP (r2, r3, y2, y3, y_interp);
1073               gl = LERP (g2, g3, y2, y3, y_interp);
1074               bl = LERP (b2, b3, y2, y3, y_interp);
1075             }
1076 
1077           xr = LERP (x1, x3, y1, y3, y_interp);
1078 
1079           rr = LERP (r1, r3, y1, y3, y_interp);
1080           gr = LERP (g1, g3, y1, y3, y_interp);
1081           br = LERP (b1, b3, y1, y3, y_interp);
1082 
1083           if (xl > xr)
1084             {
1085               SWAP (xl, xr, t);
1086               SWAP (rl, rr, t);
1087               SWAP (gl, gr, t);
1088               SWAP (bl, br, t);
1089             }
1090 
1091           x_start = MAX (xl - PAD, x);
1092           x_end = MIN (xr + PAD, x + width);
1093           x_start = MIN (x_start, x_end);
1094 
1095           c = (rl << 16) | (gl << 8) | bl;
1096 
1097           for (xx = x; xx < x_start; xx++)
1098             *p++ = c;
1099 
1100           for (; xx < x_end; xx++)
1101             {
1102               x_interp = CLAMP (xx, xl, xr);
1103 
1104               *p++ = ((LERP (rl, rr, xl, xr, x_interp) << 16) |
1105                       (LERP (gl, gr, xl, xr, x_interp) << 8) |
1106                       LERP (bl, br, xl, xr, x_interp));
1107             }
1108 
1109           c = (rr << 16) | (gr << 8) | br;
1110 
1111           for (; xx < x + width; xx++)
1112             *p++ = c;
1113         }
1114     }
1115 
1116   if (priv->transform)
1117     {
1118       const Babl *format = babl_format ("cairo-RGB24");
1119       guchar     *b      = (guchar *) buf;
1120       gint        i;
1121 
1122       for (i = 0; i < height; i++)
1123         {
1124           gimp_color_transform_process_pixels (priv->transform,
1125                                                format, b,
1126                                                format, b,
1127                                                width);
1128 
1129           b  += stride;
1130         }
1131     }
1132 
1133   source = cairo_image_surface_create_for_data ((guchar *) buf,
1134                                                 CAIRO_FORMAT_RGB24,
1135                                                 width, height, stride);
1136 
1137   /* Draw a triangle with the image as a source */
1138 
1139   cairo_set_source_surface (cr, source, x, y);
1140   cairo_surface_destroy (source);
1141 
1142   cairo_move_to (cr, x1, y1);
1143   cairo_line_to (cr, x2, y2);
1144   cairo_line_to (cr, x3, y3);
1145   cairo_close_path (cr);
1146   cairo_fill (cr);
1147 
1148   g_free (buf);
1149 
1150   /* Draw value marker */
1151 
1152   xx = floor (sx + (vx - sx) * priv->v + (hx - vx) * priv->s * priv->v + 0.5);
1153   yy = floor (sy + (vy - sy) * priv->v + (hy - vy) * priv->s * priv->v + 0.5);
1154 
1155   r = priv->h;
1156   g = priv->s;
1157   b = priv->v;
1158   hsv_to_rgb (&r, &g, &b);
1159 
1160   if (GIMP_RGB_LUMINANCE (r, g, b) > 0.5)
1161     {
1162       detail = "colorwheel_light";
1163       cairo_set_source_rgb (cr, 0., 0., 0.);
1164     }
1165   else
1166     {
1167       detail = "colorwheel_dark";
1168       cairo_set_source_rgb (cr, 1., 1., 1.);
1169     }
1170 
1171 #define RADIUS 4
1172 #define FOCUS_RADIUS 6
1173 
1174   cairo_new_path (cr);
1175   cairo_arc (cr, xx, yy, RADIUS, 0, 2 * G_PI);
1176   cairo_stroke (cr);
1177 
1178   /* Draw focus outline */
1179 
1180   if (gtk_widget_has_focus (widget) &&
1181       ! priv->focus_on_ring)
1182     {
1183       GtkAllocation allocation;
1184       gint          focus_width;
1185       gint          focus_pad;
1186 
1187       gtk_widget_get_allocation (widget, &allocation);
1188 
1189       gtk_widget_style_get (widget,
1190                             "focus-line-width", &focus_width,
1191                             "focus-padding", &focus_pad,
1192                             NULL);
1193 
1194       gtk_paint_focus (gtk_widget_get_style (widget),
1195                        gtk_widget_get_window (widget),
1196                        gtk_widget_get_state (widget),
1197                        NULL, widget, detail,
1198                        allocation.x + xx - FOCUS_RADIUS - focus_width - focus_pad,
1199                        allocation.y + yy - FOCUS_RADIUS - focus_width - focus_pad,
1200                        2 * (FOCUS_RADIUS + focus_width + focus_pad),
1201                        2 * (FOCUS_RADIUS + focus_width + focus_pad));
1202     }
1203 }
1204 
1205 /* Paints the contents of the HSV color selector */
1206 static void
paint(GimpColorWheel * hsv,cairo_t * cr,gint x,gint y,gint width,gint height)1207 paint (GimpColorWheel *hsv,
1208        cairo_t        *cr,
1209        gint            x,
1210        gint            y,
1211        gint            width,
1212        gint            height)
1213 {
1214   GimpColorWheelPrivate *priv = hsv->priv;
1215 
1216   if (! priv->transform)
1217     gimp_color_wheel_create_transform (hsv);
1218 
1219   paint_ring (hsv, cr, x, y, width, height);
1220   paint_triangle (hsv, cr, x, y, width, height);
1221 }
1222 
1223 static gint
gimp_color_wheel_expose(GtkWidget * widget,GdkEventExpose * event)1224 gimp_color_wheel_expose (GtkWidget      *widget,
1225                          GdkEventExpose *event)
1226 {
1227   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
1228   GimpColorWheelPrivate *priv  = wheel->priv;
1229   GtkAllocation          allocation;
1230   GdkRectangle           dest;
1231   cairo_t               *cr;
1232 
1233   if (! (event->window == gtk_widget_get_window (widget) &&
1234          gtk_widget_is_drawable (widget)))
1235     return FALSE;
1236 
1237   gtk_widget_get_allocation (widget, &allocation);
1238 
1239   if (!gdk_rectangle_intersect (&event->area, &allocation, &dest))
1240     return FALSE;
1241 
1242   cr = gdk_cairo_create (gtk_widget_get_window (widget));
1243 
1244   cairo_translate (cr, allocation.x, allocation.y);
1245   paint (wheel, cr,
1246          dest.x - allocation.x,
1247          dest.y - allocation.y,
1248          dest.width, dest.height);
1249   cairo_destroy (cr);
1250 
1251   if (gtk_widget_has_focus (widget) && priv->focus_on_ring)
1252     gtk_paint_focus (gtk_widget_get_style (widget),
1253                      gtk_widget_get_window (widget),
1254                      gtk_widget_get_state (widget),
1255                      &event->area, widget, NULL,
1256                      allocation.x,
1257                      allocation.y,
1258                      allocation.width,
1259                      allocation.height);
1260 
1261   return FALSE;
1262 }
1263 
1264 static gboolean
gimp_color_wheel_focus(GtkWidget * widget,GtkDirectionType dir)1265 gimp_color_wheel_focus (GtkWidget        *widget,
1266                         GtkDirectionType  dir)
1267 {
1268   GimpColorWheel        *wheel = GIMP_COLOR_WHEEL (widget);
1269   GimpColorWheelPrivate *priv  = wheel->priv;
1270 
1271   if (!gtk_widget_has_focus (widget))
1272     {
1273       if (dir == GTK_DIR_TAB_BACKWARD)
1274         priv->focus_on_ring = FALSE;
1275       else
1276         priv->focus_on_ring = TRUE;
1277 
1278       gtk_widget_grab_focus (widget);
1279       return TRUE;
1280     }
1281 
1282   switch (dir)
1283     {
1284     case GTK_DIR_UP:
1285       if (priv->focus_on_ring)
1286         return FALSE;
1287       else
1288         priv->focus_on_ring = TRUE;
1289       break;
1290 
1291     case GTK_DIR_DOWN:
1292       if (priv->focus_on_ring)
1293         priv->focus_on_ring = FALSE;
1294       else
1295         return FALSE;
1296       break;
1297 
1298     case GTK_DIR_LEFT:
1299     case GTK_DIR_TAB_BACKWARD:
1300       if (priv->focus_on_ring)
1301         return FALSE;
1302       else
1303         priv->focus_on_ring = TRUE;
1304       break;
1305 
1306     case GTK_DIR_RIGHT:
1307     case GTK_DIR_TAB_FORWARD:
1308       if (priv->focus_on_ring)
1309         priv->focus_on_ring = FALSE;
1310       else
1311         return FALSE;
1312       break;
1313     }
1314 
1315   gtk_widget_queue_draw (widget);
1316 
1317   return TRUE;
1318 }
1319 
1320 /**
1321  * gimp_color_wheel_new:
1322  *
1323  * Creates a new HSV color selector.
1324  *
1325  * Return value: A newly-created HSV color selector.
1326  *
1327  * Since: 2.10
1328  */
1329 GtkWidget*
gimp_color_wheel_new(void)1330 gimp_color_wheel_new (void)
1331 {
1332   return g_object_new (GIMP_TYPE_COLOR_WHEEL, NULL);
1333 }
1334 
1335 /**
1336  * gimp_color_wheel_set_color:
1337  * @hsv: An HSV color selector
1338  * @h: Hue
1339  * @s: Saturation
1340  * @v: Value
1341  *
1342  * Sets the current color in an HSV color selector.
1343  * Color component values must be in the [0.0, 1.0] range.
1344  *
1345  * Since: 2.10
1346  */
1347 void
gimp_color_wheel_set_color(GimpColorWheel * wheel,gdouble h,gdouble s,gdouble v)1348 gimp_color_wheel_set_color (GimpColorWheel *wheel,
1349                             gdouble         h,
1350                             gdouble         s,
1351                             gdouble         v)
1352 {
1353   GimpColorWheelPrivate *priv;
1354 
1355   g_return_if_fail (GIMP_IS_COLOR_WHEEL (wheel));
1356   g_return_if_fail (h >= 0.0 && h <= 1.0);
1357   g_return_if_fail (s >= 0.0 && s <= 1.0);
1358   g_return_if_fail (v >= 0.0 && v <= 1.0);
1359 
1360   priv = wheel->priv;
1361 
1362   priv->h = h;
1363   priv->s = s;
1364   priv->v = v;
1365 
1366   g_signal_emit (wheel, wheel_signals[CHANGED], 0);
1367 
1368   gtk_widget_queue_draw (GTK_WIDGET (wheel));
1369 }
1370 
1371 /**
1372  * gimp_color_wheel_get_color:
1373  * @hsv: An HSV color selector
1374  * @h: (out): Return value for the hue
1375  * @s: (out): Return value for the saturation
1376  * @v: (out): Return value for the value
1377  *
1378  * Queries the current color in an HSV color selector.
1379  * Returned values will be in the [0.0, 1.0] range.
1380  *
1381  * Since: 2.10
1382  */
1383 void
gimp_color_wheel_get_color(GimpColorWheel * wheel,gdouble * h,gdouble * s,gdouble * v)1384 gimp_color_wheel_get_color (GimpColorWheel *wheel,
1385                             gdouble        *h,
1386                             gdouble        *s,
1387                             gdouble        *v)
1388 {
1389   GimpColorWheelPrivate *priv;
1390 
1391   g_return_if_fail (GIMP_IS_COLOR_WHEEL (wheel));
1392 
1393   priv = wheel->priv;
1394 
1395   if (h) *h = priv->h;
1396   if (s) *s = priv->s;
1397   if (v) *v = priv->v;
1398 }
1399 
1400 /**
1401  * gimp_color_wheel_set_ring_fraction:
1402  * @ring: A wheel color selector
1403  * @fraction: Ring fraction
1404  *
1405  * Sets the ring fraction of a wheel color selector.
1406  *
1407  * Since: 2.10
1408  */
1409 void
gimp_color_wheel_set_ring_fraction(GimpColorWheel * hsv,gdouble fraction)1410 gimp_color_wheel_set_ring_fraction (GimpColorWheel *hsv,
1411                                     gdouble         fraction)
1412 {
1413   GimpColorWheelPrivate *priv;
1414 
1415   g_return_if_fail (GIMP_IS_COLOR_WHEEL (hsv));
1416 
1417   priv = hsv->priv;
1418 
1419   priv->ring_fraction = CLAMP (fraction, 0.01, 0.99);
1420 
1421   gtk_widget_queue_draw (GTK_WIDGET (hsv));
1422 }
1423 
1424 /**
1425  * gimp_color_wheel_get_ring_fraction:
1426  * @ring: A wheel color selector
1427  *
1428  * Returns value: The ring fraction of the wheel color selector.
1429  *
1430  * Since: 2.10
1431  */
1432 gdouble
gimp_color_wheel_get_ring_fraction(GimpColorWheel * wheel)1433 gimp_color_wheel_get_ring_fraction (GimpColorWheel *wheel)
1434 {
1435   GimpColorWheelPrivate *priv;
1436 
1437   g_return_val_if_fail (GIMP_IS_COLOR_WHEEL (wheel), DEFAULT_FRACTION);
1438 
1439   priv = wheel->priv;
1440 
1441   return priv->ring_fraction;
1442 }
1443 
1444 /**
1445  * gimp_color_wheel_set_color_config:
1446  * @wheel:  a #GimpColorWheel widget.
1447  * @config: a #GimpColorConfig object.
1448  *
1449  * Sets the color management configuration to use with this color wheel.
1450  *
1451  * Since: 2.10
1452  */
1453 void
gimp_color_wheel_set_color_config(GimpColorWheel * wheel,GimpColorConfig * config)1454 gimp_color_wheel_set_color_config (GimpColorWheel  *wheel,
1455                                    GimpColorConfig *config)
1456 {
1457   GimpColorWheelPrivate *priv;
1458 
1459   g_return_if_fail (GIMP_IS_COLOR_WHEEL (wheel));
1460   g_return_if_fail (config == NULL || GIMP_IS_COLOR_CONFIG (config));
1461 
1462   priv = wheel->priv;
1463 
1464   if (config != priv->config)
1465     {
1466       if (priv->config)
1467         {
1468           g_signal_handlers_disconnect_by_func (priv->config,
1469                                                 gimp_color_wheel_destroy_transform,
1470                                                 wheel);
1471 
1472           gimp_color_wheel_destroy_transform (wheel);
1473         }
1474 
1475       g_set_object (&priv->config, config);
1476 
1477       if (priv->config)
1478         {
1479           g_signal_connect_swapped (priv->config, "notify",
1480                                     G_CALLBACK (gimp_color_wheel_destroy_transform),
1481                                     wheel);
1482         }
1483     }
1484 }
1485 
1486 /**
1487  * gimp_color_wheel_is_adjusting:
1488  * @hsv: A #GimpColorWheel
1489  *
1490  * An HSV color selector can be said to be adjusting if multiple rapid
1491  * changes are being made to its value, for example, when the user is
1492  * adjusting the value with the mouse. This function queries whether
1493  * the HSV color selector is being adjusted or not.
1494  *
1495  * Return value: %TRUE if clients can ignore changes to the color value,
1496  *     since they may be transitory, or %FALSE if they should consider
1497  *     the color value status to be final.
1498  *
1499  * Since: 2.10
1500  */
1501 gboolean
gimp_color_wheel_is_adjusting(GimpColorWheel * wheel)1502 gimp_color_wheel_is_adjusting (GimpColorWheel *wheel)
1503 {
1504   GimpColorWheelPrivate *priv;
1505 
1506   g_return_val_if_fail (GIMP_IS_COLOR_WHEEL (wheel), FALSE);
1507 
1508   priv = wheel->priv;
1509 
1510   return priv->mode != DRAG_NONE;
1511 }
1512 
1513 static void
gimp_color_wheel_move(GimpColorWheel * wheel,GtkDirectionType dir)1514 gimp_color_wheel_move (GimpColorWheel   *wheel,
1515                        GtkDirectionType  dir)
1516 {
1517   GimpColorWheelPrivate *priv = wheel->priv;
1518   gdouble                hue, sat, val;
1519   gint                   hx, hy, sx, sy, vx, vy; /* HSV vertices */
1520   gint                   x, y; /* position in triangle */
1521 
1522   hue = priv->h;
1523   sat = priv->s;
1524   val = priv->v;
1525 
1526   compute_triangle (wheel, &hx, &hy, &sx, &sy, &vx, &vy);
1527 
1528   x = floor (sx + (vx - sx) * priv->v + (hx - vx) * priv->s * priv->v + 0.5);
1529   y = floor (sy + (vy - sy) * priv->v + (hy - vy) * priv->s * priv->v + 0.5);
1530 
1531 #define HUE_DELTA 0.002
1532   switch (dir)
1533     {
1534     case GTK_DIR_UP:
1535       if (priv->focus_on_ring)
1536         hue += HUE_DELTA;
1537       else
1538         {
1539           y -= 1;
1540           compute_sv (wheel, x, y, &sat, &val);
1541         }
1542       break;
1543 
1544     case GTK_DIR_DOWN:
1545       if (priv->focus_on_ring)
1546         hue -= HUE_DELTA;
1547       else
1548         {
1549           y += 1;
1550           compute_sv (wheel, x, y, &sat, &val);
1551         }
1552       break;
1553 
1554     case GTK_DIR_LEFT:
1555       if (priv->focus_on_ring)
1556         hue += HUE_DELTA;
1557       else
1558         {
1559           x -= 1;
1560           compute_sv (wheel, x, y, &sat, &val);
1561         }
1562       break;
1563 
1564     case GTK_DIR_RIGHT:
1565       if (priv->focus_on_ring)
1566         hue -= HUE_DELTA
1567           ;
1568       else
1569         {
1570           x += 1;
1571           compute_sv (wheel, x, y, &sat, &val);
1572         }
1573       break;
1574 
1575     default:
1576       /* we don't care about the tab directions */
1577       break;
1578     }
1579 
1580   /* Wrap */
1581   if (hue < 0.0)
1582     hue = 1.0;
1583   else if (hue > 1.0)
1584     hue = 0.0;
1585 
1586   gimp_color_wheel_set_color (wheel, hue, sat, val);
1587 }
1588 
1589 static void
gimp_color_wheel_create_transform(GimpColorWheel * wheel)1590 gimp_color_wheel_create_transform (GimpColorWheel *wheel)
1591 {
1592   GimpColorWheelPrivate *priv = wheel->priv;
1593 
1594   if (priv->config)
1595     {
1596       static GimpColorProfile *profile = NULL;
1597 
1598       const Babl *format = babl_format ("cairo-RGB24");
1599 
1600       if (G_UNLIKELY (! profile))
1601         profile = gimp_color_profile_new_rgb_srgb ();
1602 
1603       priv->transform = gimp_widget_get_color_transform (GTK_WIDGET (wheel),
1604                                                          priv->config,
1605                                                          profile,
1606                                                          format,
1607                                                          format);
1608     }
1609 }
1610 
1611 static void
gimp_color_wheel_destroy_transform(GimpColorWheel * wheel)1612 gimp_color_wheel_destroy_transform (GimpColorWheel *wheel)
1613 {
1614   GimpColorWheelPrivate *priv = wheel->priv;
1615 
1616   if (priv->transform)
1617     {
1618       g_object_unref (priv->transform);
1619       priv->transform = NULL;
1620     }
1621 
1622   gtk_widget_queue_draw (GTK_WIDGET (wheel));
1623 }
1624