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
81typedef struct _GtkStatusbarMsg GtkStatusbarMsg;
82
83typedef struct _GtkStatusbarClass GtkStatusbarClass;
84
85struct _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
99struct _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
111struct _GtkStatusbarMsg
112{
113 char *text;
114 guint context_id;
115 guint message_id;
116};
117
118enum
119{
120 SIGNAL_TEXT_PUSHED,
121 SIGNAL_TEXT_POPPED,
122 SIGNAL_LAST
123};
124
125static void gtk_statusbar_update (GtkStatusbar *statusbar,
126 guint context_id,
127 const char *text);
128
129static void gtk_statusbar_msg_free (GtkStatusbarMsg *msg);
130
131static guint statusbar_signals[SIGNAL_LAST] = { 0 };
132
133G_DEFINE_TYPE (GtkStatusbar, gtk_statusbar, GTK_TYPE_WIDGET)
134
135static void
136gtk_statusbar_dispose (GObject *object)
137{
138 GtkStatusbar *self = GTK_STATUSBAR (object);
139
140 g_slist_free_full (list: self->messages, free_func: (GDestroyNotify) gtk_statusbar_msg_free);
141 self->messages = NULL;
142
143 g_slist_free_full (list: self->keys, free_func: 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
151static void
152gtk_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 signal_flags: G_SIGNAL_RUN_LAST,
174 G_STRUCT_OFFSET (GtkStatusbarClass, text_pushed),
175 NULL, NULL,
176 c_marshaller: _gtk_marshal_VOID__UINT_STRING,
177 G_TYPE_NONE, n_params: 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 signal_flags: G_SIGNAL_RUN_LAST,
193 G_STRUCT_OFFSET (GtkStatusbarClass, text_popped),
194 NULL, NULL,
195 c_marshaller: _gtk_marshal_VOID__UINT_STRING,
196 G_TYPE_NONE, n_params: 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, resource_name: "/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
210static void
211gtk_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 */
228GtkWidget*
229gtk_statusbar_new (void)
230{
231 return g_object_new (GTK_TYPE_STATUSBAR, NULL);
232}
233
234static void
235gtk_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), str: 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 */
260guint
261gtk_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 (string1: "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), key: string, GUINT_TO_POINTER (id), NULL);
278 statusbar->keys = g_slist_prepend (list: statusbar->keys, data: string);
279 }
280 else
281 g_free (mem: string);
282
283 return id;
284}
285
286static GtkStatusbarMsg *
287gtk_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 (str: text);
295 msg->context_id = context_id;
296 msg->message_id = statusbar->seq_message_id++;
297
298 return msg;
299}
300
301static void
302gtk_statusbar_msg_free (GtkStatusbarMsg *msg)
303{
304 g_free (mem: 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 */
320guint
321gtk_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 (list: statusbar->messages, data: msg);
332
333 g_signal_emit (instance: statusbar,
334 signal_id: statusbar_signals[SIGNAL_TEXT_PUSHED],
335 detail: 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 */
354void
355gtk_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 (list: statusbar->messages, link_: 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 (instance: statusbar,
383 signal_id: statusbar_signals[SIGNAL_TEXT_POPPED],
384 detail: 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 */
398void
399gtk_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 (list: statusbar->messages, link_: 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 */
446void
447gtk_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 */
502const char *
503gtk_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

source code of gtk/gtk/gtkstatusbar.c