1 /*
2  * Copyright (c) 2013 - 2014 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 
22 #include "gtkactionbar.h"
23 #include "gtkintl.h"
24 #include "gtkbuildable.h"
25 #include "gtktypebuiltins.h"
26 #include "gtkbox.h"
27 #include "gtkrevealer.h"
28 #include "gtkwidgetprivate.h"
29 #include "gtkprivate.h"
30 #include "gtkcenterbox.h"
31 #include "gtkbinlayout.h"
32 
33 #include <string.h>
34 
35 /**
36  * GtkActionBar:
37  *
38  * `GtkActionBar` is designed to present contextual actions.
39  *
40  * ![An example GtkActionBar](action-bar.png)
41  *
42  * It is expected to be displayed below the content and expand
43  * horizontally to fill the area.
44  *
45  * It allows placing children at the start or the end. In addition, it
46  * contains an internal centered box which is centered with respect to
47  * the full width of the box, even if the children at either side take
48  * up different amounts of space.
49  *
50  * # CSS nodes
51  *
52  * ```
53  * actionbar
54  * ╰── revealer
55  *     ╰── box
56  *         ├── box.start
57  *         │   ╰── [start children]
58  *         ├── [center widget]
59  *         ╰── box.end
60  *             ╰── [end children]
61  * ```
62  *
63  * A `GtkActionBar`'s CSS node is called `actionbar`. It contains a `revealer`
64  * subnode, which contains a `box` subnode, which contains two `box` subnodes at
65  * the start and end of the action bar, with `start` and `end style classes
66  * respectively, as well as a center node that represents the center child.
67  *
68  * Each of the boxes contains children packed for that side.
69  */
70 
71 typedef struct _GtkActionBarClass         GtkActionBarClass;
72 
73 struct _GtkActionBar
74 {
75   GtkWidget parent;
76 
77   GtkWidget *center_box;
78   GtkWidget *start_box;
79   GtkWidget *end_box;
80   GtkWidget *revealer;
81 };
82 
83 struct _GtkActionBarClass
84 {
85   GtkWidgetClass parent_class;
86 };
87 
88 enum {
89   PROP_0,
90   PROP_REVEALED,
91   LAST_PROP
92 };
93 static GParamSpec *props[LAST_PROP] = { NULL, };
94 
95 static void gtk_action_bar_buildable_interface_init (GtkBuildableIface *iface);
96 
G_DEFINE_TYPE_WITH_CODE(GtkActionBar,gtk_action_bar,GTK_TYPE_WIDGET,G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_action_bar_buildable_interface_init))97 G_DEFINE_TYPE_WITH_CODE (GtkActionBar, gtk_action_bar, GTK_TYPE_WIDGET,
98                          G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
99                                                 gtk_action_bar_buildable_interface_init))
100 
101 static void
102 gtk_action_bar_set_property (GObject      *object,
103                              guint         prop_id,
104                              const GValue *value,
105                              GParamSpec   *pspec)
106 {
107   GtkActionBar *self = GTK_ACTION_BAR (object);
108 
109   switch (prop_id)
110     {
111     case PROP_REVEALED:
112       gtk_action_bar_set_revealed (self, g_value_get_boolean (value));
113       break;
114     default:
115       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
116       break;
117     }
118 }
119 
120 static void
gtk_action_bar_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)121 gtk_action_bar_get_property (GObject    *object,
122                              guint       prop_id,
123                              GValue     *value,
124                              GParamSpec *pspec)
125 {
126   GtkActionBar *self = GTK_ACTION_BAR (object);
127 
128   switch (prop_id)
129     {
130     case PROP_REVEALED:
131       g_value_set_boolean (value, gtk_action_bar_get_revealed (self));
132       break;
133     default:
134       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
135       break;
136     }
137 }
138 
139 static void
gtk_action_bar_dispose(GObject * object)140 gtk_action_bar_dispose (GObject *object)
141 {
142   GtkActionBar *self = GTK_ACTION_BAR (object);
143 
144   g_clear_pointer (&self->revealer, gtk_widget_unparent);
145 
146   self->center_box = NULL;
147   self->start_box = NULL;
148   self->end_box = NULL;
149 
150   G_OBJECT_CLASS (gtk_action_bar_parent_class)->dispose (object);
151 }
152 
153 static void
gtk_action_bar_class_init(GtkActionBarClass * klass)154 gtk_action_bar_class_init (GtkActionBarClass *klass)
155 {
156   GObjectClass *object_class;
157   GtkWidgetClass *widget_class;
158 
159   object_class = G_OBJECT_CLASS (klass);
160   widget_class = GTK_WIDGET_CLASS (klass);
161 
162   object_class->set_property = gtk_action_bar_set_property;
163   object_class->get_property = gtk_action_bar_get_property;
164   object_class->dispose = gtk_action_bar_dispose;
165 
166   widget_class->focus = gtk_widget_focus_child;
167 
168   /**
169    * GtkActionBar:revealed: (attributes org.gtk.Property.get=gtk_action_bar_get_revealed org.gtk.Property.set=gtk_action_bar_set_revealed)
170    *
171    * Controls whether the action bar shows its contents.
172    */
173   props[PROP_REVEALED] =
174     g_param_spec_boolean ("revealed",
175                           P_("Reveal"),
176                           P_("Controls whether the action bar shows its contents or not"),
177                           TRUE,
178                           GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
179 
180   g_object_class_install_properties (object_class, LAST_PROP, props);
181 
182   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
183   gtk_widget_class_set_css_name (widget_class, I_("actionbar"));
184 }
185 
186 static void
gtk_action_bar_init(GtkActionBar * self)187 gtk_action_bar_init (GtkActionBar *self)
188 {
189   GtkWidget *widget = GTK_WIDGET (self);
190 
191   self->revealer = gtk_revealer_new ();
192   gtk_widget_set_parent (self->revealer, widget);
193 
194   gtk_revealer_set_reveal_child (GTK_REVEALER (self->revealer), TRUE);
195   gtk_revealer_set_transition_type (GTK_REVEALER (self->revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
196 
197   self->start_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
198   self->end_box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
199 
200   gtk_widget_add_css_class (self->start_box, "start");
201   gtk_widget_add_css_class (self->end_box, "end");
202 
203   self->center_box = gtk_center_box_new ();
204   gtk_center_box_set_start_widget (GTK_CENTER_BOX (self->center_box), self->start_box);
205   gtk_center_box_set_end_widget (GTK_CENTER_BOX (self->center_box), self->end_box);
206 
207   gtk_revealer_set_child (GTK_REVEALER (self->revealer), self->center_box);
208 }
209 
210 static GtkBuildableIface *parent_buildable_iface;
211 
212 static void
gtk_action_bar_buildable_add_child(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * type)213 gtk_action_bar_buildable_add_child (GtkBuildable *buildable,
214                                     GtkBuilder   *builder,
215                                     GObject      *child,
216                                     const char   *type)
217 {
218   GtkActionBar *self = GTK_ACTION_BAR (buildable);
219 
220   if (g_strcmp0 (type, "start") == 0)
221     gtk_action_bar_pack_start (self, GTK_WIDGET (child));
222   else if (g_strcmp0 (type, "center") == 0)
223     gtk_action_bar_set_center_widget (self, GTK_WIDGET (child));
224   else if (g_strcmp0 (type, "end") == 0)
225     gtk_action_bar_pack_end (self, GTK_WIDGET (child));
226   else if (type == NULL && GTK_IS_WIDGET (child))
227     gtk_action_bar_pack_start (self, GTK_WIDGET (child));
228   else
229     parent_buildable_iface->add_child (buildable, builder, child, type);
230 }
231 
232 static void
gtk_action_bar_buildable_interface_init(GtkBuildableIface * iface)233 gtk_action_bar_buildable_interface_init (GtkBuildableIface *iface)
234 {
235   parent_buildable_iface = g_type_interface_peek_parent (iface);
236   iface->add_child = gtk_action_bar_buildable_add_child;
237 }
238 
239 /**
240  * gtk_action_bar_pack_start:
241  * @action_bar: A `GtkActionBar`
242  * @child: the `GtkWidget` to be added to @action_bar
243  *
244  * Adds @child to @action_bar, packed with reference to the
245  * start of the @action_bar.
246  */
247 void
gtk_action_bar_pack_start(GtkActionBar * action_bar,GtkWidget * child)248 gtk_action_bar_pack_start (GtkActionBar *action_bar,
249                            GtkWidget    *child)
250 {
251   gtk_box_append (GTK_BOX (action_bar->start_box), child);
252 }
253 
254 /**
255  * gtk_action_bar_pack_end:
256  * @action_bar: A `GtkActionBar`
257  * @child: the `GtkWidget` to be added to @action_bar
258  *
259  * Adds @child to @action_bar, packed with reference to the
260  * end of the @action_bar.
261  */
262 void
gtk_action_bar_pack_end(GtkActionBar * action_bar,GtkWidget * child)263 gtk_action_bar_pack_end (GtkActionBar *action_bar,
264                          GtkWidget    *child)
265 {
266   gtk_box_insert_child_after (GTK_BOX (action_bar->end_box), child, NULL);
267 }
268 
269 /**
270  * gtk_action_bar_remove:
271  * @action_bar: a `GtkActionBar`
272  * @child: the `GtkWidget` to be removed
273  *
274  * Removes a child from @action_bar.
275  */
276 void
gtk_action_bar_remove(GtkActionBar * action_bar,GtkWidget * child)277 gtk_action_bar_remove (GtkActionBar *action_bar,
278                        GtkWidget    *child)
279 {
280   if (gtk_widget_get_parent (child) == action_bar->start_box)
281     gtk_box_remove (GTK_BOX (action_bar->start_box), child);
282   else if (gtk_widget_get_parent (child) == action_bar->end_box)
283     gtk_box_remove (GTK_BOX (action_bar->end_box), child);
284   else if (child == gtk_center_box_get_center_widget (GTK_CENTER_BOX (action_bar->center_box)))
285     gtk_center_box_set_center_widget (GTK_CENTER_BOX (action_bar->center_box), NULL);
286   else
287     g_warning ("Can't remove non-child %s %p from GtkActionBar %p",
288                G_OBJECT_TYPE_NAME (child), child, action_bar);
289 }
290 
291 /**
292  * gtk_action_bar_set_center_widget:
293  * @action_bar: a `GtkActionBar`
294  * @center_widget: (nullable): a widget to use for the center
295  *
296  * Sets the center widget for the `GtkActionBar`.
297  */
298 void
gtk_action_bar_set_center_widget(GtkActionBar * action_bar,GtkWidget * center_widget)299 gtk_action_bar_set_center_widget (GtkActionBar *action_bar,
300                                   GtkWidget    *center_widget)
301 {
302   gtk_center_box_set_center_widget (GTK_CENTER_BOX (action_bar->center_box), center_widget);
303 }
304 
305 /**
306  * gtk_action_bar_get_center_widget:
307  * @action_bar: a `GtkActionBar`
308  *
309  * Retrieves the center bar widget of the bar.
310  *
311  * Returns: (transfer none) (nullable): the center `GtkWidget`
312  */
313 GtkWidget *
gtk_action_bar_get_center_widget(GtkActionBar * action_bar)314 gtk_action_bar_get_center_widget (GtkActionBar *action_bar)
315 {
316   g_return_val_if_fail (GTK_IS_ACTION_BAR (action_bar), NULL);
317 
318   return gtk_center_box_get_center_widget (GTK_CENTER_BOX (action_bar->center_box));
319 }
320 
321 /**
322  * gtk_action_bar_new:
323  *
324  * Creates a new `GtkActionBar` widget.
325  *
326  * Returns: a new `GtkActionBar`
327  */
328 GtkWidget *
gtk_action_bar_new(void)329 gtk_action_bar_new (void)
330 {
331   return GTK_WIDGET (g_object_new (GTK_TYPE_ACTION_BAR, NULL));
332 }
333 
334 /**
335  * gtk_action_bar_set_revealed: (attributes org.gtk.Method.set_property=revealed)
336  * @action_bar: a `GtkActionBar`
337  * @revealed: The new value of the property
338  *
339  * Reveals or conceals the content of the action bar.
340  *
341  * Note: this does not show or hide @action_bar in the
342  * [property@Gtk.Widget:visible] sense, so revealing has
343  * no effect if the action bar is hidden.
344  */
345 void
gtk_action_bar_set_revealed(GtkActionBar * action_bar,gboolean revealed)346 gtk_action_bar_set_revealed (GtkActionBar *action_bar,
347                              gboolean      revealed)
348 {
349   g_return_if_fail (GTK_IS_ACTION_BAR (action_bar));
350 
351   if (revealed == gtk_revealer_get_reveal_child (GTK_REVEALER (action_bar->revealer)))
352     return;
353 
354   gtk_revealer_set_reveal_child (GTK_REVEALER (action_bar->revealer), revealed);
355   g_object_notify_by_pspec (G_OBJECT (action_bar), props[PROP_REVEALED]);
356 }
357 
358 /**
359  * gtk_action_bar_get_revealed: (attributes org.gtk.Method.get_property=revealed)
360  * @action_bar: a `GtkActionBar`
361  *
362  * Gets whether the contents of the action bar are revealed.
363  *
364  * Returns: the current value of the [property@Gtk.ActionBar:revealed]
365  *   property
366  */
367 gboolean
gtk_action_bar_get_revealed(GtkActionBar * action_bar)368 gtk_action_bar_get_revealed (GtkActionBar *action_bar)
369 {
370   g_return_val_if_fail (GTK_IS_ACTION_BAR (action_bar), FALSE);
371 
372   return gtk_revealer_get_reveal_child (GTK_REVEALER (action_bar->revealer));
373 }
374