1 /*
2  * Copyright (c) 2016 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 "gtkstackcombo.h"
22 #include "gtkbox.h"
23 #include "gtkstack.h"
24 #include "gtkcomboboxtext.h"
25 #include "gtkprivate.h"
26 #include "gtkintl.h"
27 
28 struct _GtkStackCombo
29 {
30   GtkBox box;
31 
32   GtkComboBox *combo;
33   GtkStack *stack;
34   GBinding *binding;
35 };
36 
37 struct _GtkStackComboClass {
38   GtkBoxClass parent_class;
39 };
40 
41 enum {
42   PROP_0,
43   PROP_STACK
44 };
45 
G_DEFINE_TYPE(GtkStackCombo,gtk_stack_combo,GTK_TYPE_BOX)46 G_DEFINE_TYPE (GtkStackCombo, gtk_stack_combo, GTK_TYPE_BOX)
47 
48 static void
49 gtk_stack_combo_init (GtkStackCombo *self)
50 {
51   self->stack = NULL;
52   self->combo = GTK_COMBO_BOX (gtk_combo_box_text_new ());
53   gtk_widget_show (GTK_WIDGET (self->combo));
54   gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (self->combo), FALSE, FALSE, 0);
55 }
56 
57 static void gtk_stack_combo_set_stack (GtkStackCombo *self,
58                                        GtkStack      *stack);
59 
60 static void
rebuild_combo(GtkStackCombo * self)61 rebuild_combo (GtkStackCombo *self)
62 {
63   gtk_stack_combo_set_stack (self, self->stack);
64 }
65 
66 static void
on_child_visible_changed(GtkStackCombo * self)67 on_child_visible_changed (GtkStackCombo *self)
68 {
69   rebuild_combo (self);
70 }
71 
72 static void
add_child(GtkWidget * widget,GtkStackCombo * self)73 add_child (GtkWidget     *widget,
74            GtkStackCombo *self)
75 {
76   g_signal_handlers_disconnect_by_func (widget, G_CALLBACK (on_child_visible_changed), self);
77   g_signal_connect_swapped (widget, "notify::visible", G_CALLBACK (on_child_visible_changed), self);
78 
79   if (gtk_widget_get_visible (widget))
80     {
81       char *name, *title;
82 
83       gtk_container_child_get (GTK_CONTAINER (self->stack), widget,
84                                "name", &name,
85                               "title", &title,
86                                NULL);
87 
88       gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (self->combo), name, title);
89 
90       g_free (name);
91       g_free (title);
92     }
93 }
94 
95 static void
populate_combo(GtkStackCombo * self)96 populate_combo (GtkStackCombo *self)
97 {
98   gtk_container_foreach (GTK_CONTAINER (self->stack), (GtkCallback)add_child, self);
99 }
100 
101 static void
clear_combo(GtkStackCombo * self)102 clear_combo (GtkStackCombo *self)
103 {
104   gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (self->combo));
105 }
106 
107 static void
on_stack_child_added(GtkContainer * container,GtkWidget * widget,GtkStackCombo * self)108 on_stack_child_added (GtkContainer  *container,
109                       GtkWidget     *widget,
110                       GtkStackCombo *self)
111 {
112   rebuild_combo (self);
113 }
114 
115 static void
on_stack_child_removed(GtkContainer * container,GtkWidget * widget,GtkStackCombo * self)116 on_stack_child_removed (GtkContainer  *container,
117                         GtkWidget     *widget,
118                         GtkStackCombo *self)
119 {
120   g_signal_handlers_disconnect_by_func (widget, G_CALLBACK (on_child_visible_changed), self);
121   rebuild_combo (self);
122 }
123 
124 static void
disconnect_stack_signals(GtkStackCombo * self)125 disconnect_stack_signals (GtkStackCombo *self)
126 {
127   g_binding_unbind (self->binding);
128   self->binding = NULL;
129   g_signal_handlers_disconnect_by_func (self->stack, on_stack_child_added, self);
130   g_signal_handlers_disconnect_by_func (self->stack, on_stack_child_removed, self);
131   g_signal_handlers_disconnect_by_func (self->stack, disconnect_stack_signals, self);
132 }
133 
134 static void
connect_stack_signals(GtkStackCombo * self)135 connect_stack_signals (GtkStackCombo *self)
136 {
137   g_signal_connect_after (self->stack, "add", G_CALLBACK (on_stack_child_added), self);
138   g_signal_connect_after (self->stack, "remove", G_CALLBACK (on_stack_child_removed), self);
139   g_signal_connect_swapped (self->stack, "destroy", G_CALLBACK (disconnect_stack_signals), self);
140   self->binding = g_object_bind_property (self->stack, "visible-child-name", self->combo, "active-id", G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
141 }
142 
143 static void
gtk_stack_combo_set_stack(GtkStackCombo * self,GtkStack * stack)144 gtk_stack_combo_set_stack (GtkStackCombo *self,
145                            GtkStack      *stack)
146 {
147   if (stack)
148     g_object_ref (stack);
149 
150   if (self->stack)
151     {
152       disconnect_stack_signals (self);
153       clear_combo (self);
154       g_clear_object (&self->stack);
155     }
156 
157   if (stack)
158     {
159       self->stack = stack;
160       populate_combo (self);
161       connect_stack_signals (self);
162     }
163 }
164 
165 static void
gtk_stack_combo_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)166 gtk_stack_combo_get_property (GObject    *object,
167                               guint       prop_id,
168                               GValue     *value,
169                               GParamSpec *pspec)
170 {
171   GtkStackCombo *self = GTK_STACK_COMBO (object);
172 
173   switch (prop_id)
174     {
175     case PROP_STACK:
176       g_value_set_object (value, self->stack);
177       break;
178 
179     default:
180       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
181       break;
182     }
183 }
184 
185 static void
gtk_stack_combo_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)186 gtk_stack_combo_set_property (GObject      *object,
187                               guint         prop_id,
188                               const GValue *value,
189                               GParamSpec   *pspec)
190 {
191   GtkStackCombo *self = GTK_STACK_COMBO (object);
192 
193   switch (prop_id)
194     {
195     case PROP_STACK:
196       if (self->stack != g_value_get_object (value))
197         {
198           gtk_stack_combo_set_stack (self, g_value_get_object (value));
199           g_object_notify (G_OBJECT (self), "stack");
200         }
201       break;
202 
203     default:
204       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
205       break;
206     }
207 }
208 
209 static void
gtk_stack_combo_dispose(GObject * object)210 gtk_stack_combo_dispose (GObject *object)
211 {
212   GtkStackCombo *self = GTK_STACK_COMBO (object);
213 
214   gtk_stack_combo_set_stack (self, NULL);
215 
216   G_OBJECT_CLASS (gtk_stack_combo_parent_class)->dispose (object);
217 }
218 
219 static void
gtk_stack_combo_class_init(GtkStackComboClass * class)220 gtk_stack_combo_class_init (GtkStackComboClass *class)
221 {
222   GObjectClass *object_class = G_OBJECT_CLASS (class);
223   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
224 
225   object_class->get_property = gtk_stack_combo_get_property;
226   object_class->set_property = gtk_stack_combo_set_property;
227   object_class->dispose = gtk_stack_combo_dispose;
228 
229   g_object_class_install_property (object_class,
230                                    PROP_STACK,
231                                    g_param_spec_object ("stack",
232                                                         P_("Stack"),
233                                                         P_("Stack"),
234                                                         GTK_TYPE_STACK,
235                                                         GTK_PARAM_READWRITE |
236                                                         G_PARAM_CONSTRUCT));
237 
238   gtk_widget_class_set_css_name (widget_class, "stackcombo");
239 }
240