]> Pileus Git - ~andy/gtk/blob - gtk/gtkcolorswatch.c
stylecontext: Do invalidation on first resize container
[~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, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include "config.h"
19
20 #include "gtkcolorswatchprivate.h"
21
22 #include "gtkcolorchooserprivate.h"
23 #include "gtkroundedboxprivate.h"
24 #include "gtkthemingbackgroundprivate.h"
25 #include "gtkdnd.h"
26 #include "gtkicontheme.h"
27 #include "gtkmain.h"
28 #include "gtkmenu.h"
29 #include "gtkmenuitem.h"
30 #include "gtkmenushell.h"
31 #include "gtkpressandholdprivate.h"
32 #include "gtkprivate.h"
33 #include "gtkintl.h"
34 #include "a11y/gtkcolorswatchaccessibleprivate.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   guint    selectable       : 1;
46
47   GdkWindow *event_window;
48
49   GtkPressAndHold *press_and_hold;
50 };
51
52 enum
53 {
54   PROP_ZERO,
55   PROP_RGBA,
56   PROP_SELECTABLE
57 };
58
59 enum
60 {
61   ACTIVATE,
62   CUSTOMIZE,
63   LAST_SIGNAL
64 };
65
66 static guint signals[LAST_SIGNAL];
67
68 G_DEFINE_TYPE (GtkColorSwatch, gtk_color_swatch, GTK_TYPE_WIDGET)
69
70 static void
71 gtk_color_swatch_init (GtkColorSwatch *swatch)
72 {
73   swatch->priv = G_TYPE_INSTANCE_GET_PRIVATE (swatch,
74                                               GTK_TYPE_COLOR_SWATCH,
75                                               GtkColorSwatchPrivate);
76
77   gtk_widget_set_can_focus (GTK_WIDGET (swatch), TRUE);
78   gtk_widget_set_has_window (GTK_WIDGET (swatch), FALSE);
79
80   swatch->priv->use_alpha = TRUE;
81   swatch->priv->selectable = TRUE;
82 }
83
84 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
85 #define ACTIVE_BADGE_RADIUS 10
86
87 static gboolean
88 swatch_draw (GtkWidget *widget,
89              cairo_t   *cr)
90 {
91   GtkColorSwatch *swatch = (GtkColorSwatch*)widget;
92   GtkThemingBackground background;
93   gdouble width, height;
94   GtkStyleContext *context;
95   GtkStateFlags state;
96   GtkIconTheme *theme;
97   GtkIconInfo *icon_info = NULL;
98
99   theme = gtk_icon_theme_get_default ();
100   context = gtk_widget_get_style_context (widget);
101   state = gtk_widget_get_state_flags (widget);
102   width = gtk_widget_get_allocated_width (widget);
103   height = gtk_widget_get_allocated_height (widget);
104
105   cairo_save (cr);
106
107   gtk_style_context_save (context);
108   gtk_style_context_set_state (context, state);
109
110   _gtk_theming_background_init_from_context (&background, context,
111                                              0, 0, width, height,
112                                              GTK_JUNCTION_NONE);
113
114   if (swatch->priv->has_color)
115     {
116       cairo_pattern_t *pattern;
117       cairo_matrix_t matrix;
118
119       if (swatch->priv->use_alpha)
120         {
121           cairo_save (cr);
122
123           _gtk_rounded_box_path (&background.padding_box, cr);
124           cairo_clip_preserve (cr);
125
126           cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
127           cairo_fill_preserve (cr);
128
129           pattern = _gtk_color_chooser_get_checkered_pattern ();
130           cairo_matrix_init_scale (&matrix, 0.125, 0.125);
131           cairo_pattern_set_matrix (pattern, &matrix);
132
133           cairo_set_source_rgb (cr, 0.66, 0.66, 0.66);
134           cairo_mask (cr, pattern);
135           cairo_pattern_destroy (pattern);
136
137           cairo_restore (cr);
138
139           background.bg_color = swatch->priv->color;
140         }
141       else
142         {
143           background.bg_color = swatch->priv->color;
144           background.bg_color.alpha = 1.0;
145         }
146
147       _gtk_theming_background_render (&background, cr);
148     }
149   else
150     _gtk_theming_background_render (&background, cr);
151
152   gtk_render_frame (context, cr,
153                     0, 0, width, height);
154
155   if (gtk_widget_has_visible_focus (widget))
156     {
157       cairo_set_line_width (cr, 2);
158       if (swatch->priv->has_color && INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) < 0.5)
159         cairo_set_source_rgba (cr, 1., 1., 1., 0.4);
160       else
161         cairo_set_source_rgba (cr, 0., 0., 0., 0.4);
162         _gtk_rounded_box_shrink (&background.padding_box, 3, 3, 3, 3);
163         _gtk_rounded_box_path (&background.padding_box, cr);
164         cairo_stroke (cr);
165     }
166
167   if (swatch->priv->icon)
168     {
169       icon_info = gtk_icon_theme_lookup_icon (theme, swatch->priv->icon, 16,
170                                               GTK_ICON_LOOKUP_GENERIC_FALLBACK
171                                               | GTK_ICON_LOOKUP_USE_BUILTIN);
172     }
173   else if ((state & GTK_STATE_FLAG_SELECTED) != 0)
174     {
175       GdkRGBA bg, border;
176       GtkBorder border_width;
177       GIcon *gicon;
178
179       gtk_style_context_add_class (context, "color-active-badge");
180       _gtk_theming_background_init_from_context (&background, context,
181                                                  (width - 2 * ACTIVE_BADGE_RADIUS) / 2, (height - 2 * ACTIVE_BADGE_RADIUS) / 2,
182                                                  2 * ACTIVE_BADGE_RADIUS, 2* ACTIVE_BADGE_RADIUS,
183                                                  GTK_JUNCTION_NONE);
184
185       if (_gtk_theming_background_has_background_image (&background))
186         {
187           _gtk_theming_background_render (&background, cr);
188         }
189       else
190         {
191           gtk_style_context_get_background_color (context, state, &bg);
192           gtk_style_context_get_border_color (context, state, &border);
193           gtk_style_context_get_border (context, state, &border_width);
194
195           cairo_new_sub_path (cr);
196           cairo_arc (cr, width / 2, height / 2, ACTIVE_BADGE_RADIUS, 0, 2 * G_PI);
197           cairo_close_path (cr);
198           gdk_cairo_set_source_rgba (cr, &bg);
199           cairo_fill_preserve (cr);
200
201           gdk_cairo_set_source_rgba (cr, &border);
202           cairo_set_line_width (cr, border_width.left);
203           cairo_stroke (cr);
204
205           gicon = g_themed_icon_new ("object-select-symbolic");
206           /* fallback for themes that don't have object-select-symbolic */
207           g_themed_icon_append_name (G_THEMED_ICON (gicon), "gtk-apply");
208
209           icon_info = gtk_icon_theme_lookup_by_gicon (theme, gicon, 16,
210                                                       GTK_ICON_LOOKUP_GENERIC_FALLBACK
211                                                       | GTK_ICON_LOOKUP_USE_BUILTIN);
212           g_object_unref (gicon);
213         }
214     }
215
216   if (icon_info != NULL)
217     {
218       GdkPixbuf *pixbuf;
219
220       pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context,
221                                                         NULL, NULL);
222
223       if (pixbuf != NULL)
224         {
225           gtk_render_icon (context, cr, pixbuf,
226                            (width - gdk_pixbuf_get_width (pixbuf)) / 2,
227                            (height - gdk_pixbuf_get_height (pixbuf)) / 2);
228           g_object_unref (pixbuf);
229         }
230
231       g_object_unref (icon_info);
232     }
233
234   cairo_restore (cr);
235   gtk_style_context_restore (context);
236
237   return FALSE;
238 }
239
240 static void
241 drag_set_color_icon (GdkDragContext *context,
242                      const GdkRGBA  *color)
243 {
244   cairo_surface_t *surface;
245   cairo_t *cr;
246
247   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 48, 32);
248   cr = cairo_create (surface);
249   gdk_cairo_set_source_rgba (cr, color);
250   cairo_paint (cr);
251
252   cairo_surface_set_device_offset (surface, -4, -4);
253   gtk_drag_set_icon_surface (context, surface);
254
255   cairo_destroy (cr);
256   cairo_surface_destroy (surface);
257 }
258
259 static void
260 swatch_drag_begin (GtkWidget      *widget,
261                    GdkDragContext *context)
262 {
263   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
264   GdkRGBA color;
265
266   gtk_color_swatch_get_rgba (swatch, &color);
267   drag_set_color_icon (context, &color);
268 }
269
270 static void
271 swatch_drag_data_get (GtkWidget        *widget,
272                       GdkDragContext   *context,
273                       GtkSelectionData *selection_data,
274                       guint             info,
275                       guint             time)
276 {
277   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
278   guint16 vals[4];
279   GdkRGBA color;
280
281   gtk_color_swatch_get_rgba (swatch, &color);
282
283   vals[0] = color.red * 0xffff;
284   vals[1] = color.green * 0xffff;
285   vals[2] = color.blue * 0xffff;
286   vals[3] = color.alpha * 0xffff;
287
288   gtk_selection_data_set (selection_data,
289                           gdk_atom_intern_static_string ("application/x-color"),
290                           16, (guchar *)vals, 8);
291 }
292
293 static void
294 swatch_drag_data_received (GtkWidget        *widget,
295                            GdkDragContext   *context,
296                            gint              x,
297                            gint              y,
298                            GtkSelectionData *selection_data,
299                            guint             info,
300                            guint             time)
301 {
302   gint length;
303   guint16 *vals;
304   GdkRGBA color;
305
306   length = gtk_selection_data_get_length (selection_data);
307
308   if (length < 0)
309     return;
310
311   /* We accept drops with the wrong format, since the KDE color
312    * chooser incorrectly drops application/x-color with format 8.
313    */
314   if (length != 8)
315     {
316       g_warning ("Received invalid color data\n");
317       return;
318     }
319
320   vals = (guint16 *) gtk_selection_data_get_data (selection_data);
321
322   color.red   = (gdouble)vals[0] / 0xffff;
323   color.green = (gdouble)vals[1] / 0xffff;
324   color.blue  = (gdouble)vals[2] / 0xffff;
325   color.alpha = (gdouble)vals[3] / 0xffff;
326
327   gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (widget), &color);
328 }
329
330 static void
331 swatch_get_preferred_width (GtkWidget *widget,
332                             gint      *min,
333                             gint      *nat)
334 {
335   *min = *nat = 48;
336 }
337
338 static void
339 swatch_get_preferred_height (GtkWidget *widget,
340                              gint      *min,
341                              gint      *nat)
342 {
343   *min = *nat = 32;
344 }
345
346 static gboolean
347 swatch_key_press (GtkWidget   *widget,
348                   GdkEventKey *event)
349 {
350   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
351
352   if (event->keyval == GDK_KEY_space ||
353       event->keyval == GDK_KEY_Return ||
354       event->keyval == GDK_KEY_ISO_Enter||
355       event->keyval == GDK_KEY_KP_Enter ||
356       event->keyval == GDK_KEY_KP_Space)
357     {
358       if (swatch->priv->has_color && 
359           swatch->priv->selectable &&
360           (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_SELECTED) == 0)
361         gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE);
362       else
363         g_signal_emit (swatch, signals[ACTIVATE], 0);
364       return TRUE;
365     }
366
367   if (GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->key_press_event (widget, event))
368     return TRUE;
369
370   return FALSE;
371 }
372
373 static gboolean
374 swatch_enter_notify (GtkWidget        *widget,
375                      GdkEventCrossing *event)
376 {
377   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
378   swatch->priv->contains_pointer = TRUE;
379   gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
380
381   return FALSE;
382 }
383
384 static gboolean
385 swatch_leave_notify (GtkWidget        *widget,
386                      GdkEventCrossing *event)
387 {
388   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
389   swatch->priv->contains_pointer = FALSE;
390   gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
391
392   return FALSE;
393 }
394
395 static void
396 emit_customize (GtkColorSwatch *swatch)
397 {
398   g_signal_emit (swatch, signals[CUSTOMIZE], 0);
399 }
400
401 static void
402 popup_position_func (GtkMenu   *menu,
403                      gint      *x,
404                      gint      *y,
405                      gboolean  *push_in,
406                      gpointer   user_data)
407 {
408   GtkWidget *widget;
409   GtkRequisition req;
410   gint root_x, root_y;
411   GdkScreen *screen;
412   GdkWindow *window;
413   GdkRectangle monitor;
414   gint monitor_num;
415
416   widget = GTK_WIDGET (user_data);
417   g_return_if_fail (gtk_widget_get_realized (widget));
418   window = gtk_widget_get_window (widget);
419
420   screen = gtk_widget_get_screen (widget);
421   monitor_num = gdk_screen_get_monitor_at_window (screen, window);
422   if (monitor_num < 0)
423     monitor_num = 0;
424   gtk_menu_set_monitor (menu, monitor_num);
425
426   gdk_window_get_origin (window, &root_x, &root_y);
427   gtk_widget_get_preferred_size (GTK_WIDGET (menu), &req, NULL);
428
429   /* Put corner of menu centered on swatch */
430   *x = root_x + gtk_widget_get_allocated_width (widget) / 2;
431   *y = root_y + gtk_widget_get_allocated_height (widget) / 2;
432
433   /* Ensure sanity */
434   gdk_screen_get_monitor_workarea (screen, monitor_num, &monitor);
435   *x = CLAMP (*x, monitor.x, MAX (monitor.x, monitor.width - req.width));
436   *y = CLAMP (*y, monitor.y, MAX (monitor.y, monitor.height - req.height));
437 }
438
439 static void
440 do_popup (GtkWidget      *swatch,
441           GdkEventButton *event)
442 {
443   GtkWidget *menu;
444   GtkWidget *item;
445
446   menu = gtk_menu_new ();
447   item = gtk_menu_item_new_with_mnemonic (_("_Customize"));
448   gtk_menu_attach_to_widget (GTK_MENU (menu), swatch, NULL);
449
450   g_signal_connect_swapped (item, "activate",
451                             G_CALLBACK (emit_customize), swatch);
452
453   gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
454
455   gtk_widget_show_all (item);
456
457   if (event)
458     gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
459                     NULL, NULL, event->button, event->time);
460   else
461     gtk_menu_popup (GTK_MENU (menu), NULL, NULL,
462                     popup_position_func, swatch,
463                     0, gtk_get_current_event_time ());
464 }
465
466 static gboolean
467 swatch_button_press (GtkWidget      *widget,
468                      GdkEventButton *event)
469 {
470   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
471
472   gtk_widget_grab_focus (widget);
473
474   if (gdk_event_triggers_context_menu ((GdkEvent *) event) &&
475       swatch->priv->has_color)
476     {
477       do_popup (widget, event);
478       return TRUE;
479     }
480   else if (event->type == GDK_2BUTTON_PRESS &&
481            event->button == GDK_BUTTON_PRIMARY)
482     {
483       g_signal_emit (swatch, signals[ACTIVATE], 0);
484       return TRUE;
485     }
486
487   return FALSE;
488 }
489
490 static gboolean
491 swatch_primary_action (GtkColorSwatch *swatch)
492 {
493   GtkWidget *widget = (GtkWidget *)swatch;
494   GtkStateFlags flags;
495
496   flags = gtk_widget_get_state_flags (widget);
497   if (!swatch->priv->has_color)
498     {
499       g_signal_emit (swatch, signals[ACTIVATE], 0);
500       return TRUE;
501     }
502   else if (swatch->priv->selectable &&
503            (flags & GTK_STATE_FLAG_SELECTED) == 0)
504     {
505       gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE);
506       return TRUE;
507     }
508
509   return FALSE;
510 }
511
512 static gboolean
513 swatch_button_release (GtkWidget      *widget,
514                        GdkEventButton *event)
515 {
516   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
517
518   if (event->button == GDK_BUTTON_PRIMARY &&
519       swatch->priv->contains_pointer)
520     return swatch_primary_action (swatch);
521
522   return FALSE;
523 }
524
525 static void
526 hold_action (GtkPressAndHold *pah,
527              gint             x,
528              gint             y,
529              GtkColorSwatch  *swatch)
530 {
531   emit_customize (swatch);
532 }
533
534 static void
535 tap_action (GtkPressAndHold *pah,
536             gint             x,
537             gint             y,
538             GtkColorSwatch  *swatch)
539 {
540   swatch_primary_action (swatch);
541 }
542
543 static gboolean
544 swatch_touch (GtkWidget     *widget,
545               GdkEventTouch *event)
546 {
547   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
548
549   if (!swatch->priv->press_and_hold)
550     {
551       gint drag_threshold;
552
553       g_object_get (gtk_widget_get_settings (widget),
554                     "gtk-dnd-drag-threshold", &drag_threshold,
555                     NULL);
556
557       swatch->priv->press_and_hold = gtk_press_and_hold_new ();
558
559       g_object_set (swatch->priv->press_and_hold,
560                     "drag-threshold", drag_threshold,
561                     "hold-time", 1000,
562                     NULL);
563
564       g_signal_connect (swatch->priv->press_and_hold, "hold",
565                         G_CALLBACK (hold_action), swatch);
566       g_signal_connect (swatch->priv->press_and_hold, "tap",
567                         G_CALLBACK (tap_action), swatch);
568     }
569
570   gtk_press_and_hold_process_event (swatch->priv->press_and_hold, (GdkEvent *)event);
571
572   return TRUE;
573 }
574
575 static void
576 swatch_map (GtkWidget *widget)
577 {
578   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
579
580   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->map (widget);
581
582   if (swatch->priv->event_window)
583     gdk_window_show (swatch->priv->event_window);
584 }
585
586 static void
587 swatch_unmap (GtkWidget *widget)
588 {
589   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
590
591   if (swatch->priv->event_window)
592     gdk_window_hide (swatch->priv->event_window);
593
594   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->unmap (widget);
595 }
596
597 static void
598 swatch_realize (GtkWidget *widget)
599 {
600   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
601   GtkAllocation allocation;
602   GdkWindow *window;
603   GdkWindowAttr attributes;
604   gint attributes_mask;
605
606   gtk_widget_get_allocation (widget, &allocation);
607   gtk_widget_set_realized (widget, TRUE);
608
609   attributes.window_type = GDK_WINDOW_CHILD;
610   attributes.x = allocation.x;
611   attributes.y = allocation.y;
612   attributes.width = allocation.width;
613   attributes.height = allocation.height;
614   attributes.wclass = GDK_INPUT_ONLY;
615   attributes.event_mask = gtk_widget_get_events (widget);
616   attributes.event_mask |= GDK_BUTTON_PRESS_MASK
617                            | GDK_BUTTON_RELEASE_MASK
618                            | GDK_ENTER_NOTIFY_MASK
619                            | GDK_LEAVE_NOTIFY_MASK
620                            | GDK_TOUCH_MASK;
621
622   attributes_mask = GDK_WA_X | GDK_WA_Y;
623
624   window = gtk_widget_get_parent_window (widget);
625   gtk_widget_set_window (widget, window);
626   g_object_ref (window);
627
628   swatch->priv->event_window = 
629     gdk_window_new (window,
630                     &attributes, attributes_mask);
631   gtk_widget_register_window (widget, swatch->priv->event_window);
632 }
633
634 static void
635 swatch_unrealize (GtkWidget *widget)
636 {
637   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
638
639   if (swatch->priv->event_window)
640     {
641       gtk_widget_unregister_window (widget, swatch->priv->event_window);
642       gdk_window_destroy (swatch->priv->event_window);
643       swatch->priv->event_window = NULL;
644     }
645
646   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->unrealize (widget);
647 }
648
649 static void
650 swatch_size_allocate (GtkWidget *widget,
651                       GtkAllocation *allocation)
652 {
653   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
654
655   gtk_widget_set_allocation (widget, allocation);
656
657   if (gtk_widget_get_realized (widget))
658     gdk_window_move_resize (swatch->priv->event_window,
659                             allocation->x,
660                             allocation->y,
661                             allocation->width,
662                             allocation->height);
663 }
664
665 static gboolean
666 swatch_popup_menu (GtkWidget *swatch)
667 {
668   do_popup (swatch, NULL);
669   return TRUE;
670 }
671
672 /* GObject implementation {{{1 */
673
674 static void
675 swatch_get_property (GObject    *object,
676                      guint       prop_id,
677                      GValue     *value,
678                      GParamSpec *pspec)
679 {
680   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
681   GdkRGBA color;
682
683   switch (prop_id)
684     {
685     case PROP_RGBA:
686       gtk_color_swatch_get_rgba (swatch, &color);
687       g_value_set_boxed (value, &color);
688       break;
689     case PROP_SELECTABLE:
690       g_value_set_boolean (value, gtk_color_swatch_get_selectable (swatch));
691       break;
692     default:
693       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
694       break;
695     }
696 }
697
698 static void
699 swatch_set_property (GObject      *object,
700                      guint         prop_id,
701                      const GValue *value,
702                      GParamSpec   *pspec)
703 {
704   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
705
706   switch (prop_id)
707     {
708     case PROP_RGBA:
709       gtk_color_swatch_set_rgba (swatch, g_value_get_boxed (value));
710       break;
711     case PROP_SELECTABLE:
712       gtk_color_swatch_set_selectable (swatch, g_value_get_boolean (value));
713       break;
714     default:
715       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
716       break;
717     }
718 }
719
720 static void
721 swatch_finalize (GObject *object)
722 {
723   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
724
725   g_free (swatch->priv->icon);
726   g_clear_object (&swatch->priv->press_and_hold);
727
728   G_OBJECT_CLASS (gtk_color_swatch_parent_class)->finalize (object);
729 }
730
731 static void
732 gtk_color_swatch_class_init (GtkColorSwatchClass *class)
733 {
734   GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
735   GObjectClass *object_class = (GObjectClass *)class;
736
737   object_class->get_property = swatch_get_property;
738   object_class->set_property = swatch_set_property;
739   object_class->finalize = swatch_finalize;
740
741   widget_class->get_preferred_width = swatch_get_preferred_width;
742   widget_class->get_preferred_height = swatch_get_preferred_height;
743   widget_class->draw = swatch_draw;
744   widget_class->drag_begin = swatch_drag_begin;
745   widget_class->drag_data_get = swatch_drag_data_get;
746   widget_class->drag_data_received = swatch_drag_data_received;
747   widget_class->key_press_event = swatch_key_press;
748   widget_class->popup_menu = swatch_popup_menu;
749   widget_class->button_press_event = swatch_button_press;
750   widget_class->button_release_event = swatch_button_release;
751   widget_class->enter_notify_event = swatch_enter_notify;
752   widget_class->leave_notify_event = swatch_leave_notify;
753   widget_class->realize = swatch_realize;
754   widget_class->unrealize = swatch_unrealize;
755   widget_class->map = swatch_map;
756   widget_class->unmap = swatch_unmap;
757   widget_class->size_allocate = swatch_size_allocate;
758   widget_class->touch_event = swatch_touch;
759
760   signals[ACTIVATE] =
761     g_signal_new ("activate",
762                   GTK_TYPE_COLOR_SWATCH,
763                   G_SIGNAL_RUN_FIRST,
764                   G_STRUCT_OFFSET (GtkColorSwatchClass, activate),
765                   NULL, NULL, NULL, G_TYPE_NONE, 0);
766
767   signals[CUSTOMIZE] =
768     g_signal_new ("customize",
769                   GTK_TYPE_COLOR_SWATCH,
770                   G_SIGNAL_RUN_FIRST,
771                   G_STRUCT_OFFSET (GtkColorSwatchClass, customize),
772                   NULL, NULL, NULL, G_TYPE_NONE, 0);
773
774   g_object_class_install_property (object_class, PROP_RGBA,
775       g_param_spec_boxed ("rgba", P_("RGBA Color"), P_("Color as RGBA"),
776                           GDK_TYPE_RGBA, GTK_PARAM_READWRITE));
777   g_object_class_install_property (object_class, PROP_SELECTABLE,
778       g_param_spec_boolean ("selectable", P_("Selectable"), P_("Whether the swatch is selectable"),
779                             TRUE, GTK_PARAM_READWRITE));
780
781   g_type_class_add_private (object_class, sizeof (GtkColorSwatchPrivate));
782
783   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_COLOR_SWATCH_ACCESSIBLE);
784 }
785
786 /* Public API {{{1 */
787
788 GtkWidget *
789 gtk_color_swatch_new (void)
790 {
791   return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_SWATCH, NULL);
792 }
793
794 static const GtkTargetEntry dnd_targets[] = {
795   { "application/x-color", 0 }
796 };
797
798 void
799 gtk_color_swatch_set_rgba (GtkColorSwatch *swatch,
800                            const GdkRGBA  *color)
801 {
802   GtkStyleContext *context;
803
804   context = gtk_widget_get_style_context (GTK_WIDGET (swatch));
805
806   if (!swatch->priv->has_color)
807     {
808       gtk_drag_source_set (GTK_WIDGET (swatch),
809                            GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
810                            dnd_targets, G_N_ELEMENTS (dnd_targets),
811                            GDK_ACTION_COPY | GDK_ACTION_MOVE);
812     }
813   else
814     {
815       gtk_style_context_remove_class (context, "color-light");
816       gtk_style_context_remove_class (context, "color-dark");
817     }
818
819   swatch->priv->has_color = TRUE;
820   swatch->priv->color = *color;
821
822   if (INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) > 0.5)
823     gtk_style_context_add_class (context, "color-light");
824   else
825     gtk_style_context_add_class (context, "color-dark");
826
827   gtk_widget_queue_draw (GTK_WIDGET (swatch));
828   g_object_notify (G_OBJECT (swatch), "rgba");
829 }
830
831 gboolean
832 gtk_color_swatch_get_rgba (GtkColorSwatch *swatch,
833                            GdkRGBA        *color)
834 {
835   if (swatch->priv->has_color)
836     {
837       color->red = swatch->priv->color.red;
838       color->green = swatch->priv->color.green;
839       color->blue = swatch->priv->color.blue;
840       color->alpha = swatch->priv->color.alpha;
841       return TRUE;
842     }
843   else
844     {
845       color->red = 1.0;
846       color->green = 1.0;
847       color->blue = 1.0;
848       color->alpha = 1.0;
849       return FALSE;
850     }
851 }
852
853 void
854 gtk_color_swatch_set_icon (GtkColorSwatch *swatch,
855                            const gchar    *icon)
856 {
857   swatch->priv->icon = g_strdup (icon);
858   gtk_widget_queue_draw (GTK_WIDGET (swatch));
859 }
860
861 void
862 gtk_color_swatch_set_can_drop (GtkColorSwatch *swatch,
863                                gboolean        can_drop)
864 {
865   if (can_drop)
866     {
867       gtk_drag_dest_set (GTK_WIDGET (swatch),
868                          GTK_DEST_DEFAULT_HIGHLIGHT |
869                          GTK_DEST_DEFAULT_MOTION |
870                          GTK_DEST_DEFAULT_DROP,
871                          dnd_targets, G_N_ELEMENTS (dnd_targets),
872                          GDK_ACTION_COPY);
873     }
874   else
875     {
876       gtk_drag_dest_unset (GTK_WIDGET (swatch));
877     }
878 }
879
880 void
881 gtk_color_swatch_set_use_alpha (GtkColorSwatch *swatch,
882                                 gboolean        use_alpha)
883 {
884   swatch->priv->use_alpha = use_alpha;
885   gtk_widget_queue_draw (GTK_WIDGET (swatch));
886 }
887
888 void
889 gtk_color_swatch_set_selectable (GtkColorSwatch *swatch,
890                                  gboolean selectable)
891 {
892   if (selectable == swatch->priv->selectable)
893     return;
894
895   swatch->priv->selectable = selectable;
896   g_object_notify (G_OBJECT (swatch), "selectable");
897 }
898
899 gboolean
900 gtk_color_swatch_get_selectable (GtkColorSwatch *swatch)
901 {
902   return swatch->priv->selectable;
903 }
904
905 /* vim:set foldmethod=marker: */