gtkstatusbar.c 13.4 KB
Newer Older
Cody Russell's avatar
Cody Russell committed
1
/* GTK - The GIMP Toolkit
Shawn Amundson's avatar
Shawn Amundson committed
2
3
4
5
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 * GtkStatusbar Copyright (C) 1998 Shawn T. Amundson
 *
 * This library is free software; you can redistribute it and/or
6
 * modify it under the terms of the GNU Lesser General Public
Shawn Amundson's avatar
Shawn Amundson committed
7
8
9
10
11
12
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13
 * Lesser General Public License for more details.
Shawn Amundson's avatar
Shawn Amundson committed
14
 *
15
 * You should have received a copy of the GNU Lesser General Public
Javier Jardón's avatar
Javier Jardón committed
16
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
Shawn Amundson's avatar
Shawn Amundson committed
17
18
 */

19
/*
20
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
21
22
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * 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

Timm Bäder's avatar
Timm Bäder committed
31
#include "gtkbinlayout.h"
Shawn Amundson's avatar
Shawn Amundson committed
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"
Shawn Amundson's avatar
Shawn Amundson committed
39

40
/**
Emmanuele Bassi's avatar
Emmanuele Bassi committed
41
 * GtkStatusbar:
42
 *
43
44
45
 * A `GtkStatusbar` widget is usually placed along the bottom of an application's
 * main [class@Gtk.Window].
 *
Matthias Clasen's avatar
Matthias Clasen committed
46
47
 * ![An example GtkStatusbar](statusbar.png)
 *
48
 * A `GtkStatusBar` may provide a regular commentary of the application's
49
50
51
52
 * status (as is usually the case in a web browser, for example), or may be
 * used to simply output a message when the status changes, (when an upload
 * is complete in an FTP client, for example).
 *
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
 *
Matthias Clasen's avatar
Matthias Clasen committed
56
57
58
59
60
61
 * Any messages added to a statusbar’s stack must specify a context id that
 * is used to uniquely identify the source of a message. This context id can
 * be generated by [method@Gtk.Statusbar.get_context_id], given a message and
 * the statusbar that it will be added to. Note that messages are stored in a
 * stack, and when choosing which message to display, the stack structure is
 * adhered to, regardless of the context identifier of a message.
62
63
64
65
66
 *
 * One could say that a statusbar maintains one stack of messages for
 * display purposes, but allows multiple message producers to maintain
 * sub-stacks of the messages they produced (via context ids).
 *
Matthias Clasen's avatar
Matthias Clasen committed
67
 * Status bars are created using [ctor@Gtk.Statusbar.new].
68
 *
Matthias Clasen's avatar
Matthias Clasen committed
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
Matthias Clasen's avatar
Matthias Clasen committed
72
73
74
 * [method@Gtk.Statusbar.pop]. A message can be removed from anywhere in the
 * stack if its message id was recorded at the time it was added. This is done
 * using [method@Gtk.Statusbar.remove].
75
 *
76
 * ## CSS node
77
 *
78
 * `GtkStatusbar` has a single CSS node with name `statusbar`.
79
 */
80

81
82
typedef struct _GtkStatusbarMsg GtkStatusbarMsg;

Matthias Clasen's avatar
Matthias Clasen committed
83
84
85
86
87
typedef struct _GtkStatusbarClass         GtkStatusbarClass;

struct _GtkStatusbar
{
  GtkWidget parent_instance;
88
89
90
91
92
93
94
95
96

  GtkWidget     *label;
  GtkWidget     *message_area;

  GSList        *messages;
  GSList        *keys;

  guint          seq_context_id;
  guint          seq_message_id;
Matthias Clasen's avatar
Matthias Clasen committed
97
98
99
100
101
102
103
104
};

struct _GtkStatusbarClass
{
  GtkWidgetClass parent_class;

  void  (*text_pushed)  (GtkStatusbar   *statusbar,
                         guint           context_id,
Benjamin Otte's avatar
Benjamin Otte committed
105
                         const char     *text);
Matthias Clasen's avatar
Matthias Clasen committed
106
107
  void  (*text_popped)  (GtkStatusbar   *statusbar,
                         guint           context_id,
Benjamin Otte's avatar
Benjamin Otte committed
108
                         const char     *text);
Matthias Clasen's avatar
Matthias Clasen committed
109
110
};

