1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * GtkStatusbar Copyright (C) 1998 Shawn T. Amundson
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /*
20  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
21  * file for a list of people on the GTK+ Team.  See the ChangeLog
22  * files for a list of changes.  These files are distributed with
23  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
24  */
25 
26 #include "config.h"
27 
28 #include "gtkstatusbar.h"
29 #include "gtkstatusbarprivate.h"
30 
31 #include "gtkbinlayout.h"
32 #include "gtklabel.h"
33 #include "gtkmarshalers.h"
34 #include "gtkprivate.h"
35 #include "gtkintl.h"
36 #include "gtkorientable.h"
37 #include "gtktypebuiltins.h"
38 #include "gtkwidgetprivate.h"
39 
40 /**
41  * GtkStatusbar:
42  *
43  * A `GtkStatusbar` widget is usually placed along the bottom of an application's
44  * main [class@Gtk.Window].
45  *
46  * ![An example GtkStatusbar](statusbar.png)
47  *
48  * A `GtkStatusBar` may provide a regular commentary of the application's
49  * status (as is usually the case in a web browser, for example), or may be
50  * used to simply output a message when the status changes, (when an upload
51  * is complete in an FTP client, for example).
52  *
53  * Status bars in GTK maintain a stack of messages. The message at
54  * the top of the each bar’s stack is the one that will currently be displayed.
55  *
56  * Any messages added to a statusbar’s stack must specify a context id that
57  * is used to uniquely identify the source of a message. This context id can
58  * be generated by [method@Gtk.Statusbar.get_context_id], given a message and
59  * the statusbar that it will be added to. Note that messages are stored in a
60  * stack, and when choosing which message to display, the stack structure is
61  * adhered to, regardless of the context identifier of a message.
62  *
63  * One could say that a statusbar maintains one stack of messages for
64  * display purposes, but allows multiple message producers to maintain
65  * sub-stacks of the messages they produced (via context ids).
66  *
67  * Status bars are created using [ctor@Gtk.Statusbar.new].
68  *
69  * Messages are added to the bar’s stack with [method@Gtk.Statusbar.push].
70  *
71  * The message at the top of the stack can be removed using
72  * [method@Gtk.Statusbar.pop]. A message can be removed from anywhere in the
73  * stack if its message id was recorded at the time it was added. This is done
74  * using [method@Gtk.Statusbar.remove].
75  *
76  * ## CSS node
77  *
78  * `GtkStatusbar` has a single CSS node with name `statusbar`.
79  */
80 
81 typedef struct _GtkStatusbarMsg GtkStatusbarMsg;
82 
83 typedef struct _GtkStatusbarClass         GtkStatusbarClass;
84 
85 struct _GtkStatusbar
86 {
87   GtkWidget parent_instance;
88 
89   GtkWidget     *label;
90   GtkWidget     *message_area;
91 
92   GSList        *messages;
93   GSList        *keys;
94 
95   guint          seq_context_id;
96   guint          seq_message_id;
97 };
98 
99 struct _GtkStatusbarClass
100 {
101   GtkWidgetClass parent_class;
102 
103   void  (*text_pushed)  (GtkStatusbar   *statusbar,
104                          guint           context_id,
105                          const char     *text);
106   void  (*text_popped)  (GtkStatusbar   *statusbar,
107                          guint           context_id,
108                          const char     *text);
109 };
110 
111 struct _GtkStatusbarMsg
112 {
113   char *text;
114   guint context_id;
115   guint message_id;
116 };
117 
118 enum
119 {
120   SIGNAL_TEXT_PUSHED,
121   SIGNAL_TEXT_POPPED,
122   SIGNAL_LAST
123 };
124 
125 static void     gtk_statusbar_update            (GtkStatusbar      *statusbar,
126 						 guint              context_id,
127 						 const char        *text);
128 
129 static void     gtk_statusbar_msg_free          (GtkStatusbarMsg *msg);
130 
131 static guint              statusbar_signals[SIGNAL_LAST] = { 0 };
132 
G_DEFINE_TYPE(GtkStatusbar,gtk_statusbar,GTK_TYPE_WIDGET)133 G_DEFINE_TYPE (GtkStatusbar, gtk_statusbar, GTK_TYPE_WIDGET)
134 
135 static void
136 gtk_statusbar_dispose (GObject *object)
137 {
138   GtkStatusbar *self = GTK_STATUSBAR (object);
139 
140   g_slist_free_full (self->messages, (GDestroyNotify) gtk_statusbar_msg_free);
141   self->messages = NULL;
142 
143   g_slist_free_full (self->keys, g_free);
144   self->keys = NULL;
145 
146   g_clear_pointer (&self->message_area, gtk_widget_unparent);
147 
148   G_OBJECT_CLASS (gtk_statusbar_parent_class)->dispose (object);
149 }
150 
151 static void
gtk_statusbar_class_init(GtkStatusbarClass * class)152 gtk_statusbar_class_init (GtkStatusbarClass *class)
153 {
154   GObjectClass *object_class = G_OBJECT_CLASS (class);
155   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
156 
157   object_class->dispose = gtk_statusbar_dispose;
158 
159   class->text_pushed = gtk_statusbar_update;
160   class->text_popped = gtk_statusbar_update;
161 
162   /**
163    * GtkStatusbar::text-pushed:
164    * @statusbar: the object which received the signal
165    * @context_id: the context id of the relevant message/statusbar
166    * @text: the message that was pushed
167    *
168    * Emitted whenever a new message gets pushed onto a statusbar's stack.
169    */
170   statusbar_signals[SIGNAL_TEXT_PUSHED] =
171     g_signal_new (I_("text-pushed"),
172 		  G_OBJECT_CLASS_TYPE (class),
173 		  G_SIGNAL_RUN_LAST,
174 		  G_STRUCT_OFFSET (GtkStatusbarClass, text_pushed),
175 		  NULL, NULL,
176 		  _gtk_marshal_VOID__UINT_STRING,
177 		  G_TYPE_NONE, 2,
178 		  G_TYPE_UINT,
179 		  G_TYPE_STRING);
180 
181   /**
182    * GtkStatusbar::text-popped:
183    * @statusbar: the object which received the signal
184    * @context_id: the context id of the relevant message/statusbar
185    * @text: the message that was just popped
186    *
187    * Emitted whenever a new message is popped off a statusbar's stack.
188    */
189   statusbar_signals[SIGNAL_TEXT_POPPED] =
190     g_signal_new (I_("text-popped"),
191 		  G_OBJECT_CLASS_TYPE (class),
192 		  G_SIGNAL_RUN_LAST,
193 		  G_STRUCT_OFFSET (GtkStatusbarClass, text_popped),
194 		  NULL, NULL,
195 		  _gtk_marshal_VOID__UINT_STRING,
196 		  G_TYPE_NONE, 2,
197 		  G_TYPE_UINT,
198 		  G_TYPE_STRING);
199 
200   /* Bind class to template
201    */
202   gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkstatusbar.ui");
203   gtk_widget_class_bind_template_child_internal (widget_class, GtkStatusbar, message_area);
204   gtk_widget_class_bind_template_child (widget_class, GtkStatusbar, label);
205 
206   gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
207   gtk_widget_class_set_css_name (widget_class, I_("statusbar"));
208 }
209 
210 static void
gtk_statusbar_init(GtkStatusbar * statusbar)211 gtk_statusbar_init (GtkStatusbar *statusbar)
212 {
213   statusbar->seq_context_id = 1;
214   statusbar->seq_message_id = 1;
215   statusbar->messages = NULL;
216   statusbar->keys = NULL;
217 
218   gtk_widget_init_template (GTK_WIDGET (statusbar));
219 }
220 
221 /**
222  * gtk_statusbar_new:
223  *
224  * Creates a new `GtkStatusbar` ready for messages.
225  *
226  * Returns: the new `GtkStatusbar`
227  */
228 GtkWidget*
gtk_statusbar_new(void)229 gtk_statusbar_new (void)
230 {
231   return g_object_new (GTK_TYPE_STATUSBAR, NULL);
232 }
233 
234 static void
gtk_statusbar_update(GtkStatusbar * statusbar,guint context_id,const char * text)235 gtk_statusbar_update (GtkStatusbar *statusbar,
236 		      guint	    context_id,
237 		      const char   *text)
238 {
239   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
240 
241   if (!text)
242     text = "";
243 
244   gtk_label_set_text (GTK_LABEL (statusbar->label), text);
245 }
246 
247 /**
248  * gtk_statusbar_get_context_id:
249  * @statusbar: a `GtkStatusbar`
250  * @context_description: textual description of what context
251  *   the new message is being used in
252  *
253  * Returns a new context identifier, given a description
254  * of the actual context.
255  *
256  * Note that the description is not shown in the UI.
257  *
258  * Returns: an integer id
259  */
260 guint
gtk_statusbar_get_context_id(GtkStatusbar * statusbar,const char * context_description)261 gtk_statusbar_get_context_id (GtkStatusbar *statusbar,
262 			      const char   *context_description)
263 {
264   char *string;
265   guint id;
266 
267   g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
268   g_return_val_if_fail (context_description != NULL, 0);
269 
270   /* we need to preserve namespaces on object data */
271   string = g_strconcat ("gtk-status-bar-context:", context_description, NULL);
272 
273   id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (statusbar), string));
274   if (id == 0)
275     {
276       id = statusbar->seq_context_id++;
277       g_object_set_data_full (G_OBJECT (statusbar), string, GUINT_TO_POINTER (id), NULL);
278       statusbar->keys = g_slist_prepend (statusbar->keys, string);
279     }
280   else
281     g_free (string);
282 
283   return id;
284 }
285 
286 static GtkStatusbarMsg *
gtk_statusbar_msg_create(GtkStatusbar * statusbar,guint context_id,const char * text)287 gtk_statusbar_msg_create (GtkStatusbar *statusbar,
288 		          guint         context_id,
289 		          const char   *text)
290 {
291   GtkStatusbarMsg *msg;
292 
293   msg = g_slice_new (GtkStatusbarMsg);
294   msg->text = g_strdup (text);
295   msg->context_id = context_id;
296   msg->message_id = statusbar->seq_message_id++;
297 
298   return msg;
299 }
300 
301 static void
gtk_statusbar_msg_free(GtkStatusbarMsg * msg)302 gtk_statusbar_msg_free (GtkStatusbarMsg *msg)
303 {
304   g_free (msg->text);
305   g_slice_free (GtkStatusbarMsg, msg);
306 }
307 
308 /**
309  * gtk_statusbar_push:
310  * @statusbar: a `GtkStatusbar`
311  * @context_id: the message’s context id, as returned by
312  *    gtk_statusbar_get_context_id()
313  * @text: the message to add to the statusbar
314  *
315  * Pushes a new message onto a statusbar’s stack.
316  *
317  * Returns: a message id that can be used with
318  *   [method@Gtk.Statusbar.remove].
319  */
320 guint
gtk_statusbar_push(GtkStatusbar * statusbar,guint context_id,const char * text)321 gtk_statusbar_push (GtkStatusbar *statusbar,
322 		    guint	  context_id,
323 		    const char   *text)
324 {
325   GtkStatusbarMsg *msg;
326 
327   g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
328   g_return_val_if_fail (text != NULL, 0);
329 
330   msg = gtk_statusbar_msg_create (statusbar, context_id, text);
331   statusbar->messages = g_slist_prepend (statusbar->messages, msg);
332 
333   g_signal_emit (statusbar,
334 		 statusbar_signals[SIGNAL_TEXT_PUSHED],
335 		 0,
336 		 msg->context_id,
337 		 msg->text);
338 
339   return msg->message_id;
340 }
341 
342 /**
343  * gtk_statusbar_pop:
344  * @statusbar: a `GtkStatusbar`
345  * @context_id: a context identifier
346  *
347  * Removes the first message in the `GtkStatusbar`’s stack
348  * with the given context id.
349  *
350  * Note that this may not change the displayed message,
351  * if the message at the top of the stack has a different
352  * context id.
353  */
354 void
gtk_statusbar_pop(GtkStatusbar * statusbar,guint context_id)355 gtk_statusbar_pop (GtkStatusbar *statusbar,
356 		   guint	 context_id)
357 {
358   GtkStatusbarMsg *msg;
359 
360   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
361 
362   if (statusbar->messages)
363     {
364       GSList *list;
365 
366       for (list = statusbar->messages; list; list = list->next)
367 	{
368 	  msg = list->data;
369 
370 	  if (msg->context_id == context_id)
371 	    {
372 	      statusbar->messages = g_slist_remove_link (statusbar->messages, list);
373 	      gtk_statusbar_msg_free (msg);
374 	      g_slist_free_1 (list);
375 	      break;
376 	    }
377 	}
378     }
379 
380   msg = statusbar->messages ? statusbar->messages->data : NULL;
381 
382   g_signal_emit (statusbar,
383 		 statusbar_signals[SIGNAL_TEXT_POPPED],
384 		 0,
385 		 (guint) (msg ? msg->context_id : 0),
386 		 msg ? msg->text : NULL);
387 }
388 
389 /**
390  * gtk_statusbar_remove:
391  * @statusbar: a `GtkStatusbar`
392  * @context_id: a context identifier
393  * @message_id: a message identifier, as returned by [method@Gtk.Statusbar.push]
394  *
395  * Forces the removal of a message from a statusbar’s stack.
396  * The exact @context_id and @message_id must be specified.
397  */
398 void
gtk_statusbar_remove(GtkStatusbar * statusbar,guint context_id,guint message_id)399 gtk_statusbar_remove (GtkStatusbar *statusbar,
400 		      guint	   context_id,
401 		      guint        message_id)
402 {
403   GtkStatusbarMsg *msg;
404 
405   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
406   g_return_if_fail (message_id > 0);
407 
408   msg = statusbar->messages ? statusbar->messages->data : NULL;
409   if (msg)
410     {
411       GSList *list;
412 
413       /* care about signal emission if the topmost item is removed */
414       if (msg->context_id == context_id &&
415 	  msg->message_id == message_id)
416 	{
417 	  gtk_statusbar_pop (statusbar, context_id);
418 	  return;
419 	}
420 
421       for (list = statusbar->messages; list; list = list->next)
422 	{
423 	  msg = list->data;
424 
425 	  if (msg->context_id == context_id &&
426 	      msg->message_id == message_id)
427 	    {
428 	      statusbar->messages = g_slist_remove_link (statusbar->messages, list);
429 	      gtk_statusbar_msg_free (msg);
430 	      g_slist_free_1 (list);
431 
432 	      break;
433 	    }
434 	}
435     }
436 }
437 
438 /**
439  * gtk_statusbar_remove_all:
440  * @statusbar: a `GtkStatusbar`
441  * @context_id: a context identifier
442  *
443  * Forces the removal of all messages from a statusbar's
444  * stack with the exact @context_id.
445  */
446 void
gtk_statusbar_remove_all(GtkStatusbar * statusbar,guint context_id)447 gtk_statusbar_remove_all (GtkStatusbar *statusbar,
448                           guint         context_id)
449 {
450   GtkStatusbarMsg *msg;
451   GSList *prev, *list;
452 
453   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
454 
455   if (statusbar->messages == NULL)
456     return;
457 
458   /* We special-case the topmost message at the bottom of this
459    * function:
460    * If we need to pop it, we have to update various state and we want
461    * an up-to-date list of remaining messages in that case.
462    */
463   prev = statusbar->messages;
464   list = prev->next;
465 
466   while (list != NULL)
467     {
468       msg = list->data;
469 
470       if (msg->context_id == context_id)
471         {
472           prev->next = list->next;
473 
474           gtk_statusbar_msg_free (msg);
475           g_slist_free_1 (list);
476 
477           list = prev->next;
478         }
479       else
480         {
481           prev = list;
482           list = prev->next;
483         }
484     }
485 
486   /* Treat topmost message here */
487   msg = statusbar->messages->data;
488   if (msg->context_id == context_id)
489     {
490       gtk_statusbar_pop (statusbar, context_id);
491     }
492 }
493 
494 /**
495  * gtk_statusbar_get_message:
496  * @statusbar: a `GtkStatusbar`
497  *
498  * Retrieves the contents of the label in `GtkStatusbar`.
499  *
500  * Returns: (transfer none): the contents of the statusbar
501  */
502 const char *
gtk_statusbar_get_message(GtkStatusbar * statusbar)503 gtk_statusbar_get_message (GtkStatusbar *statusbar)
504 {
505   g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), NULL);
506 
507   return gtk_label_get_label (GTK_LABEL (statusbar->label));
508 }
509