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