111
112
struct _GtkStatusbarMsg
{
Benjamin Otte's avatar
Benjamin Otte committed
113
  char *text;
114
115
116
  guint context_id;
  guint message_id;
};
117
118
119
120
121
122
123
124

enum
{
  SIGNAL_TEXT_PUSHED,
  SIGNAL_TEXT_POPPED,
  SIGNAL_LAST
};

125
126
static void     gtk_statusbar_update            (GtkStatusbar      *statusbar,
						 guint              context_id,
Benjamin Otte's avatar
Benjamin Otte committed
127
						 const char        *text);
128
129

static void     gtk_statusbar_msg_free          (GtkStatusbarMsg *msg);
Owen Taylor's avatar
Owen Taylor committed
130

131
static guint              statusbar_signals[SIGNAL_LAST] = { 0 };
Shawn Amundson's avatar
Shawn Amundson committed
132

133
G_DEFINE_TYPE (GtkStatusbar, gtk_statusbar, GTK_TYPE_WIDGET)
134
135
136
137

static void
gtk_statusbar_dispose (GObject *object)
{
138
  GtkStatusbar *self = GTK_STATUSBAR (object);
139

140
141
142
143
144
145
  g_slist_free_full (self->messages, (GDestroyNotify) gtk_statusbar_msg_free);
  self->messages = NULL;

  g_slist_free_full (self->keys, g_free);
  self->keys = NULL;

146
  gtk_widget_dispose_template (GTK_WIDGET (self), GTK_TYPE_STATUSBAR);
147
148
149
150

  G_OBJECT_CLASS (gtk_statusbar_parent_class)->dispose (object);
}

Shawn Amundson's avatar
Shawn Amundson committed
151
152
153
static void
gtk_statusbar_class_init (GtkStatusbarClass *class)
{
154
  GObjectClass *object_class = G_OBJECT_CLASS (class);
155
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
Shawn Amundson's avatar
Shawn Amundson committed
156

157
158
  object_class->dispose = gtk_statusbar_dispose;

159
160
  class->text_pushed = gtk_statusbar_update;
  class->text_popped = gtk_statusbar_update;
161

Matthias Clasen's avatar
Matthias Clasen committed
162
  /**
163
   * GtkStatusbar::text-pushed:
Matthias Clasen's avatar
Matthias Clasen committed
164
165
166
167
   * @statusbar: the object which received the signal
   * @context_id: the context id of the relevant message/statusbar
   * @text: the message that was pushed
   *
Matthias Clasen's avatar
Matthias Clasen committed
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"),
Manish Singh's avatar
Manish Singh committed
172
173
174
175
176
177
178
179
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkStatusbarClass, text_pushed),
		  NULL, NULL,
		  _gtk_marshal_VOID__UINT_STRING,
		  G_TYPE_NONE, 2,
		  G_TYPE_UINT,
		  G_TYPE_STRING);
180
181
182

  /**
   * GtkStatusbar::text-popped:
Matthias Clasen's avatar
Matthias Clasen committed
183
184
185
   * @statusbar: the object which received the signal
   * @context_id: the context id of the relevant message/statusbar
   * @text: the message that was just popped
186
   *
Matthias Clasen's avatar
Matthias Clasen committed
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"),
Manish Singh's avatar
Manish Singh committed
191
192
193
194
195
196
197
198
		  G_OBJECT_CLASS_TYPE (class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GtkStatusbarClass, text_popped),
		  NULL, NULL,
		  _gtk_marshal_VOID__UINT_STRING,
		  G_TYPE_NONE, 2,
		  G_TYPE_UINT,
		  G_TYPE_STRING);
199

200
201
  /* Bind class to template
   */
202
  gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkstatusbar.ui");
203
204
  gtk_widget_class_bind_template_child_internal (widget_class, GtkStatusbar, message_area);
  gtk_widget_class_bind_template_child (widget_class, GtkStatusbar, label);
205

Timm Bäder's avatar
Timm Bäder committed
206
  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
Matthias Clasen's avatar
Matthias Clasen committed
207
  gtk_widget_class_set_css_name (widget_class, I_("statusbar"));
Shawn Amundson's avatar
Shawn Amundson committed
208
209
210
211
212
}

