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
30 #include "gtkframe.h"
31 #include "gtklabel.h"
32 #include "gtkmarshalers.h"
33 #include "gtkwindow.h"
34 #include "gtkprivate.h"
35 #include "gtkintl.h"
36 #include "gtkorientable.h"
37 #include "gtktypebuiltins.h"
38 #include "a11y/gtkstatusbaraccessible.h"
39
40 /**
41 * SECTION:gtkstatusbar
42 * @title: GtkStatusbar
43 * @short_description: Report messages of minor importance to the user
44 *
45 * A #GtkStatusbar is usually placed along the bottom of an application's
46 * main #GtkWindow. It may provide a regular commentary of the application's
47 * status (as is usually the case in a web browser, for example), or may be
48 * used to simply output a message when the status changes, (when an upload
49 * is complete in an FTP client, for example).
50 *
51 * Status bars in GTK+ maintain a stack of messages. The message at
52 * the top of the each bar’s stack is the one that will currently be displayed.
53 *
54 * Any messages added to a statusbar’s stack must specify a
55 * context id that is used to uniquely identify
56 * the source of a message. This context id can be generated by
57 * gtk_statusbar_get_context_id(), given a message and the statusbar that
58 * it will be added to. Note that messages are stored in a stack, and when
59 * choosing which message to display, the stack structure is adhered to,
60 * regardless of the context identifier of a message.
61 *
62 * One could say that a statusbar maintains one stack of messages for
63 * display purposes, but allows multiple message producers to maintain
64 * sub-stacks of the messages they produced (via context ids).
65 *
66 * Status bars are created using gtk_statusbar_new().
67 *
68 * Messages are added to the bar’s stack with gtk_statusbar_push().
69 *
70 * The message at the top of the stack can be removed using
71 * gtk_statusbar_pop(). A message can be removed from anywhere in the
72 * stack if its message id was recorded at the time it was added. This
73 * is done using gtk_statusbar_remove().
74 *
75 * # CSS node
76 *
77 * GtkStatusbar has a single CSS node with name statusbar.
78 */
79
80 typedef struct _GtkStatusbarMsg GtkStatusbarMsg;
81
82 struct _GtkStatusbarPrivate
83 {
84 GtkWidget *frame;
85 GtkWidget *label;
86 GtkWidget *message_area;
87
88 GSList *messages;
89 GSList *keys;
90
91 guint seq_context_id;
92 guint seq_message_id;
93 };
94
95
96 struct _GtkStatusbarMsg
97 {
98 gchar *text;
99 guint context_id;
100 guint message_id;
101 };
102
103 enum
104 {
105 SIGNAL_TEXT_PUSHED,
106 SIGNAL_TEXT_POPPED,
107 SIGNAL_LAST
108 };
109
110 static void gtk_statusbar_update (GtkStatusbar *statusbar,
111 guint context_id,
112 const gchar *text);
113 static void gtk_statusbar_destroy (GtkWidget *widget);
114
115 static guint statusbar_signals[SIGNAL_LAST] = { 0 };
116
G_DEFINE_TYPE_WITH_PRIVATE(GtkStatusbar,gtk_statusbar,GTK_TYPE_BOX)117 G_DEFINE_TYPE_WITH_PRIVATE (GtkStatusbar, gtk_statusbar, GTK_TYPE_BOX)
118
119 static void
120 gtk_statusbar_class_init (GtkStatusbarClass *class)
121 {
122 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
123
124 widget_class->destroy = gtk_statusbar_destroy;
125
126 class->text_pushed = gtk_statusbar_update;
127 class->text_popped = gtk_statusbar_update;
128
129 /**
130 * GtkStatusbar::text-pushed:
131 * @statusbar: the object which received the signal
132 * @context_id: the context id of the relevant message/statusbar
133 * @text: the message that was pushed
134 *
135 * Is emitted whenever a new message gets pushed onto a statusbar's stack.
136 */
137 statusbar_signals[SIGNAL_TEXT_PUSHED] =
138 g_signal_new (I_("text-pushed"),
139 G_OBJECT_CLASS_TYPE (class),
140 G_SIGNAL_RUN_LAST,
141 G_STRUCT_OFFSET (GtkStatusbarClass, text_pushed),
142 NULL, NULL,
143 _gtk_marshal_VOID__UINT_STRING,
144 G_TYPE_NONE, 2,
145 G_TYPE_UINT,
146 G_TYPE_STRING);
147
148 /**
149 * GtkStatusbar::text-popped:
150 * @statusbar: the object which received the signal
151 * @context_id: the context id of the relevant message/statusbar
152 * @text: the message that was just popped
153 *
154 * Is emitted whenever a new message is popped off a statusbar's stack.
155 */
156 statusbar_signals[SIGNAL_TEXT_POPPED] =
157 g_signal_new (I_("text-popped"),
158 G_OBJECT_CLASS_TYPE (class),
159 G_SIGNAL_RUN_LAST,
160 G_STRUCT_OFFSET (GtkStatusbarClass, text_popped),
161 NULL, NULL,
162 _gtk_marshal_VOID__UINT_STRING,
163 G_TYPE_NONE, 2,
164 G_TYPE_UINT,
165 G_TYPE_STRING);
166
167 /**
168 * GtkStatusbar:shadow-type:
169 *
170 * The style of the bevel around the statusbar text.
171 *
172 * Deprecated: 3.20: Use CSS properties to determine the appearance,
173 * the value of this style property is ignored.
174 */
175 gtk_widget_class_install_style_property (widget_class,
176 g_param_spec_enum ("shadow-type",
177 P_("Shadow type"),
178 P_("Style of bevel around the statusbar text"),
179 GTK_TYPE_SHADOW_TYPE,
180 GTK_SHADOW_IN,
181 GTK_PARAM_READABLE|G_PARAM_DEPRECATED));
182
183 /* Bind class to template
184 */
185 gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkstatusbar.ui");
186 gtk_widget_class_bind_template_child_internal_private (widget_class, GtkStatusbar, message_area);
187 gtk_widget_class_bind_template_child_private (widget_class, GtkStatusbar, frame);
188 gtk_widget_class_bind_template_child_private (widget_class, GtkStatusbar, label);
189
190 gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_STATUSBAR_ACCESSIBLE);
191 gtk_widget_class_set_css_name (widget_class, "statusbar");
192 }
193
194 static void
gtk_statusbar_init(GtkStatusbar * statusbar)195 gtk_statusbar_init (GtkStatusbar *statusbar)
196 {
197 GtkStatusbarPrivate *priv;
198
199 statusbar->priv = gtk_statusbar_get_instance_private (statusbar);
200 priv = statusbar->priv;
201
202 priv->seq_context_id = 1;
203 priv->seq_message_id = 1;
204 priv->messages = NULL;
205 priv->keys = NULL;
206
207 gtk_widget_init_template (GTK_WIDGET (statusbar));
208 }
209
210 /**
211 * gtk_statusbar_new:
212 *
213 * Creates a new #GtkStatusbar ready for messages.
214 *
215 * Returns: the new #GtkStatusbar
216 */
217 GtkWidget*
gtk_statusbar_new(void)218 gtk_statusbar_new (void)
219 {
220 return g_object_new (GTK_TYPE_STATUSBAR, NULL);
221 }
222
223 static void
gtk_statusbar_update(GtkStatusbar * statusbar,guint context_id,const gchar * text)224 gtk_statusbar_update (GtkStatusbar *statusbar,
225 guint context_id,
226 const gchar *text)
227 {
228 GtkStatusbarPrivate *priv;
229
230 g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
231
232 priv = statusbar->priv;
233
234 if (!text)
235 text = "";
236
237 gtk_label_set_text (GTK_LABEL (priv->label), text);
238 }
239
240 /**
241 * gtk_statusbar_get_context_id:
242 * @statusbar: a #GtkStatusbar
243 * @context_description: textual description of what context
244 * the new message is being used in
245 *
246 * Returns a new context identifier, given a description
247 * of the actual context. Note that the description is
248 * not shown in the UI.
249 *
250 * Returns: an integer id
251 */
252 guint
gtk_statusbar_get_context_id(GtkStatusbar * statusbar,const gchar * context_description)253 gtk_statusbar_get_context_id (GtkStatusbar *statusbar,
254 const gchar *context_description)
255 {
256 GtkStatusbarPrivate *priv;
257 gchar *string;
258 guint id;
259
260 g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
261 g_return_val_if_fail (context_description != NULL, 0);
262
263 priv = statusbar->priv;
264
265 /* we need to preserve namespaces on object datas */
266 string = g_strconcat ("gtk-status-bar-context:", context_description, NULL);
267
268 id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (statusbar), string));
269 if (id == 0)
270 {
271 id = priv->seq_context_id++;
272 g_object_set_data_full (G_OBJECT (statusbar), string, GUINT_TO_POINTER (id), NULL);
273 priv->keys = g_slist_prepend (priv->keys, string);
274 }
275 else
276 g_free (string);
277
278 return id;
279 }
280
281 static GtkStatusbarMsg *
gtk_statusbar_msg_create(GtkStatusbar * statusbar,guint context_id,const gchar * text)282 gtk_statusbar_msg_create (GtkStatusbar *statusbar,
283 guint context_id,
284 const gchar *text)
285 {
286 GtkStatusbarMsg *msg;
287
288 msg = g_slice_new (GtkStatusbarMsg);
289 msg->text = g_strdup (text);
290 msg->context_id = context_id;
291 msg->message_id = statusbar->priv->seq_message_id++;
292
293 return msg;
294 }
295
296 static void
gtk_statusbar_msg_free(GtkStatusbarMsg * msg)297 gtk_statusbar_msg_free (GtkStatusbarMsg *msg)
298 {
299 g_free (msg->text);
300 g_slice_free (GtkStatusbarMsg, msg);
301 }
302
303 /**
304 * gtk_statusbar_push:
305 * @statusbar: a #GtkStatusbar
306 * @context_id: the message’s context id, as returned by
307 * gtk_statusbar_get_context_id()
308 * @text: the message to add to the statusbar
309 *
310 * Pushes a new message onto a statusbar’s stack.
311 *
312 * Returns: a message id that can be used with
313 * gtk_statusbar_remove().
314 */
315 guint
gtk_statusbar_push(GtkStatusbar * statusbar,guint context_id,const gchar * text)316 gtk_statusbar_push (GtkStatusbar *statusbar,
317 guint context_id,
318 const gchar *text)
319 {
320 GtkStatusbarPrivate *priv;
321 GtkStatusbarMsg *msg;
322
323 g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
324 g_return_val_if_fail (text != NULL, 0);
325
326 priv = statusbar->priv;
327
328 msg = gtk_statusbar_msg_create (statusbar, context_id, text);
329 priv->messages = g_slist_prepend (priv->messages, msg);
330
331 g_signal_emit (statusbar,
332 statusbar_signals[SIGNAL_TEXT_PUSHED],
333 0,
334 msg->context_id,
335 msg->text);
336
337 return msg->message_id;
338 }
339
340 /**
341 * gtk_statusbar_pop:
342 * @statusbar: a #GtkStatusbar
343 * @context_id: a context identifier
344 *
345 * Removes the first message in the #GtkStatusbar’s stack
346 * with the given context id.
347 *
348 * Note that this may not change the displayed message, if
349 * the message at the top of the stack has a different
350 * context id.
351 */
352 void
gtk_statusbar_pop(GtkStatusbar * statusbar,guint context_id)353 gtk_statusbar_pop (GtkStatusbar *statusbar,
354 guint context_id)
355 {
356 GtkStatusbarPrivate *priv;
357 GtkStatusbarMsg *msg;
358
359 g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
360
361 priv = statusbar->priv;
362
363 if (priv->messages)
364 {
365 GSList *list;
366
367 for (list = priv->messages; list; list = list->next)
368 {
369 msg = list->data;
370
371 if (msg->context_id == context_id)
372 {
373 priv->messages = g_slist_remove_link (priv->messages, list);
374 gtk_statusbar_msg_free (msg);
375 g_slist_free_1 (list);
376 break;
377 }
378 }
379 }
380
381 msg = priv->messages ? priv->messages->data : NULL;
382
383 g_signal_emit (statusbar,
384 statusbar_signals[SIGNAL_TEXT_POPPED],
385 0,
386 (guint) (msg ? msg->context_id : 0),
387 msg ? msg->text : NULL);
388 }
389
390 /**
391 * gtk_statusbar_remove:
392 * @statusbar: a #GtkStatusbar
393 * @context_id: a context identifier
394 * @message_id: a message identifier, as returned by gtk_statusbar_push()
395 *
396 * Forces the removal of a message from a statusbar’s stack.
397 * The exact @context_id and @message_id must be specified.
398 */
399 void
gtk_statusbar_remove(GtkStatusbar * statusbar,guint context_id,guint message_id)400 gtk_statusbar_remove (GtkStatusbar *statusbar,
401 guint context_id,
402 guint message_id)
403 {
404 GtkStatusbarPrivate *priv;
405 GtkStatusbarMsg *msg;
406
407 g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
408 g_return_if_fail (message_id > 0);
409
410 priv = statusbar->priv;
411
412 msg = priv->messages ? priv->messages->data : NULL;
413 if (msg)
414 {
415 GSList *list;
416
417 /* care about signal emission if the topmost item is removed */
418 if (msg->context_id == context_id &&
419 msg->message_id == message_id)
420 {
421 gtk_statusbar_pop (statusbar, context_id);
422 return;
423 }
424
425 for (list = priv->messages; list; list = list->next)
426 {
427 msg = list->data;
428
429 if (msg->context_id == context_id &&
430 msg->message_id == message_id)
431 {
432 priv->messages = g_slist_remove_link (priv->messages, list);
433 gtk_statusbar_msg_free (msg);
434 g_slist_free_1 (list);
435
436 break;
437 }
438 }
439 }
440 }
441
442 /**
443 * gtk_statusbar_remove_all:
444 * @statusbar: a #GtkStatusbar
445 * @context_id: a context identifier
446 *
447 * Forces the removal of all messages from a statusbar's
448 * stack with the exact @context_id.
449 *
450 * Since: 2.22
451 */
452 void
gtk_statusbar_remove_all(GtkStatusbar * statusbar,guint context_id)453 gtk_statusbar_remove_all (GtkStatusbar *statusbar,
454 guint context_id)
455 {
456 GtkStatusbarPrivate *priv;
457 GtkStatusbarMsg *msg;
458 GSList *prev, *list;
459
460 g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
461
462 priv = statusbar->priv;
463
464 if (priv->messages == NULL)
465 return;
466
467 /* We special-case the topmost message at the bottom of this
468 * function:
469 * If we need to pop it, we have to update various state and we want
470 * an up-to-date list of remaining messages in that case.
471 */
472 prev = priv->messages;
473 list = prev->next;
474
475 while (list != NULL)
476 {
477 msg = list->data;
478
479 if (msg->context_id == context_id)
480 {
481 prev->next = list->next;
482
483 gtk_statusbar_msg_free (msg);
484 g_slist_free_1 (list);
485
486 list = prev->next;
487 }
488 else
489 {
490 prev = list;
491 list = prev->next;
492 }
493 }
494
495 /* Treat topmost message here */
496 msg = priv->messages->data;
497 if (msg->context_id == context_id)
498 {
499 gtk_statusbar_pop (statusbar, context_id);
500 }
501 }
502
503 /**
504 * gtk_statusbar_get_message_area:
505 * @statusbar: a #GtkStatusbar
506 *
507 * Retrieves the box containing the label widget.
508 *
509 * Returns: (type Gtk.Box) (transfer none): a #GtkBox
510 *
511 * Since: 2.20
512 */
513 GtkWidget*
gtk_statusbar_get_message_area(GtkStatusbar * statusbar)514 gtk_statusbar_get_message_area (GtkStatusbar *statusbar)
515 {
516 GtkStatusbarPrivate *priv;
517
518 g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), NULL);
519
520 priv = statusbar->priv;
521
522 return priv->message_area;
523 }
524
525 static void
gtk_statusbar_destroy(GtkWidget * widget)526 gtk_statusbar_destroy (GtkWidget *widget)
527 {
528 GtkStatusbar *statusbar = GTK_STATUSBAR (widget);
529 GtkStatusbarPrivate *priv = statusbar->priv;
530
531 g_slist_free_full (priv->messages, (GDestroyNotify) gtk_statusbar_msg_free);
532 priv->messages = NULL;
533
534 g_slist_free_full (priv->keys, g_free);
535 priv->keys = NULL;
536
537 GTK_WIDGET_CLASS (gtk_statusbar_parent_class)->destroy (widget);
538 }
539