]> Pileus Git - ~andy/gtk/blob - gtk/gtkcolorswatch.c
colorchooser: factor out a private method to get the checkboard pattern
[~andy/gtk] / gtk / gtkcolorswatch.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 "gtkcolorswatchprivate.h"
23
24 #include "gtkcolorchooserprivate.h"
25 #include "gtkroundedboxprivate.h"
26 #include "gtkthemingbackgroundprivate.h"
27 #include "gtkdnd.h"
28 #include "gtkicontheme.h"
29 #include "gtkmain.h"
30 #include "gtkmenu.h"
31 #include "gtkmenuitem.h"
32 #include "gtkmenushell.h"
33 #include "gtkprivate.h"
34 #include "gtkintl.h"
35
36
37 struct _GtkColorSwatchPrivate
38 {
39   GdkRGBA color;
40   gdouble radius[4];
41   gchar *icon;
42   guint    has_color        : 1;
43   guint    contains_pointer : 1;
44   guint    use_alpha        : 1;
45 };
46
47 enum
48 {
49   PROP_ZERO,
50   PROP_RGBA
51 };
52
53 enum
54 {
55   ACTIVATE,
56   CUSTOMIZE,
57   LAST_SIGNAL
58 };
59
60 static guint signals[LAST_SIGNAL];
61
62 G_DEFINE_TYPE (GtkColorSwatch, gtk_color_swatch, GTK_TYPE_DRAWING_AREA)
63
64 static void
65 gtk_color_swatch_init (GtkColorSwatch *swatch)
66 {
67   swatch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swatch,
68                                               GTK_TYPE_COLOR_SWATCH,
69                                               GtkColorSwatchPrivate);
70
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
74                                               | GDK_EXPOSURE_MASK
75                                               | GDK_ENTER_NOTIFY_MASK
76                                               | GDK_LEAVE_NOTIFY_MASK);
77   swatch->priv->use_alpha = TRUE;
78 }
79
80 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
81
82 static gboolean
83 swatch_draw (GtkWidget *widget,
84              cairo_t   *cr)
85 {
86   GtkColorSwatch *swatch = (GtkColorSwatch*)widget;
87   GtkThemingBackground background;
88   gdouble width, height;
89   GtkStyleContext *context;
90   GtkStateFlags state;
91   GtkIconTheme *theme;
92   GtkIconInfo *icon_info = NULL;
93
94   theme = gtk_icon_theme_get_default ();
95   context = gtk_widget_get_style_context (widget);
96   state = gtk_widget_get_state_flags (widget);
97   width = gtk_widget_get_allocated_width (widget);
98   height = gtk_widget_get_allocated_height (widget);
99
100   cairo_save (cr);
101
102   gtk_style_context_save (context);
103   gtk_style_context_set_state (context, state);
104
105   _gtk_theming_background_init_from_context (&background, context,
106                                              0, 0, width, height,
107                                              GTK_JUNCTION_NONE);
108
109   if (swatch->priv->has_color)
110     {
111       cairo_pattern_t *pattern;
112       cairo_matrix_t matrix;
113
114       if (swatch->priv->use_alpha)
115         {
116           cairo_save (cr);
117
118           _gtk_rounded_box_path (&background.clip_box, cr);
119           cairo_clip_preserve (cr);
120
121           cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
122           cairo_fill_preserve (cr);
123
124           pattern = _gtk_color_chooser_get_checkered_pattern ();
125           cairo_matrix_init_scale (&matrix, 0.125, 0.125);
126           cairo_pattern_set_matrix (pattern, &matrix);
127
128           cairo_set_source_rgb (cr, 0.66, 0.66, 0.66);
129           cairo_mask (cr, pattern);
130           cairo_pattern_destroy (pattern);
131
132           cairo_restore (cr);
133
134           background.bg_color = swatch->priv->color;
135         }
136       else
137         {
138           background.bg_color = swatch->priv->color;
139           background.bg_color.alpha = 1.0;
140         }
141
142       _gtk_theming_background_render (&background, cr);
143     }
144
145   gtk_render_frame (context, cr,
146                     0, 0, width, height);
147
148   if (gtk_widget_has_visible_focus (widget))
149     {
150       cairo_set_line_width (cr, 2);
151       if (swatch->priv->has_color && INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) < 0.5)
152         cairo_set_source_rgba (cr, 1., 1., 1., 0.4);
153       else
154         cairo_set_source_rgba (cr, 0., 0., 0., 0.4);
155       _gtk_rounded_box_shrink (&background.clip_box, 3, 3, 3, 3);
156       _gtk_rounded_box_path (&background.clip_box, cr);
157       cairo_stroke (cr);
158     }
159
160   if (swatch->priv->icon)
161     {
162       icon_info = gtk_icon_theme_lookup_icon (theme, swatch->priv->icon, 16,
163                                               GTK_ICON_LOOKUP_GENERIC_FALLBACK
164                                               | GTK_ICON_LOOKUP_USE_BUILTIN);
165     }
166   else if ((state & GTK_STATE_FLAG_SELECTED) != 0)
167     {
168       GdkRGBA bg, border;
169       GtkBorder border_width;
170       GIcon *gicon;
171
172       gtk_style_context_add_class (context, "color-active-badge");
173       gtk_style_context_get_background_color (context, state, &bg);
174       gtk_style_context_get_border_color (context, state, &border);
175       gtk_style_context_get_border (context, state, &border_width);
176
177       cairo_new_sub_path (cr);
178       cairo_arc (cr, width / 2, height / 2, 10, 0, 2 * G_PI);
179       cairo_close_path (cr);
180       gdk_cairo_set_source_rgba (cr, &bg);
181       cairo_fill_preserve (cr);
182
183       gdk_cairo_set_source_rgba (cr, &border);
184       cairo_set_line_width (cr, border_width.left);
185       cairo_stroke (cr);
186
187       gicon = g_themed_icon_new ("object-select-symbolic");
188       /* fallback for themes that don't have object-select-symbolic */
189       g_themed_icon_append_name (G_THEMED_ICON (gicon), "gtk-apply");
190
191       icon_info = gtk_icon_theme_lookup_by_gicon (theme, gicon, 16,
192                                                   GTK_ICON_LOOKUP_GENERIC_FALLBACK
193                                                   | GTK_ICON_LOOKUP_USE_BUILTIN);
194       g_object_unref (gicon);
195     }
196
197   if (icon_info != NULL)
198     {
199       GdkPixbuf *pixbuf;
200
201       pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context,
202                                                         NULL, NULL);
203
204       if (pixbuf != NULL)
205         {
206           gtk_render_icon (context, cr, pixbuf,
207                            (width - gdk_pixbuf_get_width (pixbuf)) / 2,
208                            (height - gdk_pixbuf_get_height (pixbuf)) / 2);
209           g_object_unref (pixbuf);
210         }
211
212       gtk_icon_info_free (icon_info);
213     }
214
215   cairo_restore (cr);
216   gtk_style_context_restore (context);
217
218   return FALSE;
219 }
220
221 static void
222 drag_set_color_icon (GdkDragContext *context,
223                      const GdkRGBA  *color)
224 {
225   cairo_surface_t *surface;
226   cairo_t *cr;
227
228   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 48, 32);
229   cr = cairo_create (surface);
230   gdk_cairo_set_source_rgba (cr, color);
231   cairo_paint (cr);
232
233   cairo_surface_set_device_offset (surface, -4, -4);
234   gtk_drag_set_icon_surface (context, surface);
235
236   cairo_destroy (cr);
237   cairo_surface_destroy (surface);
238 }
239
240 static void
241 swatch_drag_begin (GtkWidget      *widget,
242                    GdkDragContext *context)
243 {
244   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
245   GdkRGBA color;
246
247   gtk_color_swatch_get_rgba (swatch, &color);
248   drag_set_color_icon (context, &color);
249 }
250
251 static void
252 swatch_drag_data_get (GtkWidget        *widget,
253                       GdkDragContext   *context,
254                       GtkSelectionData *selection_data,
255                       guint             info,
256                       guint             time)
257 {
258   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
259   guint16 vals[4];
260   GdkRGBA color;
261
262   gtk_color_swatch_get_rgba (swatch, &color);
263
264   vals[0] = color.red * 0xffff;
265   vals[1] = color.green * 0xffff;
266   vals[2] = color.blue * 0xffff;
267   vals[3] = color.alpha * 0xffff;
268
269   gtk_selection_data_set (selection_data,
270                           gdk_atom_intern_static_string ("application/x-color"),
271                           16, (guchar *)vals, 8);
272 }
273
274 static void
275 swatch_drag_data_received (GtkWidget        *widget,
276                            GdkDragContext   *context,
277                            gint              x,
278                            gint              y,
279                            GtkSelectionData *selection_data,
280                            guint             info,
281                            guint             time)
282 {
283   gint length;
284   guint16 *vals;
285   GdkRGBA color;
286
287   length = gtk_selection_data_get_length (selection_data);
288
289   if (length < 0)
290     return;
291
292   /* We accept drops with the wrong format, since the KDE color
293    * chooser incorrectly drops application/x-color with format 8.
294    */
295   if (length != 8)
296     {
297       g_warning ("Received invalid color data\n");
298       return;
299     }
300
301   vals = (guint16 *) gtk_selection_data_get_data (selection_data);
302
303   color.red   = (gdouble)vals[0] / 0xffff;
304   color.green = (gdouble)vals[1] / 0xffff;
305   color.blue  = (gdouble)vals[2] / 0xffff;
306   color.alpha = (gdouble)vals[3] / 0xffff;
307
308   gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (widget), &color);
309 }
310
311 static void
312 swatch_get_preferred_width (GtkWidget *widget,
313                             gint      *min,
314                             gint      *nat)
315 {
316   *min = *nat = 48;
317 }
318
319 static void
320 swatch_get_preferred_height (GtkWidget *widget,
321                              gint      *min,
322                              gint      *nat)
323 {
324   *min = *nat = 32;
325 }
326
327 static gboolean
328 swatch_key_press (GtkWidget   *widget,
329                   GdkEventKey *event)
330 {
331   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
332
333   if (event->keyval == GDK_KEY_space ||
334       event->keyval == GDK_KEY_Return ||
335       event->keyval == GDK_KEY_ISO_Enter||
336       event->keyval == GDK_KEY_KP_Enter ||
337       event->keyval == GDK_KEY_KP_Space)
338     {
339       if (swatch->priv->has_color && (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_SELECTED) == 0)
340         gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE);
341       else
342         g_signal_emit (swatch, signals[ACTIVATE], 0);
343       return TRUE;
344     }
345
346   if (GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->key_press_event (widget, event))
347     return TRUE;
348
349   return FALSE;
350 }
351
352 static gboolean
353 swatch_enter_notify (GtkWidget        *widget,
354                      GdkEventCrossing *event)
355 {
356   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
357   swatch->priv->contains_pointer = TRUE;
358   return FALSE;
359 }
360
361 static gboolean
362 swatch_leave_notify (GtkWidget        *widget,
363                      GdkEventCrossing *event)
364 {
365   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
366   swatch->priv->contains_pointer = FALSE;
367   return FALSE;
368 }
369
370 static void
371 emit_customize (GtkColorSwatch *swatch)
372 {
373   g_signal_emit (swatch, signals[CUSTOMIZE], 0);
374 }
375
376 static void
377 popup_position_func (GtkMenu   *menu,
378                      gint      *x,
379                      gint      *y,
380                      gboolean  *push_in,
381                      gpointer   user_data)
382 {
383   GtkWidget *widget;
384   GtkRequisition req;
385   gint root_x, root_y;
386   GdkScreen *screen;
387   GdkWindow *window;
388   GdkRectangle monitor;
389   gint monitor_num;
390
391   widget = GTK_WIDGET (user_data);
392   g_return_if_fail (gtk_widget_get_realized (widget));
393   window = gtk_widget_get_window (widget);
394
395   screen = gtk_widget_get_screen (widget);
396   monitor_num = gdk_screen_get_monitor_at_window (screen, window);
397   if (monitor_num < 0)
398     monitor_num = 0;
399   gtk_menu_set_monitor (menu, monitor_num);
400
401   gdk_window_get_origin (window, &root_x, &root_y);
402   gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL);
403
404   /* Put corner of menu centered on swatch */
405   *x = root_x + gtk_widget_get_allocated_width (widget) / 2;
406   *y = root_y + gtk_widget_get_allocated_height (widget) / 2;
407
408   /* Ensure sanity */
409   gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor);
410   *x = CLAMP (*x, monitor.x, MAX (monitor.x, monitor.width - req.width));
411   *y = CLAMP (*y, monitor.y, MAX (monitor.y, monitor.height - req.height));
412 }
413
414 static void
415 do_popup (GtkWidget      *swatch,
416           GdkEventButton *event)
417 {
418   GtkWidget *menu;
419   GtkWidget *item;
420
421   menu = gtk_menu_new ();
422   item = gtk_menu_item_new_with_mnemonic (_("_Customize"));
423   gtk_menu_attach_to_widget (GTK_MENU (menu), swatch, NULL);
424
425   g_signal_connect_swapped (item, "activate",
426                             G_CALLBACK (emit_customize), swatch);
427
428   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
429
430   gtk_widget_show_all (item);
431
432   if (event)
433     gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
434                     NULL, NULL, event->button, event->time);
435   else
436     gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
437                     popup_position_func, swatch,
438                     0, gtk_get_current_event_time ());
439 }
440
441 static gboolean
442 swatch_button_press (GtkWidget      *widget,
443                      GdkEventButton *event)
444 {
445   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
446
447   gtk_widget_grab_focus (widget);
448
449   if (gdk_event_triggers_context_menu ((GdkEvent *) event) &&
450       swatch->priv->has_color)
451     {
452       do_popup (widget, event);
453       return TRUE;
454     }
455   else if (event->type == GDK_2BUTTON_PRESS &&
456            event->button == GDK_BUTTON_PRIMARY)
457     {
458       g_signal_emit (swatch, signals[ACTIVATE], 0);
459       return TRUE;
460     }
461
462   return FALSE;
463 }
464
465 static gboolean
466 swatch_button_release (GtkWidget      *widget,
467                        GdkEventButton *event)
468 {
469   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
470   GtkStateFlags flags;
471
472   if (event->button == GDK_BUTTON_PRIMARY &&
473       swatch->priv->contains_pointer)
474     {
475       flags = gtk_widget_get_state_flags (widget);
476       if (!swatch->priv->has_color)
477         {
478           g_signal_emit (swatch, signals[ACTIVATE], 0);
479           return TRUE;
480         }
481       else if ((flags & GTK_STATE_FLAG_SELECTED) == 0)
482         {
483           gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE);
484           return TRUE;
485         }
486     }
487
488   return FALSE;
489 }
490
491 static gboolean
492 swatch_popup_menu (GtkWidget *swatch)
493 {
494   do_popup (swatch, NULL);
495   return TRUE;
496 }
497
498 /* GObject implementation {{{1 */
499
500 static void
501 swatch_get_property (GObject    *object,
502                      guint       prop_id,
503                      GValue     *value,
504                      GParamSpec *pspec)
505 {
506   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
507   GdkRGBA color;
508
509   switch (prop_id)
510     {
511     case PROP_RGBA:
512       gtk_color_swatch_get_rgba (swatch, &color);
513       g_value_set_boxed (value, &color);
514       break;
515     default:
516       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
517       break;
518     }
519 }
520
521 static void
522 swatch_set_property (GObject      *object,
523                      guint         prop_id,
524                      const GValue *value,
525                      GParamSpec   *pspec)
526 {
527   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
528
529   switch (prop_id)
530     {
531     case PROP_RGBA:
532       gtk_color_swatch_set_rgba (swatch, g_value_get_boxed (value));
533       break;
534     default:
535       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
536       break;
537     }
538 }
539
540 static void
541 swatch_finalize (GObject *object)
542 {
543   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
544
545   g_free (swatch->priv->icon);
546
547   G_OBJECT_CLASS (gtk_color_swatch_parent_class)->finalize (object);
548 }
549
550 static void
551 gtk_color_swatch_class_init (GtkColorSwatchClass *class)
552 {
553   GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
554   GObjectClass *object_class = (GObjectClass *)class;
555
556   object_class->get_property = swatch_get_property;
557   object_class->set_property = swatch_set_property;
558   object_class->finalize = swatch_finalize;
559
560   widget_class->get_preferred_width = swatch_get_preferred_width;
561   widget_class->get_preferred_height = swatch_get_preferred_height;
562   widget_class->draw = swatch_draw;
563   widget_class->drag_begin = swatch_drag_begin;
564   widget_class->drag_data_get = swatch_drag_data_get;
565   widget_class->drag_data_received = swatch_drag_data_received;
566   widget_class->key_press_event = swatch_key_press;
567   widget_class->popup_menu = swatch_popup_menu;
568   widget_class->button_press_event = swatch_button_press;
569   widget_class->button_release_event = swatch_button_release;
570   widget_class->enter_notify_event = swatch_enter_notify;
571   widget_class->leave_notify_event = swatch_leave_notify;
572
573   signals[ACTIVATE] =
574     g_signal_new ("activate",
575                   GTK_TYPE_COLOR_SWATCH,
576                   G_SIGNAL_RUN_FIRST,
577                   G_STRUCT_OFFSET (GtkColorSwatchClass, activate),
578                   NULL, NULL, NULL, G_TYPE_NONE, 0);
579
580   signals[CUSTOMIZE] =
581     g_signal_new ("customize",
582                   GTK_TYPE_COLOR_SWATCH,
583                   G_SIGNAL_RUN_FIRST,
584                   G_STRUCT_OFFSET (GtkColorSwatchClass, customize),
585                   NULL, NULL, NULL, G_TYPE_NONE, 0);
586
587   g_object_class_install_property (object_class, PROP_RGBA,
588       g_param_spec_boxed ("rgba", P_("RGBA Color"), P_("Color as RGBA"),
589                           GDK_TYPE_RGBA, GTK_PARAM_READWRITE));
590
591   g_type_class_add_private (object_class, sizeof (GtkColorSwatchPrivate));
592 }
593
594 /* Public API {{{1 */
595
596 GtkWidget *
597 gtk_color_swatch_new (void)
598 {
599   return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_SWATCH, NULL);
600 }
601
602 static const GtkTargetEntry dnd_targets[] = {
603   { "application/x-color", 0 }
604 };
605
606 void
607 gtk_color_swatch_set_rgba (GtkColorSwatch *swatch,
608                            const GdkRGBA  *color)
609 {
610   GtkStyleContext *context;
611
612   context = gtk_widget_get_style_context (GTK_WIDGET (swatch));
613
614   if (!swatch->priv->has_color)
615     {
616       gtk_drag_source_set (GTK_WIDGET (swatch),
617                            GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
618                            dnd_targets, G_N_ELEMENTS (dnd_targets),
619                            GDK_ACTION_COPY | GDK_ACTION_MOVE);
620     }
621   else
622     {
623       gtk_style_context_remove_class (context, "color-light");
624       gtk_style_context_remove_class (context, "color-dark");
625     }
626
627   swatch->priv->has_color = TRUE;
628   swatch->priv->color = *color;
629
630   if (INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) > 0.5)
631     gtk_style_context_add_class (context, "color-light");
632   else
633     gtk_style_context_add_class (context, "color-dark");
634
635   gtk_widget_queue_draw (GTK_WIDGET (swatch));
636   g_object_notify (G_OBJECT (swatch), "rgba");
637 }
638
639 gboolean
640 gtk_color_swatch_get_rgba (GtkColorSwatch *swatch,
641                            GdkRGBA        *color)
642 {
643   if (swatch->priv->has_color)
644     {
645       color->red = swatch->priv->color.red;
646       color->green = swatch->priv->color.green;
647       color->blue = swatch->priv->color.blue;
648       color->alpha = swatch->priv->color.alpha;
649       return TRUE;
650     }
651   else
652     {
653       color->red = 1.0;
654       color->green = 1.0;
655       color->blue = 1.0;
656       color->alpha = 1.0;
657       return FALSE;
658     }
659 }
660
661 void
662 gtk_color_swatch_set_icon (GtkColorSwatch *swatch,
663                            const gchar    *icon)
664 {
665   swatch->priv->icon = g_strdup (icon);
666   gtk_widget_queue_draw (GTK_WIDGET (swatch));
667 }
668
669 void
670 gtk_color_swatch_set_can_drop (GtkColorSwatch *swatch,
671                                gboolean        can_drop)
672 {
673   if (can_drop)
674     {
675       gtk_drag_dest_set (GTK_WIDGET (swatch),
676                          GTK_DEST_DEFAULT_HIGHLIGHT |
677                          GTK_DEST_DEFAULT_MOTION |
678                          GTK_DEST_DEFAULT_DROP,
679                          dnd_targets, G_N_ELEMENTS (dnd_targets),
680                          GDK_ACTION_COPY);
681     }
682   else
683     {
684       gtk_drag_dest_unset (GTK_WIDGET (swatch));
685     }
686 }
687
688 void
689 gtk_color_swatch_set_use_alpha (GtkColorSwatch *swatch,
690                                 gboolean        use_alpha)
691 {
692   swatch->priv->use_alpha = use_alpha;
693   gtk_widget_queue_draw (GTK_WIDGET (swatch));
694 }
695
696 /* vim:set foldmethod=marker: */