static void
gtk_statusbar_init (GtkStatusbar *statusbar)
{
213
214
215
216
  statusbar->seq_context_id = 1;
  statusbar->seq_message_id = 1;
  statusbar->messages = NULL;
  statusbar->keys = NULL;
217

218
  gtk_widget_init_template (GTK_WIDGET (statusbar));
219
220
}

221
222
223
/**
 * gtk_statusbar_new:
 *
Matthias Clasen's avatar
Matthias Clasen committed
224
 * Creates a new `GtkStatusbar` ready for messages.
225
 *
Matthias Clasen's avatar
Matthias Clasen committed
226
 * Returns: the new `GtkStatusbar`
227
 */
228
GtkWidget*
229
gtk_statusbar_new (void)
Shawn Amundson's avatar
Shawn Amundson committed
230
{
Manish Singh's avatar
Manish Singh committed
231
  return g_object_new (GTK_TYPE_STATUSBAR, NULL);
232
233
234
235
}

static void
gtk_statusbar_update (GtkStatusbar *statusbar,
236
		      guint	    context_id,
Benjamin Otte's avatar
Benjamin Otte committed
237
		      const char   *text)
238
239
{
  g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
Shawn Amundson's avatar
Shawn Amundson committed
240

241
242
  if (!text)
    text = "";
Shawn Amundson's avatar
Shawn Amundson committed
243

244
  gtk_label_set_text (GTK_LABEL (statusbar->label), text);
Shawn Amundson's avatar
Shawn Amundson committed
245
246
}

247
248
/**
 * gtk_statusbar_get_context_id:
Matthias Clasen's avatar
Matthias Clasen committed
249
250
251
252
253
254
 * @statusbar: a `GtkStatusbar`
 * @context_description: textual description of what context
 *   the new message is being used in
 *
 * Returns a new context identifier, given a description
 * of the actual context.
255
 *
Matthias Clasen's avatar
Matthias Clasen committed
256
 * Note that the description is not shown in the UI.
257
258
259
 *
 * Returns: an integer id
 */
260
261
guint
gtk_statusbar_get_context_id (GtkStatusbar *statusbar,
Benjamin Otte's avatar
Benjamin Otte committed
262
			      const char   *context_description)
263
{
Benjamin Otte's avatar
Benjamin Otte committed
264
  char *string;
Yevgen Muntyan's avatar
Yevgen Muntyan committed
265
  guint id;
266

267
268
269
  g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
  g_return_val_if_fail (context_description != NULL, 0);

270
  /* we need to preserve namespaces on object data */
271
272
  string = g_strconcat ("gtk-status-bar-context:", context_description, NULL);

273
274
  id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (statusbar), string));
  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
281
282
    }
  else
    g_free (string);

283
  return id;
284
285
}

Paolo Borelli's avatar
Paolo Borelli committed
286
287
288
static GtkStatusbarMsg *
gtk_statusbar_msg_create (GtkStatusbar *statusbar,
		          guint         context_id,
Benjamin Otte's avatar
Benjamin Otte committed
289
		          const char   *text)
Paolo Borelli's avatar
Paolo Borelli committed
290
291
292
293
294
295
{
  GtkStatusbarMsg *msg;

  msg = g_slice_new (GtkStatusbarMsg);
  msg->text = g_strdup (text);
  msg->context_id = context_id;
296
  msg->message_id = statusbar->seq_message_id++;
Paolo Borelli's avatar
Paolo Borelli committed
297
298
299
300
301
302
303
304
305
306
307

  return msg;
}

static void
gtk_statusbar_msg_free (GtkStatusbarMsg *msg)
{
  g_free (msg->text);
  g_slice_free (GtkStatusbarMsg, msg);
}

