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 "gtkdnd.h"
24 #include "gtkicontheme.h"
25 #include "gtkmain.h"
26 #include "gtkmenu.h"
27 #include "gtkmenuitem.h"
28 #include "gtkmenushell.h"
29 #include "gtkprivate.h"
30 #include "gtkintl.h"
31 #include "gtkrenderprivate.h"
32 #include "gtkiconhelperprivate.h"
33 #include "gtkcssnodeprivate.h"
34 #include "gtkcsscustomgadgetprivate.h"
35 #include "gtkwidgetprivate.h"
36 #include "gtkstylecontextprivate.h"
37 #include "a11y/gtkcolorswatchaccessibleprivate.h"
38 
39 
40 /*
41  * GtkColorSwatch has two CSS nodes, the main one named colorswatch
42  * and a subnode named overlay. The main node gets the .light or .dark
43  * style classes added depending on the brightness of the color that
44  * the swatch is showing.
45  *
46  * The color swatch has the .activatable style class by default. It can
47  * be removed for non-activatable swatches.
48  */
49 
50 struct _GtkColorSwatchPrivate
51 {
52   GdkRGBA color;
53   gdouble radius[4];
54   gchar *icon;
55   guint    has_color        : 1;
56   guint    use_alpha        : 1;
57   guint    selectable       : 1;
58   guint    has_menu         : 1;
59 
60   GdkWindow *event_window;
61 
62   GtkGesture *long_press_gesture;
63   GtkGesture *multipress_gesture;
64   GtkCssGadget *gadget;
65   GtkCssGadget *overlay_gadget;
66 
67   GtkWidget *popover;
68 };
69 
70 enum
71 {
72   PROP_ZERO,
73   PROP_RGBA,
74   PROP_SELECTABLE,
75   PROP_HAS_MENU
76 };
77 
78 enum
79 {
80   ACTIVATE,
81   CUSTOMIZE,
82   LAST_SIGNAL
83 };
84 
85 static guint signals[LAST_SIGNAL];
86 
87 
G_DEFINE_TYPE_WITH_PRIVATE(GtkColorSwatch,gtk_color_swatch,GTK_TYPE_WIDGET)88 G_DEFINE_TYPE_WITH_PRIVATE (GtkColorSwatch, gtk_color_swatch, GTK_TYPE_WIDGET)
89 
90 static gboolean
91 swatch_draw (GtkWidget *widget,
92              cairo_t   *cr)
93 {
94   gtk_css_gadget_draw (GTK_COLOR_SWATCH (widget)->priv->gadget, cr);
95 
96   return FALSE;
97 }
98 
99 #define INTENSITY(r, g, b) ((r) * 0.30 + (g) * 0.59 + (b) * 0.11)
100 
101 static gboolean
gtk_color_swatch_render(GtkCssGadget * gadget,cairo_t * cr,int x,int y,int width,int height,gpointer data)102 gtk_color_swatch_render (GtkCssGadget *gadget,
103                          cairo_t      *cr,
104                          int           x,
105                          int           y,
106                          int           width,
107                          int           height,
108                          gpointer      data)
109 {
110   GtkWidget *widget;
111   GtkColorSwatch *swatch;
112   GtkStyleContext *context;
113 
114   widget = gtk_css_gadget_get_owner (gadget);
115   swatch = GTK_COLOR_SWATCH (widget);
116   context = gtk_widget_get_style_context (widget);
117 
118   if (swatch->priv->has_color)
119     {
120       cairo_pattern_t *pattern;
121       cairo_matrix_t matrix;
122       GtkAllocation allocation, border_allocation;
123 
124       gtk_widget_get_allocation (widget, &allocation);
125       gtk_css_gadget_get_border_allocation (gadget, &border_allocation, NULL);
126 
127       border_allocation.x -= allocation.x;
128       border_allocation.y -= allocation.y;
129 
130       gtk_render_content_path (context, cr,
131                                border_allocation.x,
132                                border_allocation.y,
133                                border_allocation.width,
134                                border_allocation.height);
135 
136       if (swatch->priv->use_alpha)
137         {
138           cairo_save (cr);
139 
140           cairo_clip_preserve (cr);
141 
142           cairo_set_source_rgb (cr, 0.33, 0.33, 0.33);
143           cairo_fill_preserve (cr);
144 
145           pattern = _gtk_color_chooser_get_checkered_pattern ();
146           cairo_matrix_init_scale (&matrix, 0.125, 0.125);
147           cairo_pattern_set_matrix (pattern, &matrix);
148 
149           cairo_set_source_rgb (cr, 0.66, 0.66, 0.66);
150           cairo_mask (cr, pattern);
151           cairo_pattern_destroy (pattern);
152 
153           cairo_restore (cr);
154 
155           gdk_cairo_set_source_rgba (cr, &swatch->priv->color);
156         }
157       else
158         {
159           cairo_set_source_rgb (cr,
160                                 swatch->priv->color.red,
161                                 swatch->priv->color.green,
162                                 swatch->priv->color.blue);
163         }
164 
165       cairo_fill (cr);
166     }
167 
168   gtk_css_gadget_draw (swatch->priv->overlay_gadget, cr);
169 
170   return gtk_widget_has_visible_focus (widget);
171 }
172 
173 static void
drag_set_color_icon(GdkDragContext * context,const GdkRGBA * color)174 drag_set_color_icon (GdkDragContext *context,
175                      const GdkRGBA  *color)
176 {
177   cairo_surface_t *surface;
178   cairo_t *cr;
179 
180   surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, 48, 32);
181   cr = cairo_create (surface);
182   gdk_cairo_set_source_rgba (cr, color);
183   cairo_paint (cr);
184 
185   cairo_surface_set_device_offset (surface, -4, -4);
186   gtk_drag_set_icon_surface (context, surface);
187 
188   cairo_destroy (cr);
189   cairo_surface_destroy (surface);
190 }
191 
192 static void
swatch_drag_begin(GtkWidget * widget,GdkDragContext * context)193 swatch_drag_begin (GtkWidget      *widget,
194                    GdkDragContext *context)
195 {
196   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
197   GdkRGBA color;
198 
199   gtk_color_swatch_get_rgba (swatch, &color);
200   drag_set_color_icon (context, &color);
201 }
202 
203 static void
swatch_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)204 swatch_drag_data_get (GtkWidget        *widget,
205                       GdkDragContext   *context,
206                       GtkSelectionData *selection_data,
207                       guint             info,
208                       guint             time)
209 {
210   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
211   guint16 vals[4];
212   GdkRGBA color;
213 
214   gtk_color_swatch_get_rgba (swatch, &color);
215 
216   vals[0] = color.red * 0xffff;
217   vals[1] = color.green * 0xffff;
218   vals[2] = color.blue * 0xffff;
219   vals[3] = color.alpha * 0xffff;
220 
221   gtk_selection_data_set (selection_data,
222                           gdk_atom_intern_static_string ("application/x-color"),
223                           16, (guchar *)vals, 8);
224 }
225 
226 static void
swatch_drag_data_received(GtkWidget * widget,GdkDragContext * context,gint x,gint y,GtkSelectionData * selection_data,guint info,guint time)227 swatch_drag_data_received (GtkWidget        *widget,
228                            GdkDragContext   *context,
229                            gint              x,
230                            gint              y,
231                            GtkSelectionData *selection_data,
232                            guint             info,
233                            guint             time)
234 {
235   gint length;
236   guint16 *vals;
237   GdkRGBA color;
238 
239   length = gtk_selection_data_get_length (selection_data);
240 
241   if (length < 0)
242     return;
243 
244   /* We accept drops with the wrong format, since the KDE color
245    * chooser incorrectly drops application/x-color with format 8.
246    */
247   if (length != 8)
248     {
249       g_warning ("Received invalid color data");
250       return;
251     }
252 
253   vals = (guint16 *) gtk_selection_data_get_data (selection_data);
254 
255   color.red   = (gdouble)vals[0] / 0xffff;
256   color.green = (gdouble)vals[1] / 0xffff;
257   color.blue  = (gdouble)vals[2] / 0xffff;
258   color.alpha = (gdouble)vals[3] / 0xffff;
259 
260   gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (widget), &color);
261 }
262 
263 static void
gtk_color_swatch_measure(GtkCssGadget * gadget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline,gpointer unused)264 gtk_color_swatch_measure (GtkCssGadget   *gadget,
265                           GtkOrientation  orientation,
266                           int             for_size,
267                           int            *minimum,
268                           int            *natural,
269                           int            *minimum_baseline,
270                           int            *natural_baseline,
271                           gpointer        unused)
272 {
273   GtkWidget *widget;
274   GtkColorSwatch *swatch;
275   gint w, h, min;
276 
277   widget = gtk_css_gadget_get_owner (gadget);
278   swatch = GTK_COLOR_SWATCH (widget);
279 
280   gtk_css_gadget_get_preferred_size (swatch->priv->overlay_gadget,
281                                      orientation,
282                                      -1,
283                                      minimum, natural,
284                                      NULL, NULL);
285 
286   gtk_widget_get_size_request (widget, &w, &h);
287   if (orientation == GTK_ORIENTATION_HORIZONTAL)
288     min = w < 0 ? 48 : w;
289   else
290     min = h < 0 ? 32 : h;
291 
292   *minimum = MAX (*minimum, min);
293   *natural = MAX (*natural, min);
294 }
295 
296 static gboolean
swatch_key_press(GtkWidget * widget,GdkEventKey * event)297 swatch_key_press (GtkWidget   *widget,
298                   GdkEventKey *event)
299 {
300   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
301 
302   if (event->keyval == GDK_KEY_space ||
303       event->keyval == GDK_KEY_Return ||
304       event->keyval == GDK_KEY_ISO_Enter||
305       event->keyval == GDK_KEY_KP_Enter ||
306       event->keyval == GDK_KEY_KP_Space)
307     {
308       if (swatch->priv->has_color &&
309           swatch->priv->selectable &&
310           (gtk_widget_get_state_flags (widget) & GTK_STATE_FLAG_SELECTED) == 0)
311         gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE);
312       else
313         g_signal_emit (swatch, signals[ACTIVATE], 0);
314       return TRUE;
315     }
316 
317   if (GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->key_press_event (widget, event))
318     return TRUE;
319 
320   return FALSE;
321 }
322 
323 static gboolean
swatch_enter_notify(GtkWidget * widget,GdkEventCrossing * event)324 swatch_enter_notify (GtkWidget        *widget,
325                      GdkEventCrossing *event)
326 {
327   gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_PRELIGHT, FALSE);
328 
329   return FALSE;
330 }
331 
332 static gboolean
swatch_leave_notify(GtkWidget * widget,GdkEventCrossing * event)333 swatch_leave_notify (GtkWidget        *widget,
334                      GdkEventCrossing *event)
335 {
336   gtk_widget_unset_state_flags (widget, GTK_STATE_FLAG_PRELIGHT);
337 
338   return FALSE;
339 }
340 
341 static void
emit_customize(GtkColorSwatch * swatch)342 emit_customize (GtkColorSwatch *swatch)
343 {
344   g_signal_emit (swatch, signals[CUSTOMIZE], 0);
345 }
346 
347 static void
do_popup(GtkColorSwatch * swatch)348 do_popup (GtkColorSwatch *swatch)
349 {
350   if (swatch->priv->popover == NULL)
351     {
352       GtkWidget *box;
353       GtkWidget *item;
354 
355       swatch->priv->popover = gtk_popover_new (GTK_WIDGET (swatch));
356       box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
357       gtk_container_add (GTK_CONTAINER (swatch->priv->popover), box);
358       g_object_set (box, "margin", 10, NULL);
359       item = g_object_new (GTK_TYPE_MODEL_BUTTON,
360                            "text", _("C_ustomize"),
361                            NULL);
362       g_signal_connect_swapped (item, "clicked",
363                                 G_CALLBACK (emit_customize), swatch);
364       gtk_container_add (GTK_CONTAINER (box), item);
365       gtk_widget_show_all (box);
366     }
367 
368   gtk_popover_popup (GTK_POPOVER (swatch->priv->popover));
369 }
370 
371 static gboolean
swatch_primary_action(GtkColorSwatch * swatch)372 swatch_primary_action (GtkColorSwatch *swatch)
373 {
374   GtkWidget *widget = (GtkWidget *)swatch;
375   GtkStateFlags flags;
376 
377   flags = gtk_widget_get_state_flags (widget);
378   if (!swatch->priv->has_color)
379     {
380       g_signal_emit (swatch, signals[ACTIVATE], 0);
381       return TRUE;
382     }
383   else if (swatch->priv->selectable &&
384            (flags & GTK_STATE_FLAG_SELECTED) == 0)
385     {
386       gtk_widget_set_state_flags (widget, GTK_STATE_FLAG_SELECTED, FALSE);
387       return TRUE;
388     }
389 
390   return FALSE;
391 }
392 
393 static void
hold_action(GtkGestureLongPress * gesture,gdouble x,gdouble y,GtkColorSwatch * swatch)394 hold_action (GtkGestureLongPress *gesture,
395              gdouble              x,
396              gdouble              y,
397              GtkColorSwatch      *swatch)
398 {
399   do_popup (swatch);
400   gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
401 }
402 
403 static void
tap_action(GtkGestureMultiPress * gesture,gint n_press,gdouble x,gdouble y,GtkColorSwatch * swatch)404 tap_action (GtkGestureMultiPress *gesture,
405             gint                  n_press,
406             gdouble               x,
407             gdouble               y,
408             GtkColorSwatch       *swatch)
409 {
410   guint button;
411 
412   button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
413 
414   if (button == GDK_BUTTON_PRIMARY)
415     {
416       if (n_press == 1)
417         swatch_primary_action (swatch);
418       else if (n_press > 1)
419         g_signal_emit (swatch, signals[ACTIVATE], 0);
420     }
421   else if (button == GDK_BUTTON_SECONDARY)
422     {
423       if (swatch->priv->has_color && swatch->priv->has_menu)
424         do_popup (swatch);
425     }
426 }
427 
428 static void
swatch_map(GtkWidget * widget)429 swatch_map (GtkWidget *widget)
430 {
431   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
432 
433   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->map (widget);
434 
435   if (swatch->priv->event_window)
436     gdk_window_show (swatch->priv->event_window);
437 }
438 
439 static void
swatch_unmap(GtkWidget * widget)440 swatch_unmap (GtkWidget *widget)
441 {
442   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
443 
444   if (swatch->priv->event_window)
445     gdk_window_hide (swatch->priv->event_window);
446 
447   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->unmap (widget);
448 }
449 
450 static void
swatch_realize(GtkWidget * widget)451 swatch_realize (GtkWidget *widget)
452 {
453   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
454   GtkAllocation allocation;
455   GdkWindow *window;
456   GdkWindowAttr attributes;
457   gint attributes_mask;
458 
459   gtk_widget_get_allocation (widget, &allocation);
460   gtk_widget_set_realized (widget, TRUE);
461 
462   attributes.window_type = GDK_WINDOW_CHILD;
463   attributes.x = allocation.x;
464   attributes.y = allocation.y;
465   attributes.width = allocation.width;
466   attributes.height = allocation.height;
467   attributes.wclass = GDK_INPUT_ONLY;
468   attributes.event_mask = gtk_widget_get_events (widget);
469   attributes.event_mask |= GDK_BUTTON_PRESS_MASK
470                            | GDK_BUTTON_RELEASE_MASK
471                            | GDK_ENTER_NOTIFY_MASK
472                            | GDK_LEAVE_NOTIFY_MASK
473                            | GDK_TOUCH_MASK;
474 
475   attributes_mask = GDK_WA_X | GDK_WA_Y;
476 
477   window = gtk_widget_get_parent_window (widget);
478   gtk_widget_set_window (widget, window);
479   g_object_ref (window);
480 
481   swatch->priv->event_window = gdk_window_new (window, &attributes, attributes_mask);
482   gtk_widget_register_window (widget, swatch->priv->event_window);
483 }
484 
485 static void
swatch_unrealize(GtkWidget * widget)486 swatch_unrealize (GtkWidget *widget)
487 {
488   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
489 
490   if (swatch->priv->event_window)
491     {
492       gtk_widget_unregister_window (widget, swatch->priv->event_window);
493       gdk_window_destroy (swatch->priv->event_window);
494       swatch->priv->event_window = NULL;
495     }
496 
497   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->unrealize (widget);
498 }
499 
500 static void
swatch_size_allocate(GtkWidget * widget,GtkAllocation * allocation)501 swatch_size_allocate (GtkWidget     *widget,
502                       GtkAllocation *allocation)
503 {
504   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
505   GtkAllocation clip, clip2;
506 
507   gtk_widget_set_allocation (widget, allocation);
508 
509   gtk_css_gadget_allocate (swatch->priv->gadget,
510                            allocation,
511                            gtk_widget_get_allocated_baseline (widget),
512                            &clip);
513   gtk_css_gadget_allocate (swatch->priv->overlay_gadget,
514                            allocation,
515                            gtk_widget_get_allocated_baseline (widget),
516                            &clip2);
517 
518   gdk_rectangle_union (&clip, &clip2, &clip);
519 
520   gtk_widget_set_clip (widget, &clip);
521 
522   if (gtk_widget_get_realized (widget))
523     {
524       GtkAllocation border_allocation;
525       gtk_css_gadget_get_border_allocation(swatch->priv->gadget, &border_allocation, NULL);
526       gdk_window_move_resize (swatch->priv->event_window,
527                               border_allocation.x,
528                               border_allocation.y,
529                               border_allocation.width,
530                               border_allocation.height);
531     }
532 
533 }
534 
535 static void
swatch_get_preferred_width(GtkWidget * widget,gint * minimum,gint * natural)536 swatch_get_preferred_width (GtkWidget *widget,
537                             gint      *minimum,
538                             gint      *natural)
539 {
540   gtk_css_gadget_get_preferred_size (GTK_COLOR_SWATCH (widget)->priv->gadget,
541                                      GTK_ORIENTATION_HORIZONTAL,
542                                      -1,
543                                      minimum, natural,
544                                      NULL, NULL);
545 }
546 
547 static void
swatch_get_preferred_height(GtkWidget * widget,gint * minimum,gint * natural)548 swatch_get_preferred_height (GtkWidget *widget,
549                              gint      *minimum,
550                              gint      *natural)
551 {
552   gtk_css_gadget_get_preferred_size (GTK_COLOR_SWATCH (widget)->priv->gadget,
553                                      GTK_ORIENTATION_VERTICAL,
554                                      -1,
555                                      minimum, natural,
556                                      NULL, NULL);
557 }
558 
559 static gboolean
swatch_popup_menu(GtkWidget * widget)560 swatch_popup_menu (GtkWidget *widget)
561 {
562   do_popup (GTK_COLOR_SWATCH (widget));
563   return TRUE;
564 }
565 
566 static void
update_icon(GtkColorSwatch * swatch)567 update_icon (GtkColorSwatch *swatch)
568 {
569   GtkIconHelper *icon_helper = GTK_ICON_HELPER (swatch->priv->overlay_gadget);
570 
571   if (swatch->priv->icon)
572     _gtk_icon_helper_set_icon_name (icon_helper, swatch->priv->icon, GTK_ICON_SIZE_BUTTON);
573   else if (gtk_widget_get_state_flags (GTK_WIDGET (swatch)) & GTK_STATE_FLAG_SELECTED)
574     _gtk_icon_helper_set_icon_name (icon_helper, "object-select-symbolic", GTK_ICON_SIZE_BUTTON);
575   else
576     _gtk_icon_helper_clear (icon_helper);
577 }
578 
579 static void
swatch_state_flags_changed(GtkWidget * widget,GtkStateFlags previous_state)580 swatch_state_flags_changed (GtkWidget     *widget,
581                             GtkStateFlags  previous_state)
582 {
583   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (widget);
584 
585   gtk_css_gadget_set_state (swatch->priv->gadget, gtk_widget_get_state_flags (widget));
586   gtk_css_gadget_set_state (swatch->priv->overlay_gadget, gtk_widget_get_state_flags (widget));
587 
588   update_icon (swatch);
589 
590   GTK_WIDGET_CLASS (gtk_color_swatch_parent_class)->state_flags_changed (widget, previous_state);
591 }
592 
593 /* GObject implementation {{{1 */
594 
595 static void
swatch_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)596 swatch_get_property (GObject    *object,
597                      guint       prop_id,
598                      GValue     *value,
599                      GParamSpec *pspec)
600 {
601   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
602   GdkRGBA color;
603 
604   switch (prop_id)
605     {
606     case PROP_RGBA:
607       gtk_color_swatch_get_rgba (swatch, &color);
608       g_value_set_boxed (value, &color);
609       break;
610     case PROP_SELECTABLE:
611       g_value_set_boolean (value, gtk_color_swatch_get_selectable (swatch));
612       break;
613     case PROP_HAS_MENU:
614       g_value_set_boolean (value, swatch->priv->has_menu);
615       break;
616     default:
617       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
618       break;
619     }
620 }
621 
622 static void
swatch_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)623 swatch_set_property (GObject      *object,
624                      guint         prop_id,
625                      const GValue *value,
626                      GParamSpec   *pspec)
627 {
628   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
629 
630   switch (prop_id)
631     {
632     case PROP_RGBA:
633       gtk_color_swatch_set_rgba (swatch, g_value_get_boxed (value));
634       break;
635     case PROP_SELECTABLE:
636       gtk_color_swatch_set_selectable (swatch, g_value_get_boolean (value));
637       break;
638     case PROP_HAS_MENU:
639       swatch->priv->has_menu = g_value_get_boolean (value);
640       break;
641     default:
642       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
643       break;
644     }
645 }
646 
647 static void
swatch_finalize(GObject * object)648 swatch_finalize (GObject *object)
649 {
650   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
651 
652   g_free (swatch->priv->icon);
653   g_clear_object (&swatch->priv->gadget);
654   g_clear_object (&swatch->priv->overlay_gadget);
655 
656   G_OBJECT_CLASS (gtk_color_swatch_parent_class)->finalize (object);
657 }
658 
659 static void
swatch_dispose(GObject * object)660 swatch_dispose (GObject *object)
661 {
662   GtkColorSwatch *swatch = GTK_COLOR_SWATCH (object);
663 
664   if (swatch->priv->popover)
665     {
666       gtk_widget_destroy (swatch->priv->popover);
667       swatch->priv->popover = NULL;
668     }
669 
670   g_clear_object (&swatch->priv->long_press_gesture);
671   g_clear_object (&swatch->priv->multipress_gesture);
672 
673   G_OBJECT_CLASS (gtk_color_swatch_parent_class)->dispose (object);
674 }
675 
676 static void
gtk_color_swatch_class_init(GtkColorSwatchClass * class)677 gtk_color_swatch_class_init (GtkColorSwatchClass *class)
678 {
679   GtkWidgetClass *widget_class = (GtkWidgetClass *)class;
680   GObjectClass *object_class = (GObjectClass *)class;
681 
682   object_class->get_property = swatch_get_property;
683   object_class->set_property = swatch_set_property;
684   object_class->finalize = swatch_finalize;
685   object_class->dispose = swatch_dispose;
686 
687   widget_class->get_preferred_width = swatch_get_preferred_width;
688   widget_class->get_preferred_height = swatch_get_preferred_height;
689   widget_class->draw = swatch_draw;
690   widget_class->drag_begin = swatch_drag_begin;
691   widget_class->drag_data_get = swatch_drag_data_get;
692   widget_class->drag_data_received = swatch_drag_data_received;
693   widget_class->key_press_event = swatch_key_press;
694   widget_class->popup_menu = swatch_popup_menu;
695   widget_class->enter_notify_event = swatch_enter_notify;
696   widget_class->leave_notify_event = swatch_leave_notify;
697   widget_class->realize = swatch_realize;
698   widget_class->unrealize = swatch_unrealize;
699   widget_class->map = swatch_map;
700   widget_class->unmap = swatch_unmap;
701   widget_class->size_allocate = swatch_size_allocate;
702   widget_class->state_flags_changed = swatch_state_flags_changed;
703 
704   signals[ACTIVATE] =
705     g_signal_new (I_("activate"),
706                   GTK_TYPE_COLOR_SWATCH,
707                   G_SIGNAL_RUN_FIRST,
708                   G_STRUCT_OFFSET (GtkColorSwatchClass, activate),
709                   NULL, NULL, NULL, G_TYPE_NONE, 0);
710 
711   signals[CUSTOMIZE] =
712     g_signal_new (I_("customize"),
713                   GTK_TYPE_COLOR_SWATCH,
714                   G_SIGNAL_RUN_FIRST,
715                   G_STRUCT_OFFSET (GtkColorSwatchClass, customize),
716                   NULL, NULL, NULL, G_TYPE_NONE, 0);
717 
718   g_object_class_install_property (object_class, PROP_RGBA,
719       g_param_spec_boxed ("rgba", P_("RGBA Color"), P_("Color as RGBA"),
720                           GDK_TYPE_RGBA, GTK_PARAM_READWRITE));
721   g_object_class_install_property (object_class, PROP_SELECTABLE,
722       g_param_spec_boolean ("selectable", P_("Selectable"), P_("Whether the swatch is selectable"),
723                             TRUE, GTK_PARAM_READWRITE));
724   g_object_class_install_property (object_class, PROP_HAS_MENU,
725       g_param_spec_boolean ("has-menu", P_("Has Menu"), P_("Whether the swatch should offer customization"),
726                             TRUE, GTK_PARAM_READWRITE));
727 
728   gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_COLOR_SWATCH_ACCESSIBLE);
729   gtk_widget_class_set_css_name (widget_class, "colorswatch");
730 }
731 
732 static void
gtk_color_swatch_init(GtkColorSwatch * swatch)733 gtk_color_swatch_init (GtkColorSwatch *swatch)
734 {
735   GtkCssNode *widget_node;
736 
737   swatch->priv = gtk_color_swatch_get_instance_private (swatch);
738   swatch->priv->use_alpha = TRUE;
739   swatch->priv->selectable = TRUE;
740   swatch->priv->has_menu = TRUE;
741 
742   gtk_widget_set_can_focus (GTK_WIDGET (swatch), TRUE);
743   gtk_widget_set_has_window (GTK_WIDGET (swatch), FALSE);
744 
745   swatch->priv->long_press_gesture = gtk_gesture_long_press_new (GTK_WIDGET (swatch));
746   gtk_gesture_single_set_touch_only (GTK_GESTURE_SINGLE (swatch->priv->long_press_gesture),
747                                      TRUE);
748   g_signal_connect (swatch->priv->long_press_gesture, "pressed",
749                     G_CALLBACK (hold_action), swatch);
750 
751   swatch->priv->multipress_gesture = gtk_gesture_multi_press_new (GTK_WIDGET (swatch));
752   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (swatch->priv->multipress_gesture), 0);
753   g_signal_connect (swatch->priv->multipress_gesture, "pressed",
754                     G_CALLBACK (tap_action), swatch);
755 
756   widget_node = gtk_widget_get_css_node (GTK_WIDGET (swatch));
757   swatch->priv->gadget = gtk_css_custom_gadget_new_for_node (widget_node,
758                                                              GTK_WIDGET (swatch),
759                                                              gtk_color_swatch_measure,
760                                                              NULL,
761                                                              gtk_color_swatch_render,
762                                                              NULL,
763                                                              NULL);
764   gtk_css_gadget_add_class (swatch->priv->gadget, "activatable");
765 
766   swatch->priv->overlay_gadget = gtk_icon_helper_new_named ("overlay", GTK_WIDGET (swatch));
767   _gtk_icon_helper_set_force_scale_pixbuf (GTK_ICON_HELPER (swatch->priv->overlay_gadget), TRUE);
768   gtk_css_node_set_parent (gtk_css_gadget_get_node (swatch->priv->overlay_gadget), widget_node);
769 
770 }
771 
772 /* Public API {{{1 */
773 
774 GtkWidget *
gtk_color_swatch_new(void)775 gtk_color_swatch_new (void)
776 {
777   return (GtkWidget *) g_object_new (GTK_TYPE_COLOR_SWATCH, NULL);
778 }
779 
780 static const GtkTargetEntry dnd_targets[] = {
781   { "application/x-color", 0 }
782 };
783 
784 void
gtk_color_swatch_set_rgba(GtkColorSwatch * swatch,const GdkRGBA * color)785 gtk_color_swatch_set_rgba (GtkColorSwatch *swatch,
786                            const GdkRGBA  *color)
787 {
788   GtkStyleContext *context;
789 
790   context = gtk_widget_get_style_context (GTK_WIDGET (swatch));
791 
792   if (!swatch->priv->has_color)
793     {
794       gtk_drag_source_set (GTK_WIDGET (swatch),
795                            GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
796                            dnd_targets, G_N_ELEMENTS (dnd_targets),
797                            GDK_ACTION_COPY | GDK_ACTION_MOVE);
798     }
799 
800   swatch->priv->has_color = TRUE;
801   swatch->priv->color = *color;
802 
803   if (INTENSITY (swatch->priv->color.red, swatch->priv->color.green, swatch->priv->color.blue) > 0.5)
804     {
805       gtk_style_context_add_class (context, "light");
806       gtk_style_context_remove_class (context, "dark");
807     }
808   else
809     {
810       gtk_style_context_add_class (context, "dark");
811       gtk_style_context_remove_class (context, "light");
812     }
813 
814   gtk_widget_queue_draw (GTK_WIDGET (swatch));
815   g_object_notify (G_OBJECT (swatch), "rgba");
816 }
817 
818 gboolean
gtk_color_swatch_get_rgba(GtkColorSwatch * swatch,GdkRGBA * color)819 gtk_color_swatch_get_rgba (GtkColorSwatch *swatch,
820                            GdkRGBA        *color)
821 {
822   if (swatch->priv->has_color)
823     {
824       color->red = swatch->priv->color.red;
825       color->green = swatch->priv->color.green;
826       color->blue = swatch->priv->color.blue;
827       color->alpha = swatch->priv->color.alpha;
828       return TRUE;
829     }
830   else
831     {
832       color->red = 1.0;
833       color->green = 1.0;
834       color->blue = 1.0;
835       color->alpha = 1.0;
836       return FALSE;
837     }
838 }
839 
840 void
gtk_color_swatch_set_icon(GtkColorSwatch * swatch,const gchar * icon)841 gtk_color_swatch_set_icon (GtkColorSwatch *swatch,
842                            const gchar    *icon)
843 {
844   swatch->priv->icon = g_strdup (icon);
845   update_icon (swatch);
846   gtk_widget_queue_draw (GTK_WIDGET (swatch));
847 }
848 
849 void
gtk_color_swatch_set_can_drop(GtkColorSwatch * swatch,gboolean can_drop)850 gtk_color_swatch_set_can_drop (GtkColorSwatch *swatch,
851                                gboolean        can_drop)
852 {
853   if (can_drop)
854     {
855       gtk_drag_dest_set (GTK_WIDGET (swatch),
856                          GTK_DEST_DEFAULT_HIGHLIGHT |
857                          GTK_DEST_DEFAULT_MOTION |
858                          GTK_DEST_DEFAULT_DROP,
859                          dnd_targets, G_N_ELEMENTS (dnd_targets),
860                          GDK_ACTION_COPY);
861     }
862   else
863     {
864       gtk_drag_dest_unset (GTK_WIDGET (swatch));
865     }
866 }
867 
868 void
gtk_color_swatch_set_use_alpha(GtkColorSwatch * swatch,gboolean use_alpha)869 gtk_color_swatch_set_use_alpha (GtkColorSwatch *swatch,
870                                 gboolean        use_alpha)
871 {
872   swatch->priv->use_alpha = use_alpha;
873   gtk_widget_queue_draw (GTK_WIDGET (swatch));
874 }
875 
876 void
gtk_color_swatch_set_selectable(GtkColorSwatch * swatch,gboolean selectable)877 gtk_color_swatch_set_selectable (GtkColorSwatch *swatch,
878                                  gboolean selectable)
879 {
880   if (selectable == swatch->priv->selectable)
881     return;
882 
883   swatch->priv->selectable = selectable;
884   g_object_notify (G_OBJECT (swatch), "selectable");
885 }
886 
887 gboolean
gtk_color_swatch_get_selectable(GtkColorSwatch * swatch)888 gtk_color_swatch_get_selectable (GtkColorSwatch *swatch)
889 {
890   return swatch->priv->selectable;
891 }
892 
893 /* vim:set foldmethod=marker: */
894