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