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