1 /*
2  * Copyright (c) 2013 Red Hat, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU Lesser General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or (at your
7  * option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
12  * License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public License
15  * along with this program; if not, write to the Free Software Foundation,
16  * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  */
19 
20 #include "config.h"
21 #include "gtkstackswitcher.h"
22 #include "gtkradiobutton.h"
23 #include "gtklabel.h"
24 #include "gtkdnd.h"
25 #include "gtkdragdest.h"
26 #include "gtkorientable.h"
27 #include "gtkprivate.h"
28 #include "gtkintl.h"
29 
30 /**
31  * SECTION:gtkstackswitcher
32  * @Short_description: A controller for GtkStack
33  * @Title: GtkStackSwitcher
34  * @See_also: #GtkStack
35  *
36  * The GtkStackSwitcher widget acts as a controller for a
37  * #GtkStack; it shows a row of buttons to switch between
38  * the various pages of the associated stack widget.
39  *
40  * All the content for the buttons comes from the child properties
41  * of the #GtkStack; the button visibility in a #GtkStackSwitcher
42  * widget is controlled by the visibility of the child in the
43  * #GtkStack.
44  *
45  * It is possible to associate multiple #GtkStackSwitcher widgets
46  * with the same #GtkStack widget.
47  *
48  * The GtkStackSwitcher widget was added in 3.10.
49  *
50  * # CSS nodes
51  *
52  * GtkStackSwitcher has a single CSS node named stackswitcher and
53  * style class .stack-switcher.
54  *
55  * When circumstances require it, GtkStackSwitcher adds the
56  * .needs-attention style class to the widgets representing the
57  * stack pages.
58  */
59 
60 #define TIMEOUT_EXPAND 500
61 
62 typedef struct _GtkStackSwitcherPrivate GtkStackSwitcherPrivate;
63 struct _GtkStackSwitcherPrivate
64 {
65   GtkStack *stack;
66   GHashTable *buttons;
67   gint icon_size;
68   gboolean in_child_changed;
69   GtkWidget *switch_button;
70   guint switch_timer;
71 };
72 
73 enum {
74   PROP_0,
75   PROP_ICON_SIZE,
76   PROP_STACK
77 };
78 
G_DEFINE_TYPE_WITH_PRIVATE(GtkStackSwitcher,gtk_stack_switcher,GTK_TYPE_BOX)79 G_DEFINE_TYPE_WITH_PRIVATE (GtkStackSwitcher, gtk_stack_switcher, GTK_TYPE_BOX)
80 
81 static void
82 gtk_stack_switcher_init (GtkStackSwitcher *switcher)
83 {
84   GtkStyleContext *context;
85   GtkStackSwitcherPrivate *priv;
86 
87   gtk_widget_set_has_window (GTK_WIDGET (switcher), FALSE);
88 
89   priv = gtk_stack_switcher_get_instance_private (switcher);
90 
91   priv->icon_size = GTK_ICON_SIZE_MENU;
92   priv->stack = NULL;
93   priv->buttons = g_hash_table_new (g_direct_hash, g_direct_equal);
94 
95   context = gtk_widget_get_style_context (GTK_WIDGET (switcher));
96   gtk_style_context_add_class (context, "stack-switcher");
97   gtk_style_context_add_class (context, GTK_STYLE_CLASS_LINKED);
98 
99   gtk_orientable_set_orientation (GTK_ORIENTABLE (switcher), GTK_ORIENTATION_HORIZONTAL);
100 
101   gtk_drag_dest_set (GTK_WIDGET (switcher), 0, NULL, 0, 0);
102   gtk_drag_dest_set_track_motion (GTK_WIDGET (switcher), TRUE);
103 }
104 
105 static void
on_button_clicked(GtkWidget * widget,GtkStackSwitcher * self)106 on_button_clicked (GtkWidget        *widget,
107                    GtkStackSwitcher *self)
108 {
109   GtkWidget *child;
110   GtkStackSwitcherPrivate *priv;
111 
112   priv = gtk_stack_switcher_get_instance_private (self);
113 
114   if (!priv->in_child_changed)
115     {
116       child = g_object_get_data (G_OBJECT (widget), "stack-child");
117       gtk_stack_set_visible_child (priv->stack, child);
118     }
119 }
120 
121 static void
rebuild_child(GtkWidget * self,const gchar * icon_name,const gchar * title,gint icon_size)122 rebuild_child (GtkWidget   *self,
123                const gchar *icon_name,
124                const gchar *title,
125                gint         icon_size)
126 {
127   GtkStyleContext *context;
128   GtkWidget *button_child;
129 
130   button_child = gtk_bin_get_child (GTK_BIN (self));
131   if (button_child != NULL)
132     gtk_widget_destroy (button_child);
133 
134   button_child = NULL;
135   context = gtk_widget_get_style_context (GTK_WIDGET (self));
136 
137   if (icon_name != NULL)
138     {
139       button_child = gtk_image_new_from_icon_name (icon_name, icon_size);
140       if (title != NULL)
141         gtk_widget_set_tooltip_text (GTK_WIDGET (self), title);
142 
143       gtk_style_context_remove_class (context, "text-button");
144       gtk_style_context_add_class (context, "image-button");
145     }
146   else if (title != NULL)
147     {
148       button_child = gtk_label_new (title);
149 
150       gtk_widget_set_tooltip_text (GTK_WIDGET (self), NULL);
151 
152       gtk_style_context_remove_class (context, "image-button");
153       gtk_style_context_add_class (context, "text-button");
154     }
155 
156   if (button_child)
157     {
158       gtk_widget_set_halign (GTK_WIDGET (button_child), GTK_ALIGN_CENTER);
159       gtk_widget_show_all (button_child);
160       gtk_container_add (GTK_CONTAINER (self), button_child);
161     }
162 }
163 
164 static void
update_needs_attention(GtkWidget * widget,GtkWidget * button,gpointer data)165 update_needs_attention (GtkWidget *widget, GtkWidget *button, gpointer data)
166 {
167   GtkContainer *container;
168   gboolean needs_attention;
169   GtkStyleContext *context;
170 
171   container = GTK_CONTAINER (data);
172   gtk_container_child_get (container, widget,
173                            "needs-attention", &needs_attention,
174                            NULL);
175 
176   context = gtk_widget_get_style_context (button);
177   if (needs_attention)
178     gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
179   else
180     gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION);
181 }
182 
183 static void
update_button(GtkStackSwitcher * self,GtkWidget * widget,GtkWidget * button)184 update_button (GtkStackSwitcher *self,
185                GtkWidget        *widget,
186                GtkWidget        *button)
187 {
188   gchar *title;
189   gchar *icon_name;
190   GtkStackSwitcherPrivate *priv;
191 
192   priv = gtk_stack_switcher_get_instance_private (self);
193 
194   gtk_container_child_get (GTK_CONTAINER (priv->stack), widget,
195                            "title", &title,
196                            "icon-name", &icon_name,
197                            NULL);
198 
199   rebuild_child (button, icon_name, title, priv->icon_size);
200 
201   gtk_widget_set_visible (button, gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL));
202 
203   g_free (title);
204   g_free (icon_name);
205 
206   update_needs_attention (widget, button, priv->stack);
207 }
208 
209 static void
on_title_icon_visible_updated(GtkWidget * widget,GParamSpec * pspec,GtkStackSwitcher * self)210 on_title_icon_visible_updated (GtkWidget        *widget,
211                                GParamSpec       *pspec,
212                                GtkStackSwitcher *self)
213 {
214   GtkWidget *button;
215   GtkStackSwitcherPrivate *priv;
216 
217   priv = gtk_stack_switcher_get_instance_private (self);
218 
219   button = g_hash_table_lookup (priv->buttons, widget);
220   update_button (self, widget, button);
221 }
222 
223 static void
on_position_updated(GtkWidget * widget,GParamSpec * pspec,GtkStackSwitcher * self)224 on_position_updated (GtkWidget        *widget,
225                      GParamSpec       *pspec,
226                      GtkStackSwitcher *self)
227 {
228   GtkWidget *button;
229   gint position;
230   GtkStackSwitcherPrivate *priv;
231 
232   priv = gtk_stack_switcher_get_instance_private (self);
233 
234   button = g_hash_table_lookup (priv->buttons, widget);
235 
236   gtk_container_child_get (GTK_CONTAINER (priv->stack), widget,
237                            "position", &position,
238                            NULL);
239 
240   gtk_box_reorder_child (GTK_BOX (self), button, position);
241 }
242 
243 static void
on_needs_attention_updated(GtkWidget * widget,GParamSpec * pspec,GtkStackSwitcher * self)244 on_needs_attention_updated (GtkWidget        *widget,
245                             GParamSpec       *pspec,
246                             GtkStackSwitcher *self)
247 {
248   GtkWidget *button;
249   GtkStackSwitcherPrivate *priv;
250 
251   priv = gtk_stack_switcher_get_instance_private (self);
252 
253   button = g_hash_table_lookup (priv->buttons, widget);
254   update_button (self, widget, button);
255 }
256 
257 static void
remove_switch_timer(GtkStackSwitcher * self)258 remove_switch_timer (GtkStackSwitcher *self)
259 {
260   GtkStackSwitcherPrivate *priv;
261 
262   priv = gtk_stack_switcher_get_instance_private (self);
263 
264   if (priv->switch_timer)
265     {
266       g_source_remove (priv->switch_timer);
267       priv->switch_timer = 0;
268     }
269 }
270 
271 static gboolean
gtk_stack_switcher_switch_timeout(gpointer data)272 gtk_stack_switcher_switch_timeout (gpointer data)
273 {
274   GtkStackSwitcher *self = data;
275   GtkStackSwitcherPrivate *priv;
276   GtkWidget *button;
277 
278   priv = gtk_stack_switcher_get_instance_private (self);
279 
280   priv->switch_timer = 0;
281 
282   button = priv->switch_button;
283   priv->switch_button = NULL;
284 
285   if (button)
286     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
287 
288   return G_SOURCE_REMOVE;
289 }
290 
291 static gboolean
gtk_stack_switcher_drag_motion(GtkWidget * widget,GdkDragContext * context,gint x,gint y,guint time)292 gtk_stack_switcher_drag_motion (GtkWidget      *widget,
293                                 GdkDragContext *context,
294                                 gint            x,
295                                 gint            y,
296                                 guint           time)
297 {
298   GtkStackSwitcher *self = GTK_STACK_SWITCHER (widget);
299   GtkStackSwitcherPrivate *priv;
300   GtkAllocation allocation;
301   GtkWidget *button;
302   GHashTableIter iter;
303   gpointer value;
304   gboolean retval = FALSE;
305 
306   gtk_widget_get_allocation (widget, &allocation);
307 
308   priv = gtk_stack_switcher_get_instance_private (self);
309 
310   x += allocation.x;
311   y += allocation.y;
312 
313   button = NULL;
314   g_hash_table_iter_init (&iter, priv->buttons);
315   while (g_hash_table_iter_next (&iter, NULL, &value))
316     {
317       gtk_widget_get_allocation (GTK_WIDGET (value), &allocation);
318       if (x >= allocation.x && x <= allocation.x + allocation.width &&
319           y >= allocation.y && y <= allocation.y + allocation.height)
320         {
321           button = GTK_WIDGET (value);
322           retval = TRUE;
323           break;
324         }
325     }
326 
327   if (button != priv->switch_button)
328     remove_switch_timer (self);
329 
330   priv->switch_button = button;
331 
332   if (button && !priv->switch_timer)
333     {
334       priv->switch_timer = gdk_threads_add_timeout (TIMEOUT_EXPAND,
335                                                     gtk_stack_switcher_switch_timeout,
336                                                     self);
337       g_source_set_name_by_id (priv->switch_timer, "[gtk+] gtk_stack_switcher_switch_timeout");
338     }
339 
340   return retval;
341 }
342 
343 static void
gtk_stack_switcher_drag_leave(GtkWidget * widget,GdkDragContext * context,guint time)344 gtk_stack_switcher_drag_leave (GtkWidget      *widget,
345                                GdkDragContext *context,
346                                guint           time)
347 {
348   GtkStackSwitcher *self = GTK_STACK_SWITCHER (widget);
349 
350   remove_switch_timer (self);
351 }
352 
353 static void
add_child(GtkWidget * widget,GtkStackSwitcher * self)354 add_child (GtkWidget        *widget,
355            GtkStackSwitcher *self)
356 {
357   GtkWidget *button;
358   GList *group;
359   GtkStackSwitcherPrivate *priv;
360 
361   priv = gtk_stack_switcher_get_instance_private (self);
362 
363   button = gtk_radio_button_new (NULL);
364 
365   gtk_widget_set_focus_on_click (button, FALSE);
366   gtk_toggle_button_set_mode (GTK_TOGGLE_BUTTON (button), FALSE);
367 
368   update_button (self, widget, button);
369 
370   group = gtk_container_get_children (GTK_CONTAINER (self));
371   if (group != NULL)
372     {
373       gtk_radio_button_join_group (GTK_RADIO_BUTTON (button), GTK_RADIO_BUTTON (group->data));
374       g_list_free (group);
375     }
376 
377   gtk_container_add (GTK_CONTAINER (self), button);
378 
379   g_object_set_data (G_OBJECT (button), "stack-child", widget);
380   g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), self);
381   g_signal_connect (widget, "notify::visible", G_CALLBACK (on_title_icon_visible_updated), self);
382   g_signal_connect (widget, "child-notify::title", G_CALLBACK (on_title_icon_visible_updated), self);
383   g_signal_connect (widget, "child-notify::icon-name", G_CALLBACK (on_title_icon_visible_updated), self);
384   g_signal_connect (widget, "child-notify::position", G_CALLBACK (on_position_updated), self);
385   g_signal_connect (widget, "child-notify::needs-attention", G_CALLBACK (on_needs_attention_updated), self);
386 
387   g_hash_table_insert (priv->buttons, widget, button);
388 }
389 
390 static void
remove_child(GtkWidget * widget,GtkStackSwitcher * self)391 remove_child (GtkWidget        *widget,
392               GtkStackSwitcher *self)
393 {
394   GtkWidget *button;
395   GtkStackSwitcherPrivate *priv;
396 
397   priv = gtk_stack_switcher_get_instance_private (self);
398 
399   g_signal_handlers_disconnect_by_func (widget, on_title_icon_visible_updated, self);
400   g_signal_handlers_disconnect_by_func (widget, on_position_updated, self);
401   g_signal_handlers_disconnect_by_func (widget, on_needs_attention_updated, self);
402 
403   button = g_hash_table_lookup (priv->buttons, widget);
404   gtk_container_remove (GTK_CONTAINER (self), button);
405   g_hash_table_remove (priv->buttons, widget);
406 }
407 
408 static void
populate_switcher(GtkStackSwitcher * self)409 populate_switcher (GtkStackSwitcher *self)
410 {
411   GtkStackSwitcherPrivate *priv;
412   GtkWidget *widget, *button;
413 
414   priv = gtk_stack_switcher_get_instance_private (self);
415   gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)add_child, self);
416 
417   widget = gtk_stack_get_visible_child (priv->stack);
418   if (widget)
419     {
420       button = g_hash_table_lookup (priv->buttons, widget);
421       priv->in_child_changed = TRUE;
422       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
423       priv->in_child_changed = FALSE;
424     }
425 }
426 
427 static void
clear_switcher(GtkStackSwitcher * self)428 clear_switcher (GtkStackSwitcher *self)
429 {
430   GtkStackSwitcherPrivate *priv;
431 
432   priv = gtk_stack_switcher_get_instance_private (self);
433   gtk_container_foreach (GTK_CONTAINER (priv->stack), (GtkCallback)remove_child, self);
434 }
435 
436 static void
on_child_changed(GtkWidget * widget,GParamSpec * pspec,GtkStackSwitcher * self)437 on_child_changed (GtkWidget        *widget,
438                   GParamSpec       *pspec,
439                   GtkStackSwitcher *self)
440 {
441   GtkWidget *child;
442   GtkWidget *button;
443   GtkStackSwitcherPrivate *priv;
444 
445   priv = gtk_stack_switcher_get_instance_private (self);
446 
447   child = gtk_stack_get_visible_child (GTK_STACK (widget));
448   button = g_hash_table_lookup (priv->buttons, child);
449   if (button != NULL)
450     {
451       priv->in_child_changed = TRUE;
452       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
453       priv->in_child_changed = FALSE;
454     }
455 }
456 
457 static void
on_stack_child_added(GtkContainer * container,GtkWidget * widget,GtkStackSwitcher * self)458 on_stack_child_added (GtkContainer     *container,
459                       GtkWidget        *widget,
460                       GtkStackSwitcher *self)
461 {
462   add_child (widget, self);
463 }
464 
465 static void
on_stack_child_removed(GtkContainer * container,GtkWidget * widget,GtkStackSwitcher * self)466 on_stack_child_removed (GtkContainer     *container,
467                         GtkWidget        *widget,
468                         GtkStackSwitcher *self)
469 {
470   remove_child (widget, self);
471 }
472 
473 static void
disconnect_stack_signals(GtkStackSwitcher * switcher)474 disconnect_stack_signals (GtkStackSwitcher *switcher)
475 {
476   GtkStackSwitcherPrivate *priv;
477 
478   priv = gtk_stack_switcher_get_instance_private (switcher);
479   g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_added, switcher);
480   g_signal_handlers_disconnect_by_func (priv->stack, on_stack_child_removed, switcher);
481   g_signal_handlers_disconnect_by_func (priv->stack, on_child_changed, switcher);
482   g_signal_handlers_disconnect_by_func (priv->stack, disconnect_stack_signals, switcher);
483 }
484 
485 static void
connect_stack_signals(GtkStackSwitcher * switcher)486 connect_stack_signals (GtkStackSwitcher *switcher)
487 {
488   GtkStackSwitcherPrivate *priv;
489 
490   priv = gtk_stack_switcher_get_instance_private (switcher);
491   g_signal_connect_after (priv->stack, "add",
492                           G_CALLBACK (on_stack_child_added), switcher);
493   g_signal_connect_after (priv->stack, "remove",
494                           G_CALLBACK (on_stack_child_removed), switcher);
495   g_signal_connect (priv->stack, "notify::visible-child",
496                     G_CALLBACK (on_child_changed), switcher);
497   g_signal_connect_swapped (priv->stack, "destroy",
498                             G_CALLBACK (disconnect_stack_signals), switcher);
499 }
500 
501 /**
502  * gtk_stack_switcher_set_stack:
503  * @switcher: a #GtkStackSwitcher
504  * @stack: (allow-none): a #GtkStack
505  *
506  * Sets the stack to control.
507  *
508  * Since: 3.10
509  */
510 void
gtk_stack_switcher_set_stack(GtkStackSwitcher * switcher,GtkStack * stack)511 gtk_stack_switcher_set_stack (GtkStackSwitcher *switcher,
512                               GtkStack         *stack)
513 {
514   GtkStackSwitcherPrivate *priv;
515 
516   g_return_if_fail (GTK_IS_STACK_SWITCHER (switcher));
517   g_return_if_fail (GTK_IS_STACK (stack) || stack == NULL);
518 
519   priv = gtk_stack_switcher_get_instance_private (switcher);
520 
521   if (priv->stack == stack)
522     return;
523 
524   if (priv->stack)
525     {
526       disconnect_stack_signals (switcher);
527       clear_switcher (switcher);
528       g_clear_object (&priv->stack);
529     }
530   if (stack)
531     {
532       priv->stack = g_object_ref (stack);
533       populate_switcher (switcher);
534       connect_stack_signals (switcher);
535     }
536 
537   gtk_widget_queue_resize (GTK_WIDGET (switcher));
538 
539   g_object_notify (G_OBJECT (switcher), "stack");
540 }
541 
542 /**
543  * gtk_stack_switcher_get_stack:
544  * @switcher: a #GtkStackSwitcher
545  *
546  * Retrieves the stack.
547  * See gtk_stack_switcher_set_stack().
548  *
549  * Returns: (nullable) (transfer none): the stack, or %NULL if
550  *    none has been set explicitly.
551  *
552  * Since: 3.10
553  */
554 GtkStack *
gtk_stack_switcher_get_stack(GtkStackSwitcher * switcher)555 gtk_stack_switcher_get_stack (GtkStackSwitcher *switcher)
556 {
557   GtkStackSwitcherPrivate *priv;
558   g_return_val_if_fail (GTK_IS_STACK_SWITCHER (switcher), NULL);
559 
560   priv = gtk_stack_switcher_get_instance_private (switcher);
561   return priv->stack;
562 }
563 
564 static void
gtk_stack_switcher_set_icon_size(GtkStackSwitcher * switcher,gint icon_size)565 gtk_stack_switcher_set_icon_size (GtkStackSwitcher *switcher,
566                                   gint              icon_size)
567 {
568   GtkStackSwitcherPrivate *priv;
569 
570   g_return_if_fail (GTK_IS_STACK_SWITCHER (switcher));
571 
572   priv = gtk_stack_switcher_get_instance_private (switcher);
573 
574   if (icon_size != priv->icon_size)
575     {
576       priv->icon_size = icon_size;
577 
578       if (priv->stack != NULL)
579         {
580           clear_switcher (switcher);
581           populate_switcher (switcher);
582         }
583 
584       g_object_notify (G_OBJECT (switcher), "icon-size");
585     }
586 }
587 
588 static void
gtk_stack_switcher_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)589 gtk_stack_switcher_get_property (GObject      *object,
590                                  guint         prop_id,
591                                  GValue       *value,
592                                  GParamSpec   *pspec)
593 {
594   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
595   GtkStackSwitcherPrivate *priv;
596 
597   priv = gtk_stack_switcher_get_instance_private (switcher);
598   switch (prop_id)
599     {
600     case PROP_ICON_SIZE:
601       g_value_set_int (value, priv->icon_size);
602       break;
603 
604     case PROP_STACK:
605       g_value_set_object (value, priv->stack);
606       break;
607 
608     default:
609       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
610       break;
611     }
612 }
613 
614 static void
gtk_stack_switcher_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)615 gtk_stack_switcher_set_property (GObject      *object,
616                                  guint         prop_id,
617                                  const GValue *value,
618                                  GParamSpec   *pspec)
619 {
620   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
621 
622   switch (prop_id)
623     {
624     case PROP_ICON_SIZE:
625       gtk_stack_switcher_set_icon_size (switcher, g_value_get_int (value));
626       break;
627 
628     case PROP_STACK:
629       gtk_stack_switcher_set_stack (switcher, g_value_get_object (value));
630       break;
631 
632     default:
633       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
634       break;
635     }
636 }
637 
638 static void
gtk_stack_switcher_dispose(GObject * object)639 gtk_stack_switcher_dispose (GObject *object)
640 {
641   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
642 
643   remove_switch_timer (switcher);
644   gtk_stack_switcher_set_stack (switcher, NULL);
645 
646   G_OBJECT_CLASS (gtk_stack_switcher_parent_class)->dispose (object);
647 }
648 
649 static void
gtk_stack_switcher_finalize(GObject * object)650 gtk_stack_switcher_finalize (GObject *object)
651 {
652   GtkStackSwitcher *switcher = GTK_STACK_SWITCHER (object);
653   GtkStackSwitcherPrivate *priv;
654 
655   priv = gtk_stack_switcher_get_instance_private (switcher);
656 
657   g_hash_table_destroy (priv->buttons);
658 
659   G_OBJECT_CLASS (gtk_stack_switcher_parent_class)->finalize (object);
660 }
661 
662 static void
gtk_stack_switcher_class_init(GtkStackSwitcherClass * class)663 gtk_stack_switcher_class_init (GtkStackSwitcherClass *class)
664 {
665   GObjectClass *object_class = G_OBJECT_CLASS (class);
666   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
667 
668   object_class->get_property = gtk_stack_switcher_get_property;
669   object_class->set_property = gtk_stack_switcher_set_property;
670   object_class->dispose = gtk_stack_switcher_dispose;
671   object_class->finalize = gtk_stack_switcher_finalize;
672 
673   widget_class->drag_motion = gtk_stack_switcher_drag_motion;
674   widget_class->drag_leave = gtk_stack_switcher_drag_leave;
675   /**
676    * GtkStackSwitcher:icon-size:
677    *
678    * Use the "icon-size" property to change the size of the image displayed
679    * when a #GtkStackSwitcher is displaying icons.
680    *
681    * Since: 3.20
682    */
683   g_object_class_install_property (object_class,
684                                    PROP_ICON_SIZE,
685                                    g_param_spec_int ("icon-size",
686                                                      P_("Icon Size"),
687                                                      P_("Symbolic size to use for named icon"),
688                                                      0, G_MAXINT,
689                                                      GTK_ICON_SIZE_MENU,
690                                                      G_PARAM_EXPLICIT_NOTIFY | GTK_PARAM_READWRITE));
691 
692   g_object_class_install_property (object_class,
693                                    PROP_STACK,
694                                    g_param_spec_object ("stack",
695                                                         P_("Stack"),
696                                                         P_("Stack"),
697                                                         GTK_TYPE_STACK,
698                                                         GTK_PARAM_READWRITE |
699                                                         G_PARAM_CONSTRUCT));
700 
701   gtk_widget_class_set_css_name (widget_class, "stackswitcher");
702 }
703 
704 /**
705  * gtk_stack_switcher_new:
706  *
707  * Create a new #GtkStackSwitcher.
708  *
709  * Returns: a new #GtkStackSwitcher.
710  *
711  * Since: 3.10
712  */
713 GtkWidget *
gtk_stack_switcher_new(void)714 gtk_stack_switcher_new (void)
715 {
716   return g_object_new (GTK_TYPE_STACK_SWITCHER, NULL);
717 }
718