]> Pileus Git - ~andy/gtk/blob - gtk/gtkcoloreditor.c
99168b5fb3c6bbe7bcec90d31a954124ef74b413
[~andy/gtk] / gtk / gtkcoloreditor.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 /* TODO
21  * - move out custom sliders
22  * - pop-up entries
23  */
24
25 #include "config.h"
26
27 #include "gtkcolorchooserprivate.h"
28 #include "gtkcoloreditor.h"
29 #include "gtkcolorplane.h"
30 #include "gtkcolorswatch.h"
31 #include "gtkgrid.h"
32 #include "gtkscale.h"
33 #include "gtkaspectframe.h"
34 #include "gtkdrawingarea.h"
35 #include "gtkentry.h"
36 #include "gtkhsv.h"
37 #include "gtkadjustment.h"
38 #include "gtkintl.h"
39
40 #include <math.h>
41
42 struct _GtkColorEditorPrivate
43 {
44   GtkWidget *grid;
45   GtkWidget *swatch;
46   GtkWidget *entry;
47   GtkWidget *h_slider;
48   GtkWidget *sv_plane;
49   GtkWidget *a_slider;
50
51   GtkAdjustment *h_adj;
52   GtkAdjustment *a_adj;
53   cairo_surface_t *h_surface;
54   cairo_surface_t *a_surface;
55
56   GdkRGBA color;
57   gdouble h, s, v;
58   guint text_changed : 1;
59   guint show_alpha   : 1;
60 };
61
62 enum
63 {
64   PROP_ZERO,
65   PROP_COLOR,
66   PROP_SHOW_ALPHA
67 };
68
69 static void gtk_color_editor_iface_init (GtkColorChooserInterface *iface);
70
71 G_DEFINE_TYPE_WITH_CODE (GtkColorEditor, gtk_color_editor, GTK_TYPE_BOX,
72                          G_IMPLEMENT_INTERFACE (GTK_TYPE_COLOR_CHOOSER,
73                                                 gtk_color_editor_iface_init))
74
75 static guint
76 scale_round (gdouble value, gdouble scale)
77 {
78   value = floor (value * scale + 0.5);
79   value = MAX (value, 0);
80   value = MIN (value, scale);
81   return (guint)value;
82 }
83
84 static void
85 update_entry (GtkColorEditor *editor)
86 {
87   gchar *text;
88
89   text = g_strdup_printf ("#%02X%02X%02X",
90                           scale_round (editor->priv->color.red, 255),
91                           scale_round (editor->priv->color.green, 255),
92                           scale_round (editor->priv->color.blue, 255));
93   gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), text);
94   editor->priv->text_changed = FALSE;
95   g_free (text);
96 }
97
98 static void
99 entry_apply (GtkWidget      *entry,
100              GtkColorEditor *editor)
101 {
102   GdkRGBA color;
103   gchar *text;
104
105   if (!editor->priv->text_changed)
106     return;
107
108   text = gtk_editable_get_chars (GTK_EDITABLE (editor->priv->entry), 0, -1);
109   if (gdk_rgba_parse (&color, text))
110     {
111       color.alpha = editor->priv->color.alpha;
112       gtk_color_chooser_set_color (GTK_COLOR_CHOOSER (editor), &color);
113     }
114
115   editor->priv->text_changed = FALSE;
116
117   g_free (text);
118 }
119
120 static gboolean
121 entry_focus_out (GtkWidget      *entry,
122                  GdkEventFocus  *event,
123                  GtkColorEditor *editor)
124 {
125   entry_apply (entry, editor);
126   return FALSE;
127 }
128
129 static void
130 entry_text_changed (GtkWidget      *entry,
131                     GParamSpec     *pspec,
132                     GtkColorEditor *editor)
133 {
134   editor->priv->text_changed = TRUE;
135 }
136
137 static void
138 h_changed (GtkAdjustment  *adj,
139            GtkColorEditor *editor)
140 {
141   editor->priv->h = gtk_adjustment_get_value (adj);
142   gtk_hsv_to_rgb (editor->priv->h, editor->priv->s, editor->priv->v,
143                   &editor->priv->color.red,
144                   &editor->priv->color.green,
145                   &editor->priv->color.blue);
146   gtk_color_plane_set_h (GTK_COLOR_PLANE (editor->priv->sv_plane), editor->priv->h);
147   update_entry (editor);
148   gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), &editor->priv->color);
149   gtk_widget_queue_draw (editor->priv->a_slider);
150   if (editor->priv->a_surface)
151     {
152       cairo_surface_destroy (editor->priv->a_surface);
153       editor->priv->a_surface = NULL;
154     }
155   g_object_notify (G_OBJECT (editor), "color");
156 }
157
158 static void
159 sv_changed (GtkColorPlane  *plane,
160             GtkColorEditor *editor)
161 {
162   editor->priv->s = gtk_color_plane_get_s (plane);
163   editor->priv->v = gtk_color_plane_get_v (plane);
164   gtk_hsv_to_rgb (editor->priv->h, editor->priv->s, editor->priv->v,
165                   &editor->priv->color.red,
166                   &editor->priv->color.green,
167                   &editor->priv->color.blue);
168   update_entry (editor);
169   gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), &editor->priv->color);
170   gtk_widget_queue_draw (editor->priv->a_slider);
171   if (editor->priv->a_surface)
172     {
173       cairo_surface_destroy (editor->priv->a_surface);
174       editor->priv->a_surface = NULL;
175     }
176   g_object_notify (G_OBJECT (editor), "color");
177 }
178
179 static void
180 a_changed (GtkAdjustment  *adj,
181            GtkColorEditor *editor)
182 {
183   editor->priv->color.alpha = gtk_adjustment_get_value (adj);
184   gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), &editor->priv->color);
185   g_object_notify (G_OBJECT (editor), "color");
186 }
187
188 static cairo_pattern_t *
189 get_checkered_pattern (void)
190 {
191   /* need to respect pixman's stride being a multiple of 4 */
192   static unsigned char data[8] = { 0xFF, 0x00, 0x00, 0x00,
193                                    0x00, 0xFF, 0x00, 0x00 };
194   static cairo_surface_t *checkered = NULL;
195   cairo_pattern_t *pattern;
196
197   if (checkered == NULL)
198     checkered = cairo_image_surface_create_for_data (data,
199                                                      CAIRO_FORMAT_A8,
200                                                      2, 2, 4);
201
202   pattern = cairo_pattern_create_for_surface (checkered);
203   cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
204   cairo_pattern_set_filter (pattern, CAIRO_FILTER_NEAREST);
205
206   return pattern;
207 }
208
209 static cairo_surface_t *
210 create_h_surface (GtkWidget *widget)
211 {
212   cairo_t *cr;
213   cairo_surface_t *surface;
214   gint width, height, stride;
215   cairo_surface_t *tmp;
216   guint red, green, blue;
217   guint32 *data, *p;
218   gdouble h;
219   gdouble r, g, b;
220   gdouble f;
221   gint x, y;
222
223   if (!gtk_widget_get_realized (widget))
224     return NULL;
225
226   width = gtk_widget_get_allocated_width (widget);
227   height = gtk_widget_get_allocated_height (widget);
228
229   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
230                                                CAIRO_CONTENT_COLOR,
231                                                width, height);
232
233   if (width == 1 || height == 1)
234     return surface;
235
236   stride = cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
237
238   data = g_malloc (height * stride);
239
240   f = 1.0 / (height - 1);
241   for (y = 0; y < height; y++)
242     {
243       h = CLAMP (y * f, 0.0, 1.0);
244       p = data + y * (stride / 4);
245       for (x = 0; x < width; x++)
246         {
247           gtk_hsv_to_rgb (h, 1, 1, &r, &g, &b);
248           red = CLAMP (r * 255, 0, 255);
249           green = CLAMP (g * 255, 0, 255);
250           blue = CLAMP (b * 255, 0, 255);
251           p[x] = (red << 16) | (green << 8) | blue;
252         }
253     }
254
255   tmp = cairo_image_surface_create_for_data ((guchar *)data, CAIRO_FORMAT_RGB24,
256                                              width, height, stride);
257   cr = cairo_create (surface);
258
259   cairo_set_source_surface (cr, tmp, 0, 0);
260   cairo_paint (cr);
261
262   cairo_destroy (cr);
263   cairo_surface_destroy (tmp);
264   g_free (data);
265
266   return surface;
267 }
268
269 static gboolean
270 h_draw (GtkWidget *widget,
271         cairo_t   *cr,
272         GtkColorEditor *editor)
273 {
274   cairo_surface_t *surface;
275   gint width, height;
276
277   width = gtk_widget_get_allocated_width (widget);
278   height = gtk_widget_get_allocated_height (widget);
279
280   if (!editor->priv->h_surface)
281     editor->priv->h_surface = create_h_surface (widget);
282   surface = editor->priv->h_surface;
283
284   cairo_save (cr);
285
286   cairo_rectangle (cr, 1, 1, width - 2, height - 2);
287   cairo_clip (cr);
288   cairo_set_source_surface (cr, surface, 0, 0);
289   cairo_paint (cr);
290
291   cairo_restore (cr);
292
293   return FALSE;
294 }
295
296 static cairo_surface_t *
297 create_a_surface (GtkWidget *widget,
298                   GdkRGBA   *color)
299 {
300   cairo_t *cr;
301   cairo_surface_t *surface;
302   cairo_pattern_t *pattern;
303   cairo_matrix_t matrix;
304   gint width, height;
305
306   if (!gtk_widget_get_realized (widget))
307     return NULL;
308
309   width = gtk_widget_get_allocated_width (widget);
310   height = gtk_widget_get_allocated_height (widget);
311
312   surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget),
313                                                CAIRO_CONTENT_COLOR,
314                                                width, height);
315
316   if (width == 1 || height == 1)
317     return surface;
318
319   cr = cairo_create (surface);
320
321   cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
322   cairo_paint (cr);
323   cairo_set_source_rgb (cr, 0.66, 0.66, 0.66);
324
325   pattern = get_checkered_pattern ();
326   cairo_matrix_init_scale (&matrix, 0.125, 0.125);
327   cairo_pattern_set_matrix (pattern, &matrix);
328   cairo_mask (cr, pattern);
329   cairo_pattern_destroy (pattern);
330
331   pattern = cairo_pattern_create_linear (0, 0, width, 0);
332   cairo_pattern_add_color_stop_rgba (pattern, 0, color->red, color->green, color->blue, 0);
333   cairo_pattern_add_color_stop_rgba (pattern, width, color->red, color->green, color->blue, 1);
334   cairo_set_source (cr, pattern);
335   cairo_paint (cr);
336   cairo_pattern_destroy (pattern);
337
338   cairo_destroy (cr);
339
340   return surface;
341 }
342
343 static gboolean
344 a_draw (GtkWidget *widget,
345         cairo_t   *cr,
346         GtkColorEditor *editor)
347 {
348   cairo_surface_t *surface;
349   gint width, height;
350
351   width = gtk_widget_get_allocated_width (widget);
352   height = gtk_widget_get_allocated_height (widget);
353
354   if (!editor->priv->a_surface)
355     editor->priv->a_surface = create_a_surface (widget, &editor->priv->color);
356   surface = editor->priv->a_surface;
357
358   cairo_save (cr);
359
360   cairo_rectangle (cr, 1, 1, width - 2, height - 2);
361   cairo_clip (cr);
362   cairo_set_source_surface (cr, surface, 0, 0);
363   cairo_paint (cr);
364
365   cairo_restore (cr);
366
367   return FALSE;
368 }
369
370 static void
371 gtk_color_editor_init (GtkColorEditor *editor)
372 {
373   GtkWidget *grid;
374   GtkAdjustment *adj;
375
376   editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (editor,
377                                               GTK_TYPE_COLOR_EDITOR,
378                                               GtkColorEditorPrivate);
379   editor->priv->show_alpha = TRUE;
380
381   gtk_widget_push_composite_child ();
382
383   editor->priv->grid = grid = gtk_grid_new ();
384   gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
385   gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
386
387   editor->priv->swatch = gtk_color_swatch_new ();
388   gtk_widget_set_sensitive (editor->priv->swatch, FALSE);
389   gtk_color_swatch_set_corner_radii (GTK_COLOR_SWATCH (editor->priv->swatch), 2, 2, 2, 2);
390
391   editor->priv->entry = gtk_entry_new ();
392   g_signal_connect (editor->priv->entry, "activate",
393                     G_CALLBACK (entry_apply), editor);
394   g_signal_connect (editor->priv->entry, "notify::text",
395                     G_CALLBACK (entry_text_changed), editor);
396   g_signal_connect (editor->priv->entry, "focus-out-event",
397                     G_CALLBACK (entry_focus_out), editor);
398
399   adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
400   g_signal_connect (adj, "value-changed", G_CALLBACK (h_changed), editor);
401   editor->priv->h_slider = gtk_scale_new (GTK_ORIENTATION_VERTICAL, adj);
402   editor->priv->h_adj = adj;
403   g_signal_connect (editor->priv->h_slider, "draw", G_CALLBACK (h_draw), editor);
404
405   gtk_scale_set_draw_value (GTK_SCALE (editor->priv->h_slider), FALSE);
406   editor->priv->sv_plane = gtk_color_plane_new ();
407   gtk_widget_set_size_request (editor->priv->sv_plane, 300, 300);
408   gtk_widget_set_hexpand (editor->priv->sv_plane, TRUE);
409   gtk_widget_set_vexpand (editor->priv->sv_plane, TRUE);
410
411   g_signal_connect (editor->priv->sv_plane, "changed", G_CALLBACK (sv_changed), editor);
412
413   adj = gtk_adjustment_new (1, 0, 1, 0.01, 0.1, 0);
414   editor->priv->a_slider = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, adj);
415   g_signal_connect (adj, "value-changed", G_CALLBACK (a_changed), editor);
416   gtk_scale_set_draw_value (GTK_SCALE (editor->priv->a_slider), FALSE);
417   editor->priv->a_adj = adj;
418   g_signal_connect (editor->priv->a_slider, "draw", G_CALLBACK (a_draw), editor);
419
420   gtk_grid_attach (GTK_GRID (grid), editor->priv->swatch,   1, 0, 1, 1);
421   gtk_grid_attach (GTK_GRID (grid), editor->priv->entry,    2, 0, 1, 1);
422   gtk_grid_attach (GTK_GRID (grid), editor->priv->h_slider, 0, 1, 1, 1);
423   gtk_grid_attach (GTK_GRID (grid), editor->priv->sv_plane, 1, 1, 2, 1);
424   gtk_grid_attach (GTK_GRID (grid), editor->priv->a_slider, 1, 2, 2, 1);
425   gtk_widget_show_all (grid);
426
427   gtk_container_add (GTK_CONTAINER (editor), grid);
428   gtk_widget_pop_composite_child ();
429 }
430
431 static void
432 gtk_color_editor_get_property (GObject    *object,
433                                guint       prop_id,
434                                GValue     *value,
435                                GParamSpec *pspec)
436 {
437   GtkColorEditor *ce = GTK_COLOR_EDITOR (object);
438   GtkColorChooser *cc = GTK_COLOR_CHOOSER (object);
439
440   switch (prop_id)
441     {
442     case PROP_COLOR:
443       {
444         GdkRGBA color;
445         gtk_color_chooser_get_color (cc, &color);
446         g_value_set_boxed (value, &color);
447       }
448       break;
449     case PROP_SHOW_ALPHA:
450       g_value_set_boolean (value, gtk_widget_get_visible (ce->priv->a_slider));
451       break;
452     default:
453       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
454       break;
455     }
456 }
457
458 static void
459 gtk_color_editor_set_show_alpha (GtkColorEditor *editor,
460                                  gboolean        show_alpha)
461 {
462   if (editor->priv->show_alpha != show_alpha)
463     {
464       editor->priv->show_alpha = show_alpha;
465
466       gtk_widget_set_visible (editor->priv->a_slider, show_alpha);
467
468       gtk_color_swatch_set_show_alpha (GTK_COLOR_SWATCH (editor->priv->swatch), show_alpha);
469     }
470 }
471
472 static void
473 gtk_color_editor_set_property (GObject      *object,
474                                guint         prop_id,
475                                const GValue *value,
476                                GParamSpec   *pspec)
477 {
478   GtkColorEditor *ce = GTK_COLOR_EDITOR (object);
479   GtkColorChooser *cc = GTK_COLOR_CHOOSER (object);
480
481   switch (prop_id)
482     {
483     case PROP_COLOR:
484       gtk_color_chooser_set_color (cc, g_value_get_boxed (value));
485       break;
486     case PROP_SHOW_ALPHA:
487       gtk_color_editor_set_show_alpha (ce, g_value_get_boolean (value));
488       break;
489     default:
490       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
491       break;
492     }
493 }
494
495 static void
496 gtk_color_editor_finalize (GObject *object)
497 {
498   GtkColorEditor *editor = GTK_COLOR_EDITOR (object);
499
500   if (editor->priv->h_surface)
501     cairo_surface_destroy (editor->priv->h_surface);
502   if (editor->priv->a_surface)
503     cairo_surface_destroy (editor->priv->a_surface);
504
505   G_OBJECT_CLASS (gtk_color_editor_parent_class)->finalize (object);
506 }
507
508 static void
509 gtk_color_editor_class_init (GtkColorEditorClass *class)
510 {
511   GObjectClass *object_class = G_OBJECT_CLASS (class);
512
513   object_class->finalize = gtk_color_editor_finalize;
514   object_class->get_property = gtk_color_editor_get_property;
515   object_class->set_property = gtk_color_editor_set_property;
516
517   g_object_class_override_property (object_class, PROP_COLOR, "color");
518   g_object_class_override_property (object_class, PROP_SHOW_ALPHA, "show-alpha");
519
520   g_type_class_add_private (class, sizeof (GtkColorEditorPrivate));
521 }
522
523 static void
524 gtk_color_editor_get_color (GtkColorChooser *chooser,
525                             GdkRGBA         *color)
526 {
527   GtkColorEditor *editor = GTK_COLOR_EDITOR (chooser);
528
529   color->red = editor->priv->color.red;
530   color->green = editor->priv->color.green;
531   color->blue = editor->priv->color.blue;
532   color->alpha = editor->priv->color.alpha;
533 }
534
535 static void
536 gtk_color_editor_set_color (GtkColorChooser *chooser,
537                             const GdkRGBA   *color)
538 {
539   GtkColorEditor *editor = GTK_COLOR_EDITOR (chooser);
540   gdouble h, s, v;
541
542   editor->priv->color.red = color->red;
543   editor->priv->color.green = color->green;
544   editor->priv->color.blue = color->blue;
545   editor->priv->color.alpha = color->alpha;
546   gtk_rgb_to_hsv (color->red, color->green, color->blue, &h, &s, &v);
547   editor->priv->h = h;
548   editor->priv->s = s;
549   editor->priv->v = v;
550   gtk_color_plane_set_h (GTK_COLOR_PLANE (editor->priv->sv_plane), h);
551   gtk_color_plane_set_s (GTK_COLOR_PLANE (editor->priv->sv_plane), s);
552   gtk_color_plane_set_v (GTK_COLOR_PLANE (editor->priv->sv_plane), v);
553   gtk_color_swatch_set_color (GTK_COLOR_SWATCH (editor->priv->swatch), color);
554
555   gtk_adjustment_set_value (editor->priv->h_adj, h);
556   gtk_adjustment_set_value (editor->priv->a_adj, color->alpha);
557   update_entry (editor);
558
559   if (editor->priv->a_surface)
560     {
561       cairo_surface_destroy (editor->priv->a_surface);
562       editor->priv->a_surface = NULL;
563     }
564   gtk_widget_queue_draw (GTK_WIDGET (editor));
565
566   g_object_notify (G_OBJECT (editor), "color");
567 }
568
569 static void
570 gtk_color_editor_iface_init (GtkColorChooserInterface *iface)
571 {
572   iface->get_color = gtk_color_editor_get_color;
573   iface->set_color = gtk_color_editor_set_color;
574 }
575
576 GtkWidget *
577 gtk_color_editor_new (void)
578 {
579   return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_EDITOR, NULL);
580 }