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