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