]> Pileus Git - ~andy/gtk/blob - gtk/gtkcolorswatch.c
bf5edf654dbd446effcf033fbe287ab3350edf7a
[~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     {
456       do_popup (widget, event);
457       return TRUE;
458     }
459   else if (event->button == GDK_BUTTON_PRIMARY &&
460            swatch->priv->selected)
461     {
462       g_signal_emit (swatch, signals[ACTIVATE], 0);
463       return TRUE;
464     }
465
466   return FALSE;
467 }
468
469 static gboolean
470 swatch_button_release (GtkWidget      *widget,
471                        GdkEventButton *event)
472 {
473   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
474
475   gtk_widget_grab_focus (GTK_WIDGET (swatch));
476
477   if (event->button == GDK_BUTTON_PRIMARY &&
478       swatch->priv->contains_pointer)
479     {
480       if (!swatch->priv->has_color)
481         {
482           g_signal_emit (swatch, signals[ACTIVATE], 0);
483           return TRUE;
484         }
485       else if (!swatch->priv->selected)
486         {
487           gtk_color_swatch_set_selected (swatch, TRUE);
488           return TRUE;
489         }
490     }
491
492   return FALSE;
493 }
494
495 static gboolean
496 swatch_menu (GtkWidget *swatch)
497 {
498   do_popup (swatch, NULL);
499   return TRUE;
500 }
501
502 static void
503 gtk_color_swatch_class_init (GtkColorSwatchClass *class)
504 {
505   GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
506   GObjectClass *object_class = (GObjectClass *)class;
507
508   object_class->get_property = swatch_get_property;
509   object_class->set_property = swatch_set_property;
510   object_class->finalize = swatch_finalize;
511
512   widget_class->get_preferred_width = swatch_get_preferred_width;
513   widget_class->get_preferred_height = swatch_get_preferred_height;
514   widget_class->draw = swatch_draw;
515   widget_class->drag_begin = swatch_drag_begin;
516   widget_class->drag_data_get = swatch_drag_data_get;
517   widget_class->drag_data_received = swatch_drag_data_received;
518   widget_class->key_press_event = swatch_key_press;
519   widget_class->popup_menu = swatch_menu;
520   widget_class->button_press_event = swatch_button_press;
521   widget_class->button_release_event = swatch_button_release;
522   widget_class->enter_notify_event = swatch_enter;
523   widget_class->leave_notify_event = swatch_leave;
524
525   signals[ACTIVATE] =
526     g_signal_new ("activate",
527                   GTK_TYPE_COLOR_SWATCH,
528                   G_SIGNAL_RUN_FIRST,
529                   G_STRUCT_OFFSET (GtkColorSwatchClass, activate),
530                   NULL, NULL, NULL, G_TYPE_NONE, 0);
531
532   signals[CUSTOMIZE] =
533     g_signal_new ("customize",
534                   GTK_TYPE_COLOR_SWATCH,
535                   G_SIGNAL_RUN_FIRST,
536                   G_STRUCT_OFFSET (GtkColorSwatchClass, customize),
537                   NULL, NULL, NULL, G_TYPE_NONE, 0);
538
539   g_object_class_install_property (object_class, PROP_COLOR,
540       g_param_spec_boxed ("color", P_("Color"), P_("Color"),
541                           GDK_TYPE_RGBA, GTK_PARAM_READWRITE));
542
543   g_object_class_install_property (object_class, PROP_SELECTED,
544       g_param_spec_boolean ("selected", P_("Selected"), P_("Selected"),
545                             FALSE, GTK_PARAM_READWRITE));
546
547   g_type_class_add_private (object_class, sizeof (GtkColorSwatchPrivate));
548 }
549
550
551 /* Public API {{{1 */
552
553 GtkWidget *
554 gtk_color_swatch_new (void)
555 {
556   return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_SWATCH, NULL);
557 }
558
559 void
560 gtk_color_swatch_set_color (GtkColorSwatch *swatch,
561                             const GdkRGBA  *color)
562 {
563   static const GtkTargetEntry targets[] = {
564     { "application/x-color", 0 }
565   };
566
567   if (!swatch->priv->has_color)
568     gtk_drag_source_set (GTK_WIDGET (swatch),
569                          GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
570                          targets, 1,
571                          GDK_ACTION_COPY | GDK_ACTION_MOVE);
572
573   swatch->priv->has_color = TRUE;
574   swatch->priv->color.red = color->red;
575   swatch->priv->color.green = color->green;
576   swatch->priv->color.blue = color->blue;
577   swatch->priv->color.alpha = color->alpha;
578
579   gtk_widget_queue_draw (GTK_WIDGET (swatch));
580   g_object_notify (G_OBJECT (swatch), "color");
581 }
582
583 gboolean
584 gtk_color_swatch_get_color (GtkColorSwatch *swatch,
585                             GdkRGBA        *color)
586 {
587   if (swatch->priv->has_color)
588     {
589       color->red = swatch->priv->color.red;
590       color->green = swatch->priv->color.green;
591       color->blue = swatch->priv->color.blue;
592       color->alpha = swatch->priv->color.alpha;
593       return TRUE;
594     }
595   else
596     {
597       color->red = 1.0;
598       color->green = 1.0;
599       color->blue = 1.0;
600       color->alpha = 1.0;
601       return FALSE;
602     }
603 }
604
605 void
606 gtk_color_swatch_set_corner_radii (GtkColorSwatch *swatch,
607                                    gdouble         top_left,
608                                    gdouble         top_right,
609                                    gdouble         bottom_right,
610                                    gdouble         bottom_left)
611 {
612   swatch->priv->radius[0] = top_left;
613   swatch->priv->radius[1] = top_right;
614   swatch->priv->radius[2] = bottom_right;
615   swatch->priv->radius[3] = bottom_left;
616
617   gtk_widget_queue_draw (GTK_WIDGET (swatch));
618 }
619
620 void
621 gtk_color_swatch_set_selected (GtkColorSwatch *swatch,
622                                gboolean        selected)
623 {
624   if (swatch->priv->selected != selected)
625     {
626       swatch->priv->selected = selected;
627       gtk_widget_queue_draw (GTK_WIDGET (swatch));
628       g_object_notify (G_OBJECT (swatch), "selected");
629     }
630 }
631
632 void
633 gtk_color_swatch_set_icon (GtkColorSwatch *swatch,
634                            const gchar    *icon)
635 {
636   swatch->priv->icon = g_strdup (icon);
637   gtk_widget_queue_draw (GTK_WIDGET (swatch));
638 }
639
640 void
641 gtk_color_swatch_set_can_drop (GtkColorSwatch *swatch,
642                                gboolean        can_drop)
643 {
644   static const GtkTargetEntry targets[] = {
645     { "application/x-color", 0 }
646   };
647
648   if (!swatch->priv->can_drop)
649     gtk_drag_dest_set (GTK_WIDGET (swatch),
650                        GTK_DEST_DEFAULT_HIGHLIGHT |
651                        GTK_DEST_DEFAULT_MOTION |
652                        GTK_DEST_DEFAULT_DROP,
653                        targets, 1,
654                        GDK_ACTION_COPY);
655
656   swatch->priv->can_drop = can_drop;
657 }
658
659 /* vim:set foldmethod=marker: */