]> Pileus Git - ~andy/gtk/blob - gtk/gtkcolorplane.c
Change FSF Address
[~andy/gtk] / gtk / gtkcolorplane.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2012 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19
20 #include "gtkcolorplaneprivate.h"
21
22 #include "gtkcolorutils.h"
23 #include "gtkaccessible.h"
24 #include "gtkintl.h"
25
26 struct _GtkColorPlanePrivate
27 {
28   GtkAdjustment *h_adj;
29   GtkAdjustment *s_adj;
30   GtkAdjustment *v_adj;
31
32   cairo_surface_t *surface;
33   gboolean in_drag;
34 };
35
36 G_DEFINE_TYPE (GtkColorPlane, gtk_color_plane, GTK_TYPE_DRAWING_AREA)
37
38 static void
39 sv_to_xy (GtkColorPlane *plane,
40           gint          *x,
41           gint          *y)
42 {
43   gdouble s, v;
44   gint width, height;
45
46   width = gtk_widget_get_allocated_width (GTK_WIDGET (plane));
47   height = gtk_widget_get_allocated_height (GTK_WIDGET (plane));
48
49   s = gtk_adjustment_get_value (plane->priv->s_adj);
50   v = gtk_adjustment_get_value (plane->priv->v_adj);
51
52   *x = CLAMP (width * v, 0, width - 1);
53   *y = CLAMP (height * (1 - s), 0, height - 1);
54 }
55
56 static gboolean
57 plane_draw (GtkWidget *widget,
58             cairo_t   *cr)
59 {
60   GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
61   gint x, y;
62   gint width, height;
63
64   cairo_set_source_surface (cr, plane->priv->surface, 0, 0);
65   cairo_paint (cr);
66
67   sv_to_xy (plane, &x, &y);
68   width = gtk_widget_get_allocated_width (widget);
69   height = gtk_widget_get_allocated_height (widget);
70
71   cairo_move_to (cr, 0,     y + 0.5);
72   cairo_line_to (cr, width, y + 0.5);
73
74   cairo_move_to (cr, x + 0.5, 0);
75   cairo_line_to (cr, x + 0.5, height);
76
77   if (gtk_widget_has_visible_focus (widget))
78     {
79       cairo_set_line_width (cr, 3.0);
80       cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.6);
81       cairo_stroke_preserve (cr);
82
83       cairo_set_line_width (cr, 1.0);
84       cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.8);
85       cairo_stroke (cr);
86     }
87   else
88     {
89       cairo_set_line_width (cr, 1.0);
90       cairo_set_source_rgba (cr, 0.8, 0.8, 0.8, 0.8);
91       cairo_stroke (cr);
92     }
93
94   return FALSE;
95 }
96
97 static void
98 create_surface (GtkColorPlane *plane)
99 {
100   GtkWidget *widget = GTK_WIDGET (plane);
101   cairo_t *cr;
102   cairo_surface_t *surface;
103   gint width, height, stride;
104   cairo_surface_t *tmp;
105   guint red, green, blue;
106   guint32 *data, *p;
107   gdouble h, s, v;
108   gdouble r, g, b;
109   gdouble sf, vf;
110   gint x, y;
111
112   if (!gtk_widget_get_realized (widget))
113     return;
114
115   width = gtk_widget_get_allocated_width (widget);
116   height = gtk_widget_get_allocated_height (widget);
117
118   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
119                                                CAIRO_CONTENT_COLOR,
120                                                width, height);
121
122   if (plane->priv->surface)
123     cairo_surface_destroy (plane->priv->surface);
124   plane->priv->surface = surface;
125
126   if (width == 1 || height == 1)
127     return;
128
129   stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
130
131   data = g_malloc (height * stride);
132
133   h = gtk_adjustment_get_value (plane->priv->h_adj);
134   sf = 1.0 / (height - 1);
135   vf = 1.0 / (width - 1);
136   for (y = 0; y < height; y++)
137     {
138       s = CLAMP (1.0 - y * sf, 0.0, 1.0);
139       p = data + y * (stride / 4);
140       for (x = 0; x < width; x++)
141         {
142           v = x * vf;
143           gtk_hsv_to_rgb (h, s, v, &r, &g, &b);
144           red = CLAMP (r * 255, 0, 255);
145           green = CLAMP (g * 255, 0, 255);
146           blue = CLAMP (b * 255, 0, 255);
147           p[x] = (red << 16) | (green << 8) | blue;
148         }
149     }
150
151   tmp = cairo_image_surface_create_for_data ((guchar *)data, CAIRO_FORMAT_RGB24,
152                                              width, height, stride);
153   cr = cairo_create (surface);
154
155   cairo_set_source_surface (cr, tmp, 0, 0);
156   cairo_paint (cr);
157
158   cairo_destroy (cr);
159   cairo_surface_destroy (tmp);
160   g_free (data);
161 }
162
163 static gboolean
164 plane_configure (GtkWidget         *widget,
165                  GdkEventConfigure *event)
166 {
167   create_surface (GTK_COLOR_PLANE (widget));
168   return TRUE;
169 }
170
171 static void
172 set_cross_grab (GtkWidget *widget,
173                 GdkDevice *device,
174                 guint32    time)
175 {
176   GdkCursor *cursor;
177
178   cursor = gdk_cursor_new_for_display (gtk_widget_get_display (GTK_WIDGET (widget)),
179                                        GDK_CROSSHAIR);
180   gdk_device_grab (device,
181                    gtk_widget_get_window (widget),
182                    GDK_OWNERSHIP_NONE,
183                    FALSE,
184                    GDK_POINTER_MOTION_MASK
185                     | GDK_POINTER_MOTION_HINT_MASK
186                     | GDK_BUTTON_RELEASE_MASK,
187                    cursor,
188                    time);
189   g_object_unref (cursor);
190 }
191
192 static gboolean
193 plane_grab_broken (GtkWidget          *widget,
194                    GdkEventGrabBroken *event)
195 {
196   GTK_COLOR_PLANE (widget)->priv->in_drag = FALSE;
197   return TRUE;
198 }
199
200 static void
201 h_changed (GtkColorPlane *plane)
202 {
203   create_surface (plane);
204   gtk_widget_queue_draw (GTK_WIDGET (plane));
205 }
206
207 static void
208 sv_changed (GtkColorPlane *plane)
209 {
210   gtk_widget_queue_draw (GTK_WIDGET (plane));
211 }
212
213 static void
214 update_color (GtkColorPlane *plane,
215               gint           x,
216               gint           y)
217 {
218   GtkWidget *widget = GTK_WIDGET (plane);
219   gdouble s, v;
220
221   s = CLAMP (1 - y * (1.0 / gtk_widget_get_allocated_height (widget)), 0, 1);
222   v = CLAMP (x * (1.0 / gtk_widget_get_allocated_width (widget)), 0, 1);
223   gtk_adjustment_set_value (plane->priv->s_adj, s);
224   gtk_adjustment_set_value (plane->priv->v_adj, v);
225
226   gtk_widget_queue_draw (widget);
227 }
228
229 static gboolean
230 plane_button_press (GtkWidget      *widget,
231                     GdkEventButton *event)
232 {
233   GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
234
235   if (event->button == GDK_BUTTON_SECONDARY)
236     {
237       gboolean handled;
238
239       g_signal_emit_by_name (widget, "popup-menu", &handled);
240
241       return TRUE;
242     }
243
244   if (plane->priv->in_drag || event->button != GDK_BUTTON_PRIMARY)
245     return FALSE;
246
247   plane->priv->in_drag = TRUE;
248   set_cross_grab (widget, gdk_event_get_device ((GdkEvent*)event), event->time);
249   update_color (plane, event->x, event->y);
250   gtk_widget_grab_focus (widget);
251
252   return TRUE;
253 }
254
255 static gboolean
256 plane_button_release (GtkWidget      *widget,
257                       GdkEventButton *event)
258 {
259   GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
260
261   if (!plane->priv->in_drag || event->button != GDK_BUTTON_PRIMARY)
262     return FALSE;
263
264   plane->priv->in_drag = FALSE;
265
266   update_color (plane, event->x, event->y);
267   gdk_device_ungrab (gdk_event_get_device ((GdkEvent *) event), event->time);
268
269   return TRUE;
270 }
271
272 static gboolean
273 plane_motion_notify (GtkWidget      *widget,
274                      GdkEventMotion *event)
275 {
276   GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
277
278   if (!plane->priv->in_drag)
279     return FALSE;
280
281   gdk_event_request_motions (event);
282   update_color (plane, event->x, event->y);
283
284   return TRUE;
285 }
286
287 static void
288 sv_move (GtkColorPlane *plane,
289          gdouble        ds,
290          gdouble        dv)
291 {
292   gdouble s, v;
293
294   s = gtk_adjustment_get_value (plane->priv->s_adj);
295   v = gtk_adjustment_get_value (plane->priv->v_adj);
296
297   if (s + ds > 1)
298     {
299       if (s < 1)
300         s = 1;
301       else
302         goto error;
303     }
304   else if (s + ds < 0)
305     {
306       if (s > 0)
307         s = 0;
308       else
309         goto error;
310     }
311   else
312     {
313       s += ds;
314     }
315
316   if (v + dv > 1)
317     {
318       if (v < 1)
319         v = 1;
320       else
321         goto error;
322     }
323   else if (v + dv < 0)
324     {
325       if (v > 0)
326         v = 0;
327       else
328         goto error;
329     }
330   else
331     {
332       v += dv;
333     }
334
335   gtk_adjustment_set_value (plane->priv->s_adj, s);
336   gtk_adjustment_set_value (plane->priv->v_adj, v);
337   return;
338
339 error:
340   gtk_widget_error_bell (GTK_WIDGET (plane));
341 }
342
343 static gboolean
344 plane_key_press (GtkWidget   *widget,
345                  GdkEventKey *event)
346 {
347   GtkColorPlane *plane = GTK_COLOR_PLANE (widget);
348   gdouble step;
349
350   if ((event->state & GDK_MOD1_MASK) != 0)
351     step = 0.1;
352   else
353     step = 0.01;
354
355   if (event->keyval == GDK_KEY_Up ||
356       event->keyval == GDK_KEY_KP_Up)
357     sv_move (plane, step, 0);
358   else if (event->keyval == GDK_KEY_Down ||
359            event->keyval == GDK_KEY_KP_Down)
360     sv_move (plane, -step, 0);
361   else if (event->keyval == GDK_KEY_Left ||
362            event->keyval == GDK_KEY_KP_Left)
363     sv_move (plane, 0, -step);
364   else if (event->keyval == GDK_KEY_Right ||
365            event->keyval == GDK_KEY_KP_Right)
366     sv_move (plane, 0, step);
367   else
368     return GTK_WIDGET_CLASS (gtk_color_plane_parent_class)->key_press_event (widget, event);
369
370   return TRUE;
371 }
372
373 static void
374 gtk_color_plane_init (GtkColorPlane *plane)
375 {
376   AtkObject *atk_obj;
377
378   plane->priv = G_TYPE_INSTANCE_GET_PRIVATE (plane,
379                                              GTK_TYPE_COLOR_PLANE,
380                                              GtkColorPlanePrivate);
381
382   gtk_widget_set_can_focus (GTK_WIDGET (plane), TRUE);
383   gtk_widget_set_events (GTK_WIDGET (plane), GDK_KEY_PRESS_MASK
384                                              | GDK_BUTTON_PRESS_MASK
385                                              | GDK_BUTTON_RELEASE_MASK
386                                              | GDK_POINTER_MOTION_MASK);
387
388   atk_obj = gtk_widget_get_accessible (GTK_WIDGET (plane));
389   if (GTK_IS_ACCESSIBLE (atk_obj))
390     {
391       atk_object_set_name (atk_obj, _("Color Plane"));
392       atk_object_set_role (atk_obj, ATK_ROLE_COLOR_CHOOSER);
393     }
394 }
395
396 static void
397 plane_finalize (GObject *object)
398 {
399   GtkColorPlane *plane = GTK_COLOR_PLANE (object);
400
401   if (plane->priv->surface)
402     cairo_surface_destroy (plane->priv->surface);
403
404   g_clear_object (&plane->priv->h_adj);
405   g_clear_object (&plane->priv->s_adj);
406   g_clear_object (&plane->priv->v_adj);
407
408   G_OBJECT_CLASS (gtk_color_plane_parent_class)->finalize (object);
409 }
410
411 static void
412 gtk_color_plane_class_init (GtkColorPlaneClass *class)
413 {
414   GObjectClass *object_class = G_OBJECT_CLASS (class);
415   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
416
417   object_class->finalize = plane_finalize;
418
419   widget_class->draw = plane_draw;
420   widget_class->configure_event = plane_configure;
421   widget_class->button_press_event = plane_button_press;
422   widget_class->button_release_event = plane_button_release;
423   widget_class->motion_notify_event = plane_motion_notify;
424   widget_class->grab_broken_event = plane_grab_broken;
425   widget_class->key_press_event = plane_key_press;
426
427   g_type_class_add_private (class, sizeof (GtkColorPlanePrivate));
428 }
429
430 GtkWidget *
431 gtk_color_plane_new (GtkAdjustment *h_adj,
432                      GtkAdjustment *s_adj,
433                      GtkAdjustment *v_adj)
434 {
435   GtkColorPlane *plane;
436
437   plane = (GtkColorPlane *) g_object_new (GTK_TYPE_COLOR_PLANE, NULL);
438
439   plane->priv->h_adj = g_object_ref_sink (h_adj);
440   plane->priv->s_adj = g_object_ref_sink (s_adj);
441   plane->priv->v_adj = g_object_ref_sink (v_adj);
442   g_signal_connect_swapped (h_adj, "value-changed", G_CALLBACK (h_changed), plane);
443   g_signal_connect_swapped (s_adj, "value-changed", G_CALLBACK (sv_changed), plane);
444   g_signal_connect_swapped (v_adj, "value-changed", G_CALLBACK (sv_changed), plane);
445
446   return (GtkWidget *)plane;
447 }