308
309
/**
 * gtk_statusbar_push:
Matthias Clasen's avatar
Matthias Clasen committed
310
 * @statusbar: a `GtkStatusbar`
311
 * @context_id: the message’s context id, as returned by
Matthias Clasen's avatar
Matthias Clasen committed
312
 *    gtk_statusbar_get_context_id()
313
 * @text: the message to add to the statusbar
Matthias Clasen's avatar
Matthias Clasen committed
314
 *
315
 * Pushes a new message onto a statusbar’s stack.
316
 *
Matthias Clasen's avatar
Matthias Clasen committed
317
318
 * Returns: a message id that can be used with
 *   [method@Gtk.Statusbar.remove].
319
 */
320
321
guint
gtk_statusbar_push (GtkStatusbar *statusbar,
322
		    guint	  context_id,
Benjamin Otte's avatar
Benjamin Otte committed
323
		    const char   *text)
Shawn Amundson's avatar
Shawn Amundson committed
324
325
{
  GtkStatusbarMsg *msg;
326
327
328

  g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
  g_return_val_if_fail (text != NULL, 0);
Shawn Amundson's avatar
Shawn Amundson committed
329

Paolo Borelli's avatar
Paolo Borelli committed
330
  msg = gtk_statusbar_msg_create (statusbar, context_id, text);
331
  statusbar->messages = g_slist_prepend (statusbar->messages, msg);
Shawn Amundson's avatar
Shawn Amundson committed
332

Manish Singh's avatar
Manish Singh committed
333
334
335
336
337
  g_signal_emit (statusbar,
		 statusbar_signals[SIGNAL_TEXT_PUSHED],
		 0,
		 msg->context_id,
		 msg->text);
Shawn Amundson's avatar
Shawn Amundson committed
338

339
  return msg->message_id;
Shawn Amundson's avatar
Shawn Amundson committed
340
341
}

342
343
/**
 * gtk_statusbar_pop:
Matthias Clasen's avatar
Matthias Clasen committed
344
 * @statusbar: a `GtkStatusbar`
345
346
 * @context_id: a context identifier
 *
Matthias Clasen's avatar
Matthias Clasen committed
347
348
349
350
351
 * Removes the first message in the `GtkStatusbar`’s stack
 * with the given context id.
 *
 * Note that this may not change the displayed message,
 * if the message at the top of the stack has a different
352
353
 * context id.
 */
354
void
355
356
gtk_statusbar_pop (GtkStatusbar *statusbar,
		   guint	 context_id)
Shawn Amundson's avatar
Shawn Amundson committed
357
{
358
  GtkStatusbarMsg *msg;
Shawn Amundson's avatar
Shawn Amundson committed
359

360
  g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
Shawn Amundson's avatar
Shawn Amundson committed
361

362
  if (statusbar->messages)
363
    {
364
      GSList *list;
Owen Taylor's avatar
Owen Taylor committed
365

366
      for (list = statusbar->messages; list; list = list->next)
Owen Taylor's avatar
Owen Taylor committed
367
368
369
370
371
	{
	  msg = list->data;

	  if (msg->context_id == context_id)
	    {
372
	      statusbar->messages = g_slist_remove_link (statusbar->messages, list);
Paolo Borelli's avatar
Paolo Borelli committed
373
	      gtk_statusbar_msg_free (msg);
Owen Taylor's avatar
Owen Taylor committed
374
375
376
377
	      g_slist_free_1 (list);
	      break;
	    }
	}
378
379
    }

380
  msg = statusbar->messages ? statusbar->messages->data : NULL;
Shawn Amundson's avatar
Shawn Amundson committed
381

Manish Singh's avatar
Manish Singh committed
382
383
384
385
386
  g_signal_emit (statusbar,
		 statusbar_signals[SIGNAL_TEXT_POPPED],
		 0,
		 (guint) (msg ? msg->context_id : 0),
		 msg ? msg->text : NULL);
Shawn Amundson's avatar
Shawn Amundson committed
387
388
}

389
390
/**
 * gtk_statusbar_remove:
Matthias Clasen's avatar
Matthias Clasen committed
391
 * @statusbar: a `GtkStatusbar`
392
 * @context_id: a context identifier
Matthias Clasen's avatar
Matthias Clasen committed
393
 * @message_id: a message identifier, as returned by [method@Gtk.Statusbar.push]
394
 *
Matthias Clasen's avatar
Matthias Clasen committed
395
 * Forces the removal of a message from a statusbar’s stack.
396
397
 * The exact @context_id and @message_id must be specified.
 */
Shawn Amundson's avatar
Shawn Amundson committed
398
void
399
400
401
gtk_statusbar_remove (GtkStatusbar *statusbar,
		      guint	   context_id,
		      guint        message_id)
Shawn Amundson's avatar
Shawn Amundson committed
402
{
403
  GtkStatusbarMsg *msg;
Shawn Amundson's avatar
Shawn Amundson committed
404

405
  g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
406
  g_return_if_fail (message_id > 0);
Shawn Amundson's avatar
Shawn Amundson committed
407

408
  msg = statusbar->messages ? statusbar->messages->data : NULL;
409
410
  if (msg)
    {
411
412
413
414
415
      GSList *list;

      /* care about signal emission if the topmost item is removed */
      if (msg->context_id == context_id &&
	  msg->message_id == message_id)
416
	{
417
	  gtk_statusbar_pop (statusbar, context_id);
418
419
	  return;
	}
420

421
      for (list = statusbar->messages; list; list = list->next)
422
423
	{
	  msg = list->data;
424

425
426
	  if (msg->context_id == context_id &&
	      msg->message_id == message_id)
427
	    {
428
	      statusbar->messages = g_slist_remove_link (statusbar->messages, list);
Paolo Borelli's avatar
Paolo Borelli committed
429
	      gtk_statusbar_msg_free (msg);
430
	      g_slist_free_1 (list);
431

432
433
434
	      break;
	    }
	}
Shawn Amundson's avatar
Shawn Amundson committed
435
436
437
    }
}

438
439
/**
 * gtk_statusbar_remove_all:
Matthias Clasen's avatar
Matthias Clasen committed
440
 * @statusbar: a `GtkStatusbar`
441
442
443
444
445
446
447
448
449
450
451
452
453
454
 * @context_id: a context identifier
 *
 * Forces the removal of all messages from a statusbar's
 * stack with the exact @context_id.
 */
void
gtk_statusbar_remove_all (GtkStatusbar *statusbar,
                          guint         context_id)
{
  GtkStatusbarMsg *msg;
  GSList *prev, *list;

  g_return_if_fail (GTK_IS_STATUSBAR (statusbar));

455
  if (statusbar->messages == NULL)
456
457
    return;

458
459
460
461
462
  /* We special-case the topmost message at the bottom of this
   * function:
   * If we need to pop it, we have to update various state and we want
   * an up-to-date list of remaining messages in that case.
   */
463
  prev = statusbar->messages;
464
  list = prev->next;
465
466
467
468
469
470
471

  while (list != NULL)
    {
      msg = list->data;

      if (msg->context_id == context_id)
        {
472
          prev->next = list->next;
473

Paolo Borelli's avatar
Paolo Borelli committed
474
          gtk_statusbar_msg_free (msg);
475
476
          g_slist_free_1 (list);

477
          list = prev->next;
478
479
480
481
482
483
484
        }
      else
        {
          prev = list;
          list = prev->next;
        }
    }
485
486

  /* Treat topmost message here */
487
  msg = statusbar->messages->data;
488
489
490
491
  if (msg->context_id == context_id)
    {
      gtk_statusbar_pop (statusbar, context_id);
    }
492
493
}

494
495
/**
 * gtk_statusbar_get_message:
Matthias Clasen's avatar
Matthias Clasen committed
496
 * @statusbar: a `GtkStatusbar`
497
 *
Matthias Clasen's avatar
Matthias Clasen committed
498
 * Retrieves the contents of the label in `GtkStatusbar`.
499
500
501
 *
 * Returns: (transfer none): the contents of the statusbar
 */
502
const char *
503
gtk_statusbar_get_message (GtkStatusbar *statusbar)
504
505
506
{
  g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), NULL);

507
  return gtk_label_get_label (GTK_LABEL (statusbar->label));
508
}