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