]> Pileus Git - ~andy/gtk/blob - gtk/gtkstatusbar.c
Ignore the sgml directory made by gtkdoc. Use gtk_window_set_resizable
[~andy/gtk] / gtk / gtkstatusbar.c
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, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /*
22  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
23  * file for a list of people on the GTK+ Team.  See the ChangeLog
24  * files for a list of changes.  These files are distributed with
25  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
26  */
27
28 #include "gtkframe.h"
29 #include "gtklabel.h"
30 #include "gtksignal.h"
31 #include "gtkstatusbar.h"
32 #include "gtkwindow.h"
33 #include "gtkintl.h"
34
35 typedef struct _GtkStatusbarMsg GtkStatusbarMsg;
36
37 struct _GtkStatusbarMsg
38 {
39   gchar *text;
40   guint context_id;
41   guint message_id;
42 };
43
44 enum
45 {
46   SIGNAL_TEXT_PUSHED,
47   SIGNAL_TEXT_POPPED,
48   SIGNAL_LAST
49 };
50
51 static void     gtk_statusbar_class_init     (GtkStatusbarClass *class);
52 static void     gtk_statusbar_init           (GtkStatusbar      *statusbar);
53 static void     gtk_statusbar_destroy        (GtkObject         *object);
54 static void     gtk_statusbar_update         (GtkStatusbar      *statusbar,
55                                               guint              context_id,
56                                               const gchar       *text);
57 static void     gtk_statusbar_size_allocate  (GtkWidget         *widget,
58                                               GtkAllocation     *allocation);
59 static void     gtk_statusbar_realize        (GtkWidget         *widget);
60 static void     gtk_statusbar_unrealize      (GtkWidget         *widget);
61 static void     gtk_statusbar_map            (GtkWidget         *widget);
62 static void     gtk_statusbar_unmap          (GtkWidget         *widget);
63 static gboolean gtk_statusbar_button_press   (GtkWidget         *widget,
64                                               GdkEventButton    *event);
65 static gboolean gtk_statusbar_expose_event   (GtkWidget         *widget,
66                                               GdkEventExpose    *event);
67 static void     gtk_statusbar_size_request   (GtkWidget         *widget,
68                                               GtkRequisition    *requisition);
69 static void     gtk_statusbar_size_allocate  (GtkWidget         *widget,
70                                               GtkAllocation     *allocation);
71 static void     gtk_statusbar_create_window  (GtkStatusbar      *statusbar);
72 static void     gtk_statusbar_destroy_window (GtkStatusbar      *statusbar);
73
74 static GtkContainerClass *parent_class;
75 static guint              statusbar_signals[SIGNAL_LAST] = { 0 };
76
77 GtkType      
78 gtk_statusbar_get_type (void)
79 {
80   static GtkType statusbar_type = 0;
81
82   if (!statusbar_type)
83     {
84       static const GtkTypeInfo statusbar_info =
85       {
86         "GtkStatusbar",
87         sizeof (GtkStatusbar),
88         sizeof (GtkStatusbarClass),
89         (GtkClassInitFunc) gtk_statusbar_class_init,
90         (GtkObjectInitFunc) gtk_statusbar_init,
91         /* reserved_1 */ NULL,
92         /* reserved_2 */ NULL,
93         (GtkClassInitFunc) NULL,
94       };
95
96       statusbar_type = gtk_type_unique (GTK_TYPE_HBOX, &statusbar_info);
97     }
98
99   return statusbar_type;
100 }
101
102 static void
103 gtk_statusbar_class_init (GtkStatusbarClass *class)
104 {
105   GtkObjectClass *object_class;
106   GtkWidgetClass *widget_class;
107   GtkContainerClass *container_class;
108
109   object_class = (GtkObjectClass *) class;
110   widget_class = (GtkWidgetClass *) class;
111   container_class = (GtkContainerClass *) class;
112
113   parent_class = gtk_type_class (GTK_TYPE_HBOX);
114   
115   object_class->destroy = gtk_statusbar_destroy;
116
117   widget_class->realize = gtk_statusbar_realize;
118   widget_class->unrealize = gtk_statusbar_unrealize;
119   widget_class->map = gtk_statusbar_map;
120   widget_class->unmap = gtk_statusbar_unmap;
121   
122   widget_class->button_press_event = gtk_statusbar_button_press;
123   widget_class->expose_event = gtk_statusbar_expose_event;
124
125   widget_class->size_request = gtk_statusbar_size_request;
126   widget_class->size_allocate = gtk_statusbar_size_allocate;
127   
128   class->messages_mem_chunk = g_mem_chunk_new ("GtkStatusBar messages mem chunk",
129                                                sizeof (GtkStatusbarMsg),
130                                                sizeof (GtkStatusbarMsg) * 64,
131                                                G_ALLOC_AND_FREE);
132
133   class->text_pushed = gtk_statusbar_update;
134   class->text_popped = gtk_statusbar_update;
135   
136   statusbar_signals[SIGNAL_TEXT_PUSHED] =
137     gtk_signal_new ("text_pushed",
138                     GTK_RUN_LAST,
139                     GTK_CLASS_TYPE (object_class),
140                     GTK_SIGNAL_OFFSET (GtkStatusbarClass, text_pushed),
141                     gtk_marshal_VOID__UINT_STRING,
142                     GTK_TYPE_NONE, 2,
143                     GTK_TYPE_UINT,
144                     GTK_TYPE_STRING);
145   statusbar_signals[SIGNAL_TEXT_POPPED] =
146     gtk_signal_new ("text_popped",
147                     GTK_RUN_LAST,
148                     GTK_CLASS_TYPE (object_class),
149                     GTK_SIGNAL_OFFSET (GtkStatusbarClass, text_popped),
150                     gtk_marshal_VOID__UINT_STRING,
151                     GTK_TYPE_NONE, 2,
152                     GTK_TYPE_UINT,
153                     GTK_TYPE_STRING);
154
155   gtk_widget_class_install_style_property (widget_class,
156                                            g_param_spec_enum ("shadow_type",
157                                                               _("Shadow type"),
158                                                               _("Style of bevel around the statusbar text"),
159                                                               GTK_TYPE_SHADOW_TYPE,
160                                                               GTK_SHADOW_IN,
161                                                               G_PARAM_READABLE));
162 }
163
164 static void
165 gtk_statusbar_init (GtkStatusbar *statusbar)
166 {
167   GtkBox *box;
168   GtkShadowType shadow_type;
169   
170   box = GTK_BOX (statusbar);
171
172   box->spacing = 2;
173   box->homogeneous = FALSE;
174
175   statusbar->has_resize_grip = TRUE;
176
177   gtk_widget_style_get (GTK_WIDGET (statusbar), "shadow_type", &shadow_type, NULL);
178   
179   statusbar->frame = gtk_frame_new (NULL);
180   gtk_frame_set_shadow_type (GTK_FRAME (statusbar->frame), shadow_type);
181   gtk_box_pack_start (box, statusbar->frame, TRUE, TRUE, 0);
182   gtk_widget_show (statusbar->frame);
183
184   statusbar->label = gtk_label_new ("");
185   gtk_misc_set_alignment (GTK_MISC (statusbar->label), 0.0, 0.0);
186   /* don't expand the size request for the label; if we
187    * do that then toplevels weirdly resize
188    */
189   gtk_widget_set_usize (statusbar->label, 1, -1);
190   gtk_container_add (GTK_CONTAINER (statusbar->frame), statusbar->label);
191   gtk_widget_show (statusbar->label);
192
193   statusbar->seq_context_id = 1;
194   statusbar->seq_message_id = 1;
195   statusbar->messages = NULL;
196   statusbar->keys = NULL;
197 }
198
199 GtkWidget* 
200 gtk_statusbar_new (void)
201 {
202   return gtk_type_new (GTK_TYPE_STATUSBAR);
203 }
204
205 static void
206 gtk_statusbar_update (GtkStatusbar *statusbar,
207                       guint         context_id,
208                       const gchar  *text)
209 {
210   g_return_if_fail (statusbar != NULL);
211   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
212
213   if (!text)
214     text = "";
215
216   gtk_label_set_text (GTK_LABEL (statusbar->label), text);
217 }
218
219 guint
220 gtk_statusbar_get_context_id (GtkStatusbar *statusbar,
221                               const gchar  *context_description)
222 {
223   gchar *string;
224   guint *id;
225   
226   g_return_val_if_fail (statusbar != NULL, 0);
227   g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
228   g_return_val_if_fail (context_description != NULL, 0);
229
230   /* we need to preserve namespaces on object datas */
231   string = g_strconcat ("gtk-status-bar-context:", context_description, NULL);
232
233   id = gtk_object_get_data (GTK_OBJECT (statusbar), string);
234   if (!id)
235     {
236       id = g_new (guint, 1);
237       *id = statusbar->seq_context_id++;
238       gtk_object_set_data_full (GTK_OBJECT (statusbar), string, id, (GtkDestroyNotify) g_free);
239       statusbar->keys = g_slist_prepend (statusbar->keys, string);
240     }
241   else
242     g_free (string);
243
244   return *id;
245 }
246
247 guint
248 gtk_statusbar_push (GtkStatusbar *statusbar,
249                     guint         context_id,
250                     const gchar  *text)
251 {
252   GtkStatusbarMsg *msg;
253   GtkStatusbarClass *class;
254
255   g_return_val_if_fail (statusbar != NULL, 0);
256   g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), 0);
257   g_return_val_if_fail (text != NULL, 0);
258
259   class = GTK_STATUSBAR_GET_CLASS (statusbar);
260   msg = g_chunk_new (GtkStatusbarMsg, class->messages_mem_chunk);
261   msg->text = g_strdup (text);
262   msg->context_id = context_id;
263   msg->message_id = statusbar->seq_message_id++;
264
265   statusbar->messages = g_slist_prepend (statusbar->messages, msg);
266
267   gtk_signal_emit (GTK_OBJECT (statusbar),
268                    statusbar_signals[SIGNAL_TEXT_PUSHED],
269                    msg->context_id,
270                    msg->text);
271
272   return msg->message_id;
273 }
274
275 void
276 gtk_statusbar_pop (GtkStatusbar *statusbar,
277                    guint         context_id)
278 {
279   GtkStatusbarMsg *msg;
280
281   g_return_if_fail (statusbar != NULL);
282   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
283
284   if (statusbar->messages)
285     {
286       GSList *list;
287
288       for (list = statusbar->messages; list; list = list->next)
289         {
290           msg = list->data;
291
292           if (msg->context_id == context_id)
293             {
294               GtkStatusbarClass *class;
295
296               class = GTK_STATUSBAR_GET_CLASS (statusbar);
297
298               statusbar->messages = g_slist_remove_link (statusbar->messages,
299                                                          list);
300               g_free (msg->text);
301               g_mem_chunk_free (class->messages_mem_chunk, msg);
302               g_slist_free_1 (list);
303               break;
304             }
305         }
306     }
307
308   msg = statusbar->messages ? statusbar->messages->data : NULL;
309
310   gtk_signal_emit (GTK_OBJECT (statusbar),
311                    statusbar_signals[SIGNAL_TEXT_POPPED],
312                    (guint) (msg ? msg->context_id : 0),
313                    msg ? msg->text : NULL);
314 }
315
316 void
317 gtk_statusbar_remove (GtkStatusbar *statusbar,
318                       guint        context_id,
319                       guint        message_id)
320 {
321   GtkStatusbarMsg *msg;
322
323   g_return_if_fail (statusbar != NULL);
324   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
325   g_return_if_fail (message_id > 0);
326
327   msg = statusbar->messages ? statusbar->messages->data : NULL;
328   if (msg)
329     {
330       GSList *list;
331
332       /* care about signal emission if the topmost item is removed */
333       if (msg->context_id == context_id &&
334           msg->message_id == message_id)
335         {
336           gtk_statusbar_pop (statusbar, context_id);
337           return;
338         }
339       
340       for (list = statusbar->messages; list; list = list->next)
341         {
342           msg = list->data;
343           
344           if (msg->context_id == context_id &&
345               msg->message_id == message_id)
346             {
347               GtkStatusbarClass *class;
348               
349               class = GTK_STATUSBAR_GET_CLASS (statusbar);
350               statusbar->messages = g_slist_remove_link (statusbar->messages, list);
351               g_free (msg->text);
352               g_mem_chunk_free (class->messages_mem_chunk, msg);
353               g_slist_free_1 (list);
354               
355               break;
356             }
357         }
358     }
359 }
360
361 void
362 gtk_statusbar_set_has_resize_grip (GtkStatusbar *statusbar,
363                                    gboolean      setting)
364 {
365   g_return_if_fail (GTK_IS_STATUSBAR (statusbar));
366
367   setting = setting != FALSE;
368
369   if (setting != statusbar->has_resize_grip)
370     {
371       statusbar->has_resize_grip = setting;
372       gtk_widget_queue_draw (GTK_WIDGET (statusbar));
373
374       if (GTK_WIDGET_REALIZED (statusbar))
375         {
376           if (statusbar->has_resize_grip && statusbar->grip_window == NULL)
377             gtk_statusbar_create_window (statusbar);
378           else if (!statusbar->has_resize_grip && statusbar->grip_window != NULL)
379             gtk_statusbar_destroy_window (statusbar);
380         }
381     }
382 }
383
384 gboolean
385 gtk_statusbar_get_has_resize_grip (GtkStatusbar *statusbar)
386 {
387   g_return_val_if_fail (GTK_IS_STATUSBAR (statusbar), FALSE);
388
389   return statusbar->has_resize_grip;
390 }
391
392 static void
393 gtk_statusbar_destroy (GtkObject *object)
394 {
395   GtkStatusbar *statusbar;
396   GtkStatusbarClass *class;
397   GSList *list;
398
399   g_return_if_fail (object != NULL);
400   g_return_if_fail (GTK_IS_STATUSBAR (object));
401
402   statusbar = GTK_STATUSBAR (object);
403   class = GTK_STATUSBAR_GET_CLASS (statusbar);
404
405   for (list = statusbar->messages; list; list = list->next)
406     {
407       GtkStatusbarMsg *msg;
408
409       msg = list->data;
410       g_free (msg->text);
411       g_mem_chunk_free (class->messages_mem_chunk, msg);
412     }
413   g_slist_free (statusbar->messages);
414   statusbar->messages = NULL;
415
416   for (list = statusbar->keys; list; list = list->next)
417     g_free (list->data);
418   g_slist_free (statusbar->keys);
419   statusbar->keys = NULL;
420
421   GTK_OBJECT_CLASS (parent_class)->destroy (object);
422 }
423
424 static void
425 get_grip_rect (GtkStatusbar *statusbar,
426                GdkRectangle *rect)
427 {
428   GtkWidget *widget;
429   gint w, h;
430   
431   widget = GTK_WIDGET (statusbar);
432
433   /* These are in effect the max/default size of the grip. */
434   w = 18;
435   h = 18;
436
437   if (w > (widget->allocation.width))
438     w = widget->allocation.width;
439
440   if (h > (widget->allocation.height - widget->style->ythickness))
441     h = widget->allocation.height - widget->style->ythickness;
442   
443   rect->x = widget->allocation.x + widget->allocation.width - w;
444   rect->y = widget->allocation.y + widget->allocation.height - h;
445   rect->width = w;
446   rect->height = h;
447 }
448
449 static void
450 gtk_statusbar_create_window (GtkStatusbar *statusbar)
451 {
452   GtkWidget *widget;
453   GdkWindowAttr attributes;
454   gint attributes_mask;
455   GdkRectangle rect;
456   
457   g_return_if_fail (GTK_WIDGET_REALIZED (statusbar));
458   g_return_if_fail (statusbar->has_resize_grip);
459   
460   widget = GTK_WIDGET (statusbar);
461
462   get_grip_rect (statusbar, &rect);
463
464   attributes.x = rect.x;
465   attributes.y = rect.y;
466   attributes.width = rect.width;
467   attributes.height = rect.height;
468   attributes.window_type = GDK_WINDOW_CHILD;
469   attributes.wclass = GDK_INPUT_ONLY;
470   attributes.event_mask = gtk_widget_get_events (widget) |
471     GDK_BUTTON_PRESS_MASK;
472
473   attributes_mask = GDK_WA_X | GDK_WA_Y;
474
475   statusbar->grip_window = gdk_window_new (widget->window,
476                                            &attributes, attributes_mask);
477   gdk_window_set_user_data (statusbar->grip_window, widget);
478 }
479
480 static void
481 gtk_statusbar_destroy_window (GtkStatusbar *statusbar)
482 {
483   gdk_window_set_user_data (statusbar->grip_window, NULL);
484   gdk_window_destroy (statusbar->grip_window);
485   statusbar->grip_window = NULL;
486 }
487
488 static void
489 gtk_statusbar_realize (GtkWidget *widget)
490 {
491   GtkStatusbar *statusbar;
492
493   statusbar = GTK_STATUSBAR (widget);
494   
495   (* GTK_WIDGET_CLASS (parent_class)->realize) (widget);
496
497   if (statusbar->has_resize_grip)
498     gtk_statusbar_create_window (statusbar);
499 }
500
501 static void
502 gtk_statusbar_unrealize (GtkWidget *widget)
503 {
504   GtkStatusbar *statusbar;
505
506   statusbar = GTK_STATUSBAR (widget);
507
508   if (statusbar->grip_window)
509     gtk_statusbar_destroy_window (statusbar);
510   
511   (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
512 }
513
514 static void
515 gtk_statusbar_map (GtkWidget *widget)
516 {
517   GtkStatusbar *statusbar;
518
519   statusbar = GTK_STATUSBAR (widget);
520   
521   (* GTK_WIDGET_CLASS (parent_class)->map) (widget);
522   
523   if (statusbar->grip_window)
524     gdk_window_show (statusbar->grip_window);
525 }
526
527 static void
528 gtk_statusbar_unmap (GtkWidget *widget)
529 {
530   GtkStatusbar *statusbar;
531
532   statusbar = GTK_STATUSBAR (widget);
533
534   if (statusbar->grip_window)
535     gdk_window_hide (statusbar->grip_window);
536   
537   (* GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
538 }
539
540 static gboolean
541 gtk_statusbar_button_press (GtkWidget      *widget,
542                             GdkEventButton *event)
543 {
544   GtkStatusbar *statusbar;
545   GtkWidget *ancestor;
546   
547   statusbar = GTK_STATUSBAR (widget);
548   
549   if (!statusbar->has_resize_grip)
550     return FALSE;
551   
552   ancestor = gtk_widget_get_toplevel (widget);
553
554   if (!GTK_IS_WINDOW (ancestor))
555     return FALSE;
556
557   if (event->button == 1)
558     gtk_window_begin_resize_drag (GTK_WINDOW (ancestor),
559                                   GDK_WINDOW_EDGE_SOUTH_EAST,
560                                   event->button,
561                                   event->x_root, event->y_root,
562                                   event->time);
563   else if (event->button == 2)
564     gtk_window_begin_move_drag (GTK_WINDOW (ancestor),
565                                 event->button,
566                                 event->x_root, event->y_root,
567                                 event->time);
568   else
569     return FALSE;
570   
571   return TRUE;
572 }
573
574 static gboolean
575 gtk_statusbar_expose_event (GtkWidget      *widget,
576                             GdkEventExpose *event)
577 {
578   GtkStatusbar *statusbar;
579   GdkRectangle rect;
580   
581   statusbar = GTK_STATUSBAR (widget);
582
583   GTK_WIDGET_CLASS (parent_class)->expose_event (widget, event);
584
585   if (statusbar->has_resize_grip)
586     {
587       get_grip_rect (statusbar, &rect);
588
589       gtk_paint_resize_grip (widget->style,
590                              widget->window,
591                              GTK_WIDGET_STATE (widget),
592                              NULL,
593                              widget,
594                              "statusbar",
595                              GDK_WINDOW_EDGE_SOUTH_EAST,
596                              rect.x, rect.y,
597                              /* don't draw grip over the frame, though you
598                               * can click on the frame.
599                               */
600                              rect.width - widget->style->xthickness,
601                              rect.height - widget->style->ythickness);
602     }
603
604   return FALSE;
605 }
606
607 static void
608 gtk_statusbar_size_request   (GtkWidget      *widget,
609                               GtkRequisition *requisition)
610 {
611   GtkStatusbar *statusbar;
612   GtkShadowType shadow_type;
613   
614   statusbar = GTK_STATUSBAR (widget);
615
616   gtk_widget_style_get (GTK_WIDGET (statusbar), "shadow_type", &shadow_type, NULL);  
617   gtk_frame_set_shadow_type (GTK_FRAME (statusbar->frame), shadow_type);
618   
619   GTK_WIDGET_CLASS (parent_class)->size_request (widget, requisition);
620
621   if (statusbar->has_resize_grip)
622     {
623       GdkRectangle rect;
624
625       /* x, y in the grip rect depend on size allocation, but
626        * w, h do not so this is OK
627        */
628       get_grip_rect (statusbar, &rect);
629       
630       requisition->width += rect.width;
631       requisition->height = MAX (requisition->height, rect.height);
632     }
633 }
634
635 static void
636 gtk_statusbar_size_allocate  (GtkWidget     *widget,
637                               GtkAllocation *allocation)
638 {
639   GtkStatusbar *statusbar;
640   
641   statusbar = GTK_STATUSBAR (widget);
642
643   if (statusbar->has_resize_grip)
644     {
645       GdkRectangle rect;
646       GtkRequisition saved_req;
647       
648       widget->allocation = *allocation; /* get_grip_rect needs this info */
649       get_grip_rect (statusbar, &rect);
650   
651       if (statusbar->grip_window)
652         gdk_window_move_resize (statusbar->grip_window,
653                                 rect.x, rect.y,
654                                 rect.width, rect.height);
655       
656       /* enter the bad hack zone */      
657       saved_req = widget->requisition;
658       widget->requisition.width -= rect.width; /* HBox::size_allocate needs this */
659       if (widget->requisition.width < 0)
660         widget->requisition.width = 0;
661       GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
662       widget->requisition = saved_req;
663     }
664   else
665     {
666       /* chain up normally */
667       GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation);
668     }
669 }