1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2012 Red Hat, Inc.
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.
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.
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.
22 * - accessible color names
23 * - accessible relations for popups
24 * - api to replace palette
25 * - saving per-application (?)
27 * - use css for swatches
28 * - better popup theming
33 #include "gtkcoloreditorprivate.h"
35 #include "gtkcolorchooserprivate.h"
36 #include "gtkcolorplaneprivate.h"
37 #include "gtkcolorscaleprivate.h"
38 #include "gtkcolorswatchprivate.h"
39 #include "gtkcolorutils.h"
41 #include "gtkorientable.h"
43 #include "gtkoverlay.h"
44 #include "gtkadjustment.h"
46 #include "gtkspinbutton.h"
47 #include "gtkalignment.h"
52 struct _GtkColorEditorPrivate
68 GtkWidget *current_popup;
69 GtkWidget *popdown_focus;
76 guint text_changed : 1;
87 static void gtk_color_editor_iface_init (GtkColorChooserInterface *iface);
89 G_DEFINE_TYPE_WITH_CODE (GtkColorEditor, gtk_color_editor, GTK_TYPE_BOX,
90 G_IMPLEMENT_INTERFACE (GTK_TYPE_COLOR_CHOOSER,
91 gtk_color_editor_iface_init))
94 scale_round (gdouble value, gdouble scale)
96 value = floor (value * scale + 0.5);
97 value = MAX (value, 0);
98 value = MIN (value, scale);
103 entry_set_rgba (GtkColorEditor *editor,
104 const GdkRGBA *color)
108 text = g_strdup_printf ("#%02X%02X%02X",
109 scale_round (color->red, 255),
110 scale_round (color->green, 255),
111 scale_round (color->blue, 255));
112 gtk_entry_set_text (GTK_ENTRY (editor->priv->entry), text);
113 editor->priv->text_changed = FALSE;
118 entry_apply (GtkWidget *entry,
119 GtkColorEditor *editor)
124 if (!editor->priv->text_changed)
127 text = gtk_editable_get_chars (GTK_EDITABLE (editor->priv->entry), 0, -1);
128 if (gdk_rgba_parse (&color, text))
130 color.alpha = gtk_adjustment_get_value (editor->priv->a_adj);
131 gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (editor), &color);
134 editor->priv->text_changed = FALSE;
140 entry_focus_out (GtkWidget *entry,
141 GdkEventFocus *event,
142 GtkColorEditor *editor)
144 entry_apply (entry, editor);
149 entry_text_changed (GtkWidget *entry,
151 GtkColorEditor *editor)
153 editor->priv->text_changed = TRUE;
157 hsv_changed (GtkColorEditor *editor)
162 h = gtk_adjustment_get_value (editor->priv->h_adj);
163 s = gtk_adjustment_get_value (editor->priv->s_adj);
164 v = gtk_adjustment_get_value (editor->priv->v_adj);
165 a = gtk_adjustment_get_value (editor->priv->a_adj);
167 gtk_hsv_to_rgb (h, s, v, &color.red, &color.green, &color.blue);
170 gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (editor->priv->swatch), &color);
171 gtk_color_scale_set_rgba (GTK_COLOR_SCALE (editor->priv->a_slider), &color);
172 entry_set_rgba (editor, &color);
174 g_object_notify (G_OBJECT (editor), "rgba");
178 dismiss_current_popup (GtkColorEditor *editor)
180 if (editor->priv->current_popup)
182 gtk_widget_hide (editor->priv->current_popup);
183 editor->priv->current_popup = NULL;
184 if (editor->priv->popdown_focus)
186 gtk_widget_grab_focus (editor->priv->popdown_focus);
187 editor->priv->popdown_focus = NULL;
193 popup_edit (GtkWidget *widget,
194 GtkColorEditor *editor)
196 GtkWidget *popup = NULL;
200 if (widget == editor->priv->sv_plane)
202 popup = editor->priv->sv_popup;
203 focus = editor->priv->s_entry;
205 else if (widget == editor->priv->h_slider)
207 popup = editor->priv->h_popup;
208 focus = editor->priv->h_entry;
210 else if (widget == editor->priv->a_slider)
212 popup = editor->priv->a_popup;
213 focus = editor->priv->a_entry;
218 dismiss_current_popup (editor);
219 toplevel = gtk_widget_get_toplevel (GTK_WIDGET (editor));
220 editor->priv->popdown_focus = gtk_window_get_focus (GTK_WINDOW (toplevel));
221 editor->priv->current_popup = popup;
222 gtk_widget_show (popup);
223 gtk_widget_grab_focus (focus);
228 popup_key_press (GtkWidget *popup,
230 GtkColorEditor *editor)
232 if (event->keyval == GDK_KEY_Escape)
234 dismiss_current_popup (editor);
242 get_child_position (GtkOverlay *overlay,
244 GtkAllocation *allocation,
245 GtkColorEditor *editor)
251 gtk_widget_get_preferred_size (widget, &req, NULL);
253 allocation->width = req.width;
254 allocation->height = req.height;
256 if (widget == editor->priv->sv_popup)
258 if (gtk_widget_get_direction (GTK_WIDGET (overlay)) == GTK_TEXT_DIR_RTL)
261 allocation->x = gtk_widget_get_allocated_width (GTK_WIDGET (overlay)) - req.width;
262 allocation->y = req.height / 3;
264 else if (widget == editor->priv->h_popup)
266 gtk_widget_get_allocation (editor->priv->h_slider, &alloc);
267 gtk_range_get_slider_range (GTK_RANGE (editor->priv->h_slider), &s, &e);
269 if (gtk_widget_get_direction (GTK_WIDGET (overlay)) == GTK_TEXT_DIR_RTL)
270 gtk_widget_translate_coordinates (editor->priv->h_slider,
271 gtk_widget_get_parent (editor->priv->grid),
272 - req.width, (s + e - req.height) / 2,
273 &allocation->x, &allocation->y);
275 gtk_widget_translate_coordinates (editor->priv->h_slider,
276 gtk_widget_get_parent (editor->priv->grid),
277 alloc.width, (s + e - req.height) / 2,
278 &allocation->x, &allocation->y);
280 else if (widget == editor->priv->a_popup)
282 gtk_widget_get_allocation (editor->priv->a_slider, &alloc);
283 gtk_range_get_slider_range (GTK_RANGE (editor->priv->a_slider), &s, &e);
285 gtk_widget_translate_coordinates (editor->priv->a_slider,
286 gtk_widget_get_parent (editor->priv->grid),
287 (s + e - req.width) / 2, - req.height,
288 &allocation->x, &allocation->y);
293 allocation->x = CLAMP (allocation->x, 0, gtk_widget_get_allocated_width (GTK_WIDGET (overlay)) - req.width);
294 allocation->y = CLAMP (allocation->y, 0, gtk_widget_get_allocated_height (GTK_WIDGET (overlay)) - req.height);
300 value_changed (GtkAdjustment *a,
305 scale = gtk_adjustment_get_upper (as) / gtk_adjustment_get_upper (a);
306 g_signal_handlers_block_by_func (as, value_changed, a);
307 gtk_adjustment_set_value (as, gtk_adjustment_get_value (a) * scale);
308 g_signal_handlers_unblock_by_func (as, value_changed, a);
311 static GtkAdjustment *
312 scaled_adjustment (GtkAdjustment *a,
317 as = gtk_adjustment_new (gtk_adjustment_get_value (a) * scale,
318 gtk_adjustment_get_lower (a) * scale,
319 gtk_adjustment_get_upper (a) * scale,
320 gtk_adjustment_get_step_increment (a) * scale,
321 gtk_adjustment_get_page_increment (a) * scale,
322 gtk_adjustment_get_page_size (a) * scale);
324 g_signal_connect (a, "value-changed", G_CALLBACK (value_changed), as);
325 g_signal_connect (as, "value-changed", G_CALLBACK (value_changed), a);
331 popup_draw (GtkWidget *popup,
333 GtkColorEditor *editor)
335 GtkStyleContext *context;
338 context = gtk_widget_get_style_context (popup);
339 width = gtk_widget_get_allocated_width (popup);
340 height = gtk_widget_get_allocated_height (popup);
342 gtk_render_background (context, cr, 0, 0, width, height);
343 gtk_render_frame (context, cr, 0, 0, width, height);
349 create_popup (GtkColorEditor *editor,
355 popup = gtk_alignment_new (0.5, 0.5, 0, 0);
356 gtk_alignment_set_padding (GTK_ALIGNMENT (popup), 12, 12, 12, 12);
357 gtk_style_context_add_class (gtk_widget_get_style_context (popup), GTK_STYLE_CLASS_TOOLTIP);
359 gtk_container_add (GTK_CONTAINER (popup), contents);
361 gtk_widget_show_all (popup);
362 gtk_widget_hide (popup);
363 gtk_widget_set_no_show_all (popup, TRUE);
365 g_signal_connect (popup, "draw", G_CALLBACK (popup_draw), editor);
367 gtk_overlay_add_overlay (GTK_OVERLAY (editor->priv->overlay), popup);
368 g_signal_connect (attach, "popup-menu", G_CALLBACK (popup_edit), editor);
374 gtk_color_editor_init (GtkColorEditor *editor)
378 GtkWidget *alignment;
381 GtkAdjustment *h_adj, *s_adj, *v_adj, *a_adj;
383 GdkRGBA transparent = { 0, 0, 0, 0 };
385 editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (editor,
386 GTK_TYPE_COLOR_EDITOR,
387 GtkColorEditorPrivate);
388 editor->priv->use_alpha = TRUE;
390 editor->priv->h_adj = h_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
391 editor->priv->s_adj = s_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
392 editor->priv->v_adj = v_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
393 editor->priv->a_adj = a_adj = gtk_adjustment_new (0, 0, 1, 0.01, 0.1, 0);
395 g_object_ref_sink (h_adj);
396 g_object_ref_sink (s_adj);
397 g_object_ref_sink (v_adj);
398 g_object_ref_sink (a_adj);
400 g_signal_connect_swapped (h_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
401 g_signal_connect_swapped (s_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
402 g_signal_connect_swapped (v_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
403 g_signal_connect_swapped (a_adj, "value-changed", G_CALLBACK (hsv_changed), editor);
405 gtk_widget_push_composite_child ();
407 /* Construct the main UI */
408 editor->priv->swatch = swatch = gtk_color_swatch_new ();
409 gtk_widget_set_sensitive (swatch, FALSE);
411 editor->priv->entry = entry = gtk_entry_new ();
412 atk_obj = gtk_widget_get_accessible (entry);
413 atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
414 atk_object_set_name (atk_obj, _("Color Name"));
415 g_signal_connect (entry, "activate", G_CALLBACK (entry_apply), editor);
416 g_signal_connect (entry, "notify::text", G_CALLBACK (entry_text_changed), editor);
417 g_signal_connect (entry, "focus-out-event", G_CALLBACK (entry_focus_out), editor);
419 editor->priv->h_slider = slider = gtk_color_scale_new (h_adj, GTK_COLOR_SCALE_HUE);
420 gtk_orientable_set_orientation (GTK_ORIENTABLE (slider), GTK_ORIENTATION_VERTICAL);
421 if (gtk_widget_get_direction (slider) == GTK_TEXT_DIR_RTL)
422 gtk_style_context_add_class (gtk_widget_get_style_context (slider),
423 GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
425 gtk_style_context_add_class (gtk_widget_get_style_context (slider),
426 GTK_STYLE_CLASS_SCALE_HAS_MARKS_BELOW);
428 editor->priv->sv_plane = gtk_color_plane_new (h_adj, s_adj, v_adj);
429 gtk_widget_set_size_request (editor->priv->sv_plane, 300, 300);
431 editor->priv->a_slider = slider = gtk_color_scale_new (a_adj, GTK_COLOR_SCALE_ALPHA);
432 gtk_orientable_set_orientation (GTK_ORIENTABLE (slider), GTK_ORIENTATION_HORIZONTAL);
433 gtk_style_context_add_class (gtk_widget_get_style_context (slider),
434 GTK_STYLE_CLASS_SCALE_HAS_MARKS_ABOVE);
436 editor->priv->grid = grid = gtk_grid_new ();
437 gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
438 gtk_grid_set_column_spacing (GTK_GRID (grid), 12);
440 gtk_grid_attach (GTK_GRID (grid), editor->priv->swatch, 1, 0, 1, 1);
441 gtk_grid_attach (GTK_GRID (grid), editor->priv->entry, 2, 0, 1, 1);
442 gtk_grid_attach (GTK_GRID (grid), editor->priv->h_slider, 0, 1, 1, 1);
443 gtk_grid_attach (GTK_GRID (grid), editor->priv->sv_plane, 1, 1, 2, 1);
444 gtk_grid_attach (GTK_GRID (grid), editor->priv->a_slider, 1, 2, 2, 1);
446 /* This extra alignment is necessary so we have room to the sides
447 * to place the popups as desired
449 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
450 gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 0, 0, 30, 30);
451 gtk_container_add (GTK_CONTAINER (alignment), grid);
453 editor->priv->overlay = gtk_overlay_new ();
454 gtk_widget_override_background_color (editor->priv->overlay, 0, &transparent);
455 gtk_container_add (GTK_CONTAINER (editor->priv->overlay), alignment);
457 /* Construct the sv popup */
458 editor->priv->s_entry = entry = gtk_spin_button_new (scaled_adjustment (s_adj, 100), 1, 0);
459 atk_obj = gtk_widget_get_accessible (entry);
460 atk_object_set_name (atk_obj, C_("Color channel", "Saturation"));
461 atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
462 g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
464 editor->priv->v_entry = entry = gtk_spin_button_new (scaled_adjustment (v_adj, 100), 1, 0);
465 atk_obj = gtk_widget_get_accessible (entry);
466 atk_object_set_name (atk_obj, C_("Color channel", "Value"));
467 atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
468 g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
470 grid = gtk_grid_new ();
471 gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
473 gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "S")), 0, 0, 1, 1);
474 gtk_grid_attach (GTK_GRID (grid), editor->priv->s_entry, 1, 0, 1, 1);
475 gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "V")), 0, 1, 1, 1);
476 gtk_grid_attach (GTK_GRID (grid), editor->priv->v_entry, 1, 1, 1, 1);
478 editor->priv->sv_popup = create_popup (editor, editor->priv->sv_plane, grid);
480 /* Construct the h popup */
481 editor->priv->h_entry = entry = gtk_spin_button_new (scaled_adjustment (h_adj, 100), 1, 0);
482 atk_obj = gtk_widget_get_accessible (entry);
483 atk_object_set_name (atk_obj, C_("Color channel", "Hue"));
484 atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
485 g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
487 grid = gtk_grid_new ();
488 gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
490 gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "H")), 0, 0, 1, 1);
491 gtk_grid_attach (GTK_GRID (grid), editor->priv->h_entry, 1, 0, 1, 1);
493 editor->priv->h_popup = create_popup (editor, editor->priv->h_slider, grid);
495 /* Construct the a popup */
496 editor->priv->a_entry = entry = gtk_spin_button_new (scaled_adjustment (a_adj, 100), 1, 0);
497 atk_obj = gtk_widget_get_accessible (entry);
498 atk_object_set_name (atk_obj, C_("Color channel", "Alpha"));
499 atk_object_set_role (atk_obj, ATK_ROLE_ENTRY);
500 g_signal_connect (entry, "key-press-event", G_CALLBACK (popup_key_press), editor);
502 grid = gtk_grid_new ();
503 gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
505 gtk_grid_attach (GTK_GRID (grid), gtk_label_new (C_("Color channel", "A")), 0, 0, 1, 1);
506 gtk_grid_attach (GTK_GRID (grid), editor->priv->a_entry, 1, 0, 1, 1);
508 editor->priv->a_popup = create_popup (editor, editor->priv->a_slider, grid);
510 /* Hook up popup positioning */
511 g_signal_connect (editor->priv->overlay, "get-child-position", G_CALLBACK (get_child_position), editor);
512 g_signal_connect (editor, "notify::visible", G_CALLBACK (dismiss_current_popup), NULL);
514 gtk_widget_show_all (editor->priv->overlay);
515 gtk_container_add (GTK_CONTAINER (editor), editor->priv->overlay);
517 gtk_widget_pop_composite_child ();
521 gtk_color_editor_get_property (GObject *object,
526 GtkColorEditor *ce = GTK_COLOR_EDITOR (object);
527 GtkColorChooser *cc = GTK_COLOR_CHOOSER (object);
534 gtk_color_chooser_get_rgba (cc, &color);
535 g_value_set_boxed (value, &color);
539 g_value_set_boolean (value, gtk_widget_get_visible (ce->priv->a_slider));
542 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
548 gtk_color_editor_set_use_alpha (GtkColorEditor *editor,
551 if (editor->priv->use_alpha != use_alpha)
553 editor->priv->use_alpha = use_alpha;
554 gtk_widget_set_visible (editor->priv->a_slider, use_alpha);
555 gtk_color_swatch_set_use_alpha (GTK_COLOR_SWATCH (editor->priv->swatch), use_alpha);
560 gtk_color_editor_set_property (GObject *object,
565 GtkColorEditor *ce = GTK_COLOR_EDITOR (object);
566 GtkColorChooser *cc = GTK_COLOR_CHOOSER (object);
571 gtk_color_chooser_set_rgba (cc, g_value_get_boxed (value));
574 gtk_color_editor_set_use_alpha (ce, g_value_get_boolean (value));
577 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
583 gtk_color_editor_finalize (GObject *object)
585 GtkColorEditor *editor = GTK_COLOR_EDITOR (object);
587 g_clear_object (&editor->priv->h_adj);
588 g_clear_object (&editor->priv->s_adj);
589 g_clear_object (&editor->priv->v_adj);
590 g_clear_object (&editor->priv->a_adj);
592 G_OBJECT_CLASS (gtk_color_editor_parent_class)->finalize (object);
596 gtk_color_editor_class_init (GtkColorEditorClass *class)
598 GObjectClass *object_class = G_OBJECT_CLASS (class);
600 object_class->finalize = gtk_color_editor_finalize;
601 object_class->get_property = gtk_color_editor_get_property;
602 object_class->set_property = gtk_color_editor_set_property;
604 g_object_class_override_property (object_class, PROP_RGBA, "rgba");
605 g_object_class_override_property (object_class, PROP_USE_ALPHA, "use-alpha");
607 g_type_class_add_private (class, sizeof (GtkColorEditorPrivate));
611 gtk_color_editor_get_rgba (GtkColorChooser *chooser,
614 GtkColorEditor *editor = GTK_COLOR_EDITOR (chooser);
617 h = gtk_adjustment_get_value (editor->priv->h_adj);
618 s = gtk_adjustment_get_value (editor->priv->s_adj);
619 v = gtk_adjustment_get_value (editor->priv->v_adj);
620 gtk_hsv_to_rgb (h, s, v, &color->red, &color->green, &color->blue);
621 color->alpha = gtk_adjustment_get_value (editor->priv->a_adj);
625 gtk_color_editor_set_rgba (GtkColorChooser *chooser,
626 const GdkRGBA *color)
628 GtkColorEditor *editor = GTK_COLOR_EDITOR (chooser);
631 gtk_rgb_to_hsv (color->red, color->green, color->blue, &h, &s, &v);
633 gtk_adjustment_set_value (editor->priv->h_adj, h);
634 gtk_adjustment_set_value (editor->priv->s_adj, s);
635 gtk_adjustment_set_value (editor->priv->v_adj, v);
636 gtk_adjustment_set_value (editor->priv->a_adj, color->alpha);
638 gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (editor->priv->swatch), color);
639 gtk_color_scale_set_rgba (GTK_COLOR_SCALE (editor->priv->a_slider), color);
640 entry_set_rgba (editor, color);
642 g_object_notify (G_OBJECT (editor), "rgba");
646 gtk_color_editor_iface_init (GtkColorChooserInterface *iface)
648 iface->get_rgba = gtk_color_editor_get_rgba;
649 iface->set_rgba = gtk_color_editor_set_rgba;
653 gtk_color_editor_new (void)
655 return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_EDITOR, NULL);