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