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