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