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 | |
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 (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 | |
151 | static void |
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 | 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 | |
210 | static void |
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* |
229 | gtk_statusbar_new (void) |
230 | { |
231 | return g_object_new (GTK_TYPE_STATUSBAR, NULL); |
232 | } |
233 | |
234 | static void |
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), 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 | */ |
260 | guint |
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 (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 | |
286 | static GtkStatusbarMsg * |
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 (str: text); |
295 | msg->context_id = context_id; |
296 | msg->message_id = statusbar->seq_message_id++; |
297 | |
298 | return msg; |
299 | } |
300 | |
301 | static void |
302 | gtk_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 | */ |
320 | guint |
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 (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 | */ |
354 | void |
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 (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 | */ |
398 | void |
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 (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 | */ |
446 | void |
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 * |
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 | |