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 #include "gtkcolorswatch.h"
23 #include "gtkroundedboxprivate.h"
25 #include "gtkicontheme.h"
28 #include "gtkmenuitem.h"
29 #include "gtkmenushell.h"
30 #include "gtkbindings.h"
31 #include "gtkprivate.h"
35 struct _GtkColorSwatchPrivate
43 guint contains_pointer : 1;
60 static guint signals[LAST_SIGNAL];
62 G_DEFINE_TYPE (GtkColorSwatch, gtk_color_swatch, GTK_TYPE_DRAWING_AREA)
65 gtk_color_swatch_init (GtkColorSwatch *swatch)
67 swatch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swatch,
68 GTK_TYPE_COLOR_SWATCH,
69 GtkColorSwatchPrivate);
71 gtk_widget_set_can_focus (GTK_WIDGET (swatch), TRUE);
72 gtk_widget_set_events (GTK_WIDGET (swatch), GDK_BUTTON_PRESS_MASK
73 | GDK_BUTTON_RELEASE_MASK
75 | GDK_ENTER_NOTIFY_MASK
76 | GDK_LEAVE_NOTIFY_MASK);
80 swatch_finalize (GObject *object)
82 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
84 g_free (swatch->priv->icon);
86 G_OBJECT_CLASS (gtk_color_swatch_parent_class)->finalize (object);
89 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
91 static cairo_pattern_t *
92 get_checkered_pattern (void)
94 /* need to respect pixman's stride being a multiple of 4 */
95 static unsigned char data[8] = { 0xFF, 0x00, 0x00, 0x00,
96 0x00, 0xFF, 0x00, 0x00 };
97 static cairo_surface_t *checkered = NULL;
98 cairo_pattern_t *pattern;
100 if (checkered == NULL)
101 checkered = cairo_image_surface_create_for_data (data,
105 pattern = cairo_pattern_create_for_surface (checkered);
106 cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
107 cairo_pattern_set_filter (pattern, CAIRO_FILTER_NEAREST);
113 swatch_draw (GtkWidget *widget,
116 GtkColorSwatch *swatch = (GtkColorSwatch*)widget;
119 gdouble width, height;
120 GtkStyleContext *context;
124 context = gtk_widget_get_style_context (widget);
125 state = gtk_widget_get_state_flags (widget);
126 width = gtk_widget_get_allocated_width (widget);
127 height = gtk_widget_get_allocated_height (widget);
129 gtk_style_context_save (context);
132 gtk_style_context_set_state (context, state);
134 _gtk_rounded_box_init_rect (&box, 0, 0, width, height);
135 for (i = 0; i < 4; i++)
136 box.corner[i].horizontal = box.corner[i].vertical = swatch->priv->radius[i];
138 _gtk_rounded_box_path (&box, cr);
140 if (swatch->priv->has_color)
142 cairo_pattern_t *pattern;
143 cairo_matrix_t matrix;
145 cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
146 cairo_fill_preserve (cr);
148 cairo_set_source_rgb (cr, 0.66, 0.66, 0.66);
150 pattern = get_checkered_pattern ();
151 cairo_matrix_init_scale (&matrix, 0.125, 0.125);
152 cairo_pattern_set_matrix (pattern, &matrix);
153 cairo_clip_preserve (cr);
154 cairo_mask (cr, pattern);
155 cairo_pattern_destroy (pattern);
157 gdk_cairo_set_source_rgba (cr, &swatch->priv->color);
158 cairo_fill_preserve (cr);
161 cairo_set_source_rgb (cr, 0.4, 0.4, 0.4);
162 cairo_set_line_width (cr, 1);
165 if (gtk_widget_has_visible_focus (widget))
167 cairo_set_line_width (cr, 2);
168 if (swatch->priv->has_color && INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) < 0.5)
169 cairo_set_source_rgba (cr, 1., 1., 1., 0.4);
171 cairo_set_source_rgba (cr, 0., 0., 0., 0.4);
172 _gtk_rounded_box_shrink (&box, 3, 3, 3, 3);
173 _gtk_rounded_box_path (&box, cr);
177 if (swatch->priv->icon)
181 theme = gtk_icon_theme_get_default ();
182 pixbuf = gtk_icon_theme_load_icon (theme, "list-add-symbolic", 16,
183 GTK_ICON_LOOKUP_GENERIC_FALLBACK
184 | GTK_ICON_LOOKUP_USE_BUILTIN,
187 gtk_render_icon (context, cr, pixbuf,
188 (width - gdk_pixbuf_get_width (pixbuf)) / 2,
189 (height - gdk_pixbuf_get_height (pixbuf)) / 2);
190 g_object_unref (pixbuf);
192 else if (swatch->priv->selected)
194 gtk_style_context_get_background_color (context, state, &bg);
195 cairo_new_sub_path (cr);
196 cairo_arc (cr, width / 2, height / 2, 10, 0, 2 * G_PI);
197 cairo_close_path (cr);
198 gdk_cairo_set_source_rgba (cr, &bg);
199 cairo_fill_preserve (cr);
200 if (INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) > 0.5)
201 cairo_set_source_rgba (cr, 0., 0., 0., 0.4);
203 cairo_set_source_rgba (cr, 1., 1., 1., 0.4);
204 cairo_set_line_width (cr, 2);
206 gtk_style_context_set_state (context, state | GTK_STATE_FLAG_ACTIVE);
207 gtk_render_check (context, cr, width / 2 - 5, height / 2 - 5, 10, 10);
211 gtk_style_context_restore (context);
217 drag_set_color_icon (GdkDragContext *context,
218 const GdkRGBA *color)
220 cairo_surface_t *surface;
223 surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 48, 32);
224 cr = cairo_create (surface);
225 gdk_cairo_set_source_rgba (cr, color);
228 cairo_surface_set_device_offset (surface, -2, -2);
229 gtk_drag_set_icon_surface (context, surface);
232 cairo_surface_destroy (surface);
236 swatch_drag_begin (GtkWidget *widget,
237 GdkDragContext *context)
239 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
242 gtk_color_swatch_get_color (swatch, &color);
243 drag_set_color_icon (context, &color);
247 swatch_drag_data_get (GtkWidget *widget,
248 GdkDragContext *context,
249 GtkSelectionData *selection_data,
253 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
257 gtk_color_swatch_get_color (swatch, &color);
259 vals[0] = color.red * 0xffff;
260 vals[1] = color.green * 0xffff;
261 vals[2] = color.blue * 0xffff;
262 vals[3] = color.alpha * 0xffff;
264 gtk_selection_data_set (selection_data,
265 gdk_atom_intern_static_string ("application/x-color"),
266 16, (guchar *)vals, 8);
270 swatch_drag_data_received (GtkWidget *widget,
271 GdkDragContext *context,
274 GtkSelectionData *selection_data,
282 length = gtk_selection_data_get_length (selection_data);
287 /* We accept drops with the wrong format, since the KDE color
288 * chooser incorrectly drops application/x-color with format 8.
292 g_warning ("Received invalid color data\n");
296 vals = (guint16 *) gtk_selection_data_get_data (selection_data);
298 color.red = (gdouble)vals[0] / 0xffff;
299 color.green = (gdouble)vals[1] / 0xffff;
300 color.blue = (gdouble)vals[2] / 0xffff;
301 color.alpha = (gdouble)vals[3] / 0xffff;
303 gtk_color_swatch_set_color (GTK_COLOR_SWATCH (widget), &color);
307 swatch_get_property (GObject *object,
312 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
318 gtk_color_swatch_get_color (swatch, &color);
319 g_value_set_boxed (value, &color);
322 g_value_set_boolean (value, swatch->priv->selected);
325 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
331 swatch_set_property (GObject *object,
336 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
341 gtk_color_swatch_set_color (swatch, g_value_get_boxed (value));
344 gtk_color_swatch_set_selected (swatch, g_value_get_boolean (value));
347 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
353 swatch_get_preferred_width (GtkWidget *widget,
361 swatch_get_preferred_height (GtkWidget *widget,
369 swatch_key_press (GtkWidget *widget,
372 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
374 if (event->keyval == GDK_KEY_space ||
375 event->keyval == GDK_KEY_Return ||
376 event->keyval == GDK_KEY_ISO_Enter||
377 event->keyval == GDK_KEY_KP_Enter ||
378 event->keyval == GDK_KEY_KP_Space)
380 if (swatch->priv->has_color && !swatch->priv->selected)
381 gtk_color_swatch_set_selected (swatch, TRUE);
383 g_signal_emit (swatch, signals[ACTIVATE], 0);
387 if (GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->key_press_event (widget, event))
394 swatch_enter (GtkWidget *widget,
395 GdkEventCrossing *event)
397 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
398 swatch->priv->contains_pointer = TRUE;
403 swatch_leave (GtkWidget *widget,
404 GdkEventCrossing *event)
406 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
407 swatch->priv->contains_pointer = FALSE;
412 emit_customize (GtkColorSwatch *swatch)
414 g_signal_emit (swatch, signals[CUSTOMIZE], 0);
418 popup_position_func (GtkMenu *menu,
429 GdkRectangle monitor;
432 widget = GTK_WIDGET (user_data);
433 g_return_if_fail (gtk_widget_get_realized (widget));
434 window = gtk_widget_get_window (widget);
436 screen = gtk_widget_get_screen (widget);
437 monitor_num = gdk_screen_get_monitor_at_window (screen, window);
440 gtk_menu_set_monitor (menu, monitor_num);
442 gdk_window_get_origin (window, &root_x, &root_y);
443 gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL);
445 /* Put corner of menu centered on swatch */
446 *x = root_x + gtk_widget_get_allocated_width (widget) / 2;
447 *y = root_y + gtk_widget_get_allocated_height (widget) / 2;
450 gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor);
451 *x = CLAMP (*x, monitor.x, MAX (monitor.x, monitor.width - req.width));
452 *y = CLAMP (*y, monitor.y, MAX (monitor.y, monitor.height - req.height));
456 do_popup (GtkWidget *swatch,
457 GdkEventButton *event)
462 menu = gtk_menu_new ();
463 item = gtk_menu_item_new_with_mnemonic (_("_Customize"));
464 gtk_menu_attach_to_widget (GTK_MENU (menu), swatch, NULL);
466 g_signal_connect_swapped (item, "activate",
467 G_CALLBACK (emit_customize), swatch);
469 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
471 gtk_widget_show_all (item);
474 gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
475 NULL, NULL, event->button, event->time);
477 gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
478 popup_position_func, swatch,
479 0, gtk_get_current_event_time ());
483 swatch_button_press (GtkWidget *widget,
484 GdkEventButton *event)
486 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
488 gtk_widget_grab_focus (GTK_WIDGET (swatch));
490 if (gdk_event_triggers_context_menu ((GdkEvent *) event) &&
491 swatch->priv->has_color)
493 do_popup (widget, event);
496 else if (event->button == GDK_BUTTON_PRIMARY &&
497 swatch->priv->selected)
499 g_signal_emit (swatch, signals[ACTIVATE], 0);
507 swatch_button_release (GtkWidget *widget,
508 GdkEventButton *event)
510 GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
512 gtk_widget_grab_focus (GTK_WIDGET (swatch));
514 if (event->button == GDK_BUTTON_PRIMARY &&
515 swatch->priv->contains_pointer)
517 if (!swatch->priv->has_color)
519 g_signal_emit (swatch, signals[ACTIVATE], 0);
522 else if (!swatch->priv->selected)
524 gtk_color_swatch_set_selected (swatch, TRUE);
533 swatch_menu (GtkWidget *swatch)
535 do_popup (swatch, NULL);
540 gtk_color_swatch_class_init (GtkColorSwatchClass *class)
542 GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
543 GObjectClass *object_class = (GObjectClass *)class;
545 object_class->get_property = swatch_get_property;
546 object_class->set_property = swatch_set_property;
547 object_class->finalize = swatch_finalize;
549 widget_class->get_preferred_width = swatch_get_preferred_width;
550 widget_class->get_preferred_height = swatch_get_preferred_height;
551 widget_class->draw = swatch_draw;
552 widget_class->drag_begin = swatch_drag_begin;
553 widget_class->drag_data_get = swatch_drag_data_get;
554 widget_class->drag_data_received = swatch_drag_data_received;
555 widget_class->key_press_event = swatch_key_press;
556 widget_class->popup_menu = swatch_menu;
557 widget_class->button_press_event = swatch_button_press;
558 widget_class->button_release_event = swatch_button_release;
559 widget_class->enter_notify_event = swatch_enter;
560 widget_class->leave_notify_event = swatch_leave;
563 g_signal_new ("activate",
564 GTK_TYPE_COLOR_SWATCH,
566 G_STRUCT_OFFSET (GtkColorSwatchClass, activate),
567 NULL, NULL, NULL, G_TYPE_NONE, 0);
570 g_signal_new ("customize",
571 GTK_TYPE_COLOR_SWATCH,
573 G_STRUCT_OFFSET (GtkColorSwatchClass, customize),
574 NULL, NULL, NULL, G_TYPE_NONE, 0);
576 g_object_class_install_property (object_class, PROP_COLOR,
577 g_param_spec_boxed ("color", P_("Color"), P_("Color"),
578 GDK_TYPE_RGBA, GTK_PARAM_READWRITE));
580 g_object_class_install_property (object_class, PROP_SELECTED,
581 g_param_spec_boolean ("selected", P_("Selected"), P_("Selected"),
582 FALSE, GTK_PARAM_READWRITE));
584 g_type_class_add_private (object_class, sizeof (GtkColorSwatchPrivate));
588 /* Public API {{{1 */
591 gtk_color_swatch_new (void)
593 return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_SWATCH, NULL);
597 gtk_color_swatch_set_color (GtkColorSwatch *swatch,
598 const GdkRGBA *color)
600 static const GtkTargetEntry targets[] = {
601 { "application/x-color", 0 }
604 if (!swatch->priv->has_color)
605 gtk_drag_source_set (GTK_WIDGET (swatch),
606 GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
608 GDK_ACTION_COPY | GDK_ACTION_MOVE);
610 swatch->priv->has_color = TRUE;
611 swatch->priv->color.red = color->red;
612 swatch->priv->color.green = color->green;
613 swatch->priv->color.blue = color->blue;
614 swatch->priv->color.alpha = color->alpha;
616 gtk_widget_queue_draw (GTK_WIDGET (swatch));
617 g_object_notify (G_OBJECT (swatch), "color");
621 gtk_color_swatch_get_color (GtkColorSwatch *swatch,
624 if (swatch->priv->has_color)
626 color->red = swatch->priv->color.red;
627 color->green = swatch->priv->color.green;
628 color->blue = swatch->priv->color.blue;
629 color->alpha = swatch->priv->color.alpha;
643 gtk_color_swatch_set_corner_radii (GtkColorSwatch *swatch,
646 gdouble bottom_right,
649 swatch->priv->radius[0] = top_left;
650 swatch->priv->radius[1] = top_right;
651 swatch->priv->radius[2] = bottom_right;
652 swatch->priv->radius[3] = bottom_left;
654 gtk_widget_queue_draw (GTK_WIDGET (swatch));
658 gtk_color_swatch_set_selected (GtkColorSwatch *swatch,
661 if (swatch->priv->selected != selected)
663 swatch->priv->selected = selected;
664 gtk_widget_queue_draw (GTK_WIDGET (swatch));
665 g_object_notify (G_OBJECT (swatch), "selected");
670 gtk_color_swatch_set_icon (GtkColorSwatch *swatch,
673 swatch->priv->icon = g_strdup (icon);
674 gtk_widget_queue_draw (GTK_WIDGET (swatch));
678 gtk_color_swatch_set_can_drop (GtkColorSwatch *swatch,
681 static const GtkTargetEntry targets[] = {
682 { "application/x-color", 0 }
685 if (!swatch->priv->can_drop)
686 gtk_drag_dest_set (GTK_WIDGET (swatch),
687 GTK_DEST_DEFAULT_HIGHLIGHT |
688 GTK_DEST_DEFAULT_MOTION |
689 GTK_DEST_DEFAULT_DROP,
693 swatch->priv->can_drop = can_drop;
696 /* vim:set foldmethod=marker: */