]> Pileus Git - ~andy/gtk/blob - gtk/gtkimagemenuitem.c
Also intern static strings passed to g_object_set_data()
[~andy/gtk] / gtk / gtkimagemenuitem.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2001 Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27 #include <config.h>
28 #include "gtkimagemenuitem.h"
29 #include "gtkaccellabel.h"
30 #include "gtkintl.h"
31 #include "gtkstock.h"
32 #include "gtkiconfactory.h"
33 #include "gtkimage.h"
34 #include "gtkmenubar.h"
35 #include "gtkcontainer.h"
36 #include "gtkwindow.h"
37 #include "gtkprivate.h"
38 #include "gtkalias.h"
39
40 static void gtk_image_menu_item_class_init           (GtkImageMenuItemClass *klass);
41 static void gtk_image_menu_item_init                 (GtkImageMenuItem      *image_menu_item);
42 static void gtk_image_menu_item_size_request         (GtkWidget        *widget,
43                                                       GtkRequisition   *requisition);
44 static void gtk_image_menu_item_size_allocate        (GtkWidget        *widget,
45                                                       GtkAllocation    *allocation);
46 static void gtk_image_menu_item_remove               (GtkContainer          *container,
47                                                       GtkWidget             *child);
48 static void gtk_image_menu_item_toggle_size_request  (GtkMenuItem           *menu_item,
49                                                       gint                  *requisition);
50
51 static void gtk_image_menu_item_forall     (GtkContainer   *container,
52                                             gboolean        include_internals,
53                                             GtkCallback     callback,
54                                             gpointer        callback_data);
55
56 static void gtk_image_menu_item_set_property (GObject         *object,
57                                               guint            prop_id,
58                                               const GValue    *value,
59                                               GParamSpec      *pspec);
60 static void gtk_image_menu_item_get_property (GObject         *object,
61                                               guint            prop_id,
62                                               GValue          *value,
63                                               GParamSpec      *pspec);
64 static void gtk_image_menu_item_screen_changed (GtkWidget        *widget,
65                                                 GdkScreen        *previous_screen);
66
67
68 enum {
69   PROP_ZERO,
70   PROP_IMAGE
71 };
72
73 static GtkMenuItemClass *parent_class = NULL;
74
75 GType
76 gtk_image_menu_item_get_type (void)
77 {
78   static GType image_menu_item_type = 0;
79
80   if (!image_menu_item_type)
81     {
82       static const GTypeInfo image_menu_item_info =
83       {
84         sizeof (GtkImageMenuItemClass),
85         NULL,           /* base_init */
86         NULL,           /* base_finalize */
87         (GClassInitFunc) gtk_image_menu_item_class_init,
88         NULL,           /* class_finalize */
89         NULL,           /* class_data */
90         sizeof (GtkImageMenuItem),
91         0,              /* n_preallocs */
92         (GInstanceInitFunc) gtk_image_menu_item_init,
93       };
94
95       image_menu_item_type =
96         g_type_register_static (GTK_TYPE_MENU_ITEM, g_intern_static_string ("GtkImageMenuItem"),
97                                 &image_menu_item_info, 0);
98     }
99
100   return image_menu_item_type;
101 }
102
103 static void
104 gtk_image_menu_item_class_init (GtkImageMenuItemClass *klass)
105 {
106   GObjectClass *gobject_class;
107   GtkWidgetClass *widget_class;
108   GtkMenuItemClass *menu_item_class;
109   GtkContainerClass *container_class;
110
111   gobject_class = (GObjectClass*) klass;
112   widget_class = (GtkWidgetClass*) klass;
113   menu_item_class = (GtkMenuItemClass*) klass;
114   container_class = (GtkContainerClass*) klass;
115   
116   parent_class = g_type_class_peek_parent (klass);
117   
118   widget_class->screen_changed = gtk_image_menu_item_screen_changed;
119   widget_class->size_request = gtk_image_menu_item_size_request;
120   widget_class->size_allocate = gtk_image_menu_item_size_allocate;
121
122   container_class->forall = gtk_image_menu_item_forall;
123   container_class->remove = gtk_image_menu_item_remove;
124   
125   menu_item_class->toggle_size_request = gtk_image_menu_item_toggle_size_request;
126
127   gobject_class->set_property = gtk_image_menu_item_set_property;
128   gobject_class->get_property = gtk_image_menu_item_get_property;
129   
130   g_object_class_install_property (gobject_class,
131                                    PROP_IMAGE,
132                                    g_param_spec_object ("image",
133                                                         P_("Image widget"),
134                                                         P_("Child widget to appear next to the menu text"),
135                                                         GTK_TYPE_WIDGET,
136                                                         GTK_PARAM_READWRITE));
137
138   gtk_settings_install_property (g_param_spec_boolean ("gtk-menu-images",
139                                                        P_("Show menu images"),
140                                                        P_("Whether images should be shown in menus"),
141                                                        TRUE,
142                                                        GTK_PARAM_READWRITE));
143   
144 }
145
146 static void
147 gtk_image_menu_item_init (GtkImageMenuItem *image_menu_item)
148 {
149   image_menu_item->image = NULL;
150 }
151
152 static void
153 gtk_image_menu_item_set_property (GObject         *object,
154                                   guint            prop_id,
155                                   const GValue    *value,
156                                   GParamSpec      *pspec)
157 {
158   GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (object);
159   
160   switch (prop_id)
161     {
162     case PROP_IMAGE:
163       {
164         GtkWidget *image;
165
166         image = (GtkWidget*) g_value_get_object (value);
167
168         gtk_image_menu_item_set_image (image_menu_item, image);
169       }
170       break;
171     default:
172       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
173       break;
174     }
175 }
176 static void
177 gtk_image_menu_item_get_property (GObject         *object,
178                                   guint            prop_id,
179                                   GValue          *value,
180                                   GParamSpec      *pspec)
181 {
182   GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (object);
183   
184   switch (prop_id)
185     {
186     case PROP_IMAGE:
187       g_value_set_object (value,
188                           (GObject*) image_menu_item->image);
189       break;
190     default:
191       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
192       break;
193     }
194 }
195
196 static gboolean
197 show_image (GtkImageMenuItem *image_menu_item)
198 {
199   GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (image_menu_item));
200   gboolean show;
201
202   g_object_get (settings, "gtk-menu-images", &show, NULL);
203
204   return show;
205 }
206
207 static void
208 gtk_image_menu_item_toggle_size_request (GtkMenuItem *menu_item,
209                                          gint        *requisition)
210 {
211   GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (menu_item);
212   GtkPackDirection pack_dir;
213   
214   if (GTK_IS_MENU_BAR (GTK_WIDGET (menu_item)->parent))
215     pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (GTK_WIDGET (menu_item)->parent));
216   else
217     pack_dir = GTK_PACK_DIRECTION_LTR;
218
219   *requisition = 0;
220
221   if (image_menu_item->image && show_image (image_menu_item))
222     {
223       GtkRequisition image_requisition;
224       guint toggle_spacing;
225       gtk_widget_get_child_requisition (image_menu_item->image,
226                                         &image_requisition);
227
228       gtk_widget_style_get (GTK_WIDGET (menu_item),
229                             "toggle-spacing", &toggle_spacing,
230                             NULL);
231       
232       if (pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL)
233         {
234           if (image_requisition.width > 0)
235             *requisition = image_requisition.width + toggle_spacing;
236         }
237       else
238         {
239           if (image_requisition.height > 0)
240             *requisition = image_requisition.height + toggle_spacing;
241         }
242     }
243 }
244
245
246 static void
247 gtk_image_menu_item_size_request (GtkWidget      *widget,
248                                   GtkRequisition *requisition)
249 {
250   GtkImageMenuItem *image_menu_item;
251   gint child_width = 0;
252   gint child_height = 0;
253   GtkPackDirection pack_dir;
254   
255   if (GTK_IS_MENU_BAR (widget->parent))
256     pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (widget->parent));
257   else
258     pack_dir = GTK_PACK_DIRECTION_LTR;
259
260   image_menu_item = GTK_IMAGE_MENU_ITEM (widget);
261   
262   if (image_menu_item->image && 
263       GTK_WIDGET_VISIBLE (image_menu_item->image) && 
264       show_image (image_menu_item))
265     {
266       GtkRequisition child_requisition;
267       
268       gtk_widget_size_request (image_menu_item->image,
269                                &child_requisition);
270
271       child_width = child_requisition.width;
272       child_height = child_requisition.height;
273     }
274   
275   (* GTK_WIDGET_CLASS (parent_class)->size_request) (widget, requisition);
276
277   /* not done with height since that happens via the
278    * toggle_size_request
279    */
280   if (pack_dir == GTK_PACK_DIRECTION_LTR || pack_dir == GTK_PACK_DIRECTION_RTL)
281     requisition->height = MAX (requisition->height, child_height);
282   else
283     requisition->width = MAX (requisition->width, child_width);
284     
285   
286   /* Note that GtkMenuShell always size requests before
287    * toggle_size_request, so toggle_size_request will be able to use
288    * image_menu_item->image->requisition
289    */
290 }
291
292 static void
293 gtk_image_menu_item_size_allocate (GtkWidget     *widget,
294                                    GtkAllocation *allocation)
295 {
296   GtkImageMenuItem *image_menu_item;
297   GtkPackDirection pack_dir;
298   
299   if (GTK_IS_MENU_BAR (widget->parent))
300     pack_dir = gtk_menu_bar_get_child_pack_direction (GTK_MENU_BAR (widget->parent));
301   else
302     pack_dir = GTK_PACK_DIRECTION_LTR;
303   
304   image_menu_item = GTK_IMAGE_MENU_ITEM (widget);  
305   
306   (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
307
308   if (image_menu_item->image && show_image (image_menu_item))
309     {
310       gint x, y, offset;
311       GtkRequisition child_requisition;
312       GtkAllocation child_allocation;
313       guint horizontal_padding, toggle_spacing;
314
315       gtk_widget_style_get (widget,
316                             "horizontal-padding", &horizontal_padding,
317                             "toggle-spacing", &toggle_spacing,
318                             NULL);
319       
320       /* Man this is lame hardcoding action, but I can't
321        * come up with a solution that's really better.
322        */
323
324       gtk_widget_get_child_requisition (image_menu_item->image,
325                                         &child_requisition);
326
327       if (pack_dir == GTK_PACK_DIRECTION_LTR ||
328           pack_dir == GTK_PACK_DIRECTION_RTL)
329         {
330           offset = GTK_CONTAINER (image_menu_item)->border_width +
331             widget->style->xthickness;
332           
333           if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) ==
334               (pack_dir == GTK_PACK_DIRECTION_LTR))
335             x = offset + horizontal_padding +
336               (GTK_MENU_ITEM (image_menu_item)->toggle_size -
337                toggle_spacing - child_requisition.width) / 2;
338           else
339             x = widget->allocation.width - offset - horizontal_padding -
340               GTK_MENU_ITEM (image_menu_item)->toggle_size + toggle_spacing +
341               (GTK_MENU_ITEM (image_menu_item)->toggle_size -
342                toggle_spacing - child_requisition.width) / 2;
343           
344           y = (widget->allocation.height - child_requisition.height) / 2;
345         }
346       else
347         {
348           offset = GTK_CONTAINER (image_menu_item)->border_width +
349             widget->style->ythickness;
350           
351           if ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_LTR) ==
352               (pack_dir == GTK_PACK_DIRECTION_TTB))
353             y = offset + horizontal_padding +
354               (GTK_MENU_ITEM (image_menu_item)->toggle_size -
355                toggle_spacing - child_requisition.height) / 2;
356           else
357             y = widget->allocation.height - offset - horizontal_padding -
358               GTK_MENU_ITEM (image_menu_item)->toggle_size + toggle_spacing +
359               (GTK_MENU_ITEM (image_menu_item)->toggle_size -
360                toggle_spacing - child_requisition.height) / 2;
361
362           x = (widget->allocation.width - child_requisition.width) / 2;
363         }
364       
365       child_allocation.width = child_requisition.width;
366       child_allocation.height = child_requisition.height;
367       child_allocation.x = widget->allocation.x + MAX (x, 0);
368       child_allocation.y = widget->allocation.y + MAX (y, 0);
369
370       gtk_widget_size_allocate (image_menu_item->image, &child_allocation);
371     }
372 }
373
374 static void
375 gtk_image_menu_item_forall (GtkContainer   *container,
376                             gboolean        include_internals,
377                             GtkCallback     callback,
378                             gpointer        callback_data)
379 {
380   GtkImageMenuItem *image_menu_item = GTK_IMAGE_MENU_ITEM (container);
381   
382   (* GTK_CONTAINER_CLASS (parent_class)->forall) (container,
383                                                   include_internals,
384                                                   callback,
385                                                   callback_data);
386
387   if (image_menu_item->image)
388     (* callback) (image_menu_item->image, callback_data);
389 }
390
391 /**
392  * gtk_image_menu_item_new:
393  * @returns: a new #GtkImageMenuItem.
394  *
395  * Creates a new #GtkImageMenuItem with an empty label.
396  **/
397 GtkWidget*
398 gtk_image_menu_item_new (void)
399 {
400   return g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL);
401 }
402
403 /**
404  * gtk_image_menu_item_new_with_label:
405  * @label: the text of the menu item.
406  * @returns: a new #GtkImageMenuItem.
407  *
408  * Creates a new #GtkImageMenuItem containing a label. 
409  **/
410 GtkWidget*
411 gtk_image_menu_item_new_with_label (const gchar *label)
412 {
413   GtkImageMenuItem *image_menu_item;
414   GtkWidget *accel_label;
415   
416   image_menu_item = g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL);
417
418   accel_label = gtk_accel_label_new (label);
419   gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);
420
421   gtk_container_add (GTK_CONTAINER (image_menu_item), accel_label);
422   gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (accel_label),
423                                     GTK_WIDGET (image_menu_item));
424   gtk_widget_show (accel_label);
425
426   return GTK_WIDGET(image_menu_item);
427 }
428
429
430 /**
431  * gtk_image_menu_item_new_with_mnemonic:
432  * @label: the text of the menu item, with an underscore in front of the
433  *         mnemonic character
434  * @returns: a new #GtkImageMenuItem
435  *
436  * Creates a new #GtkImageMenuItem containing a label. The label
437  * will be created using gtk_label_new_with_mnemonic(), so underscores
438  * in @label indicate the mnemonic for the menu item.
439  **/
440 GtkWidget*
441 gtk_image_menu_item_new_with_mnemonic (const gchar *label)
442 {
443   GtkImageMenuItem *image_menu_item;
444   GtkWidget *accel_label;
445   
446   image_menu_item = g_object_new (GTK_TYPE_IMAGE_MENU_ITEM, NULL);
447
448   accel_label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
449   gtk_label_set_text_with_mnemonic (GTK_LABEL (accel_label), label);
450   gtk_misc_set_alignment (GTK_MISC (accel_label), 0.0, 0.5);
451
452   gtk_container_add (GTK_CONTAINER (image_menu_item), accel_label);
453   gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (accel_label),
454                                     GTK_WIDGET (image_menu_item));
455   gtk_widget_show (accel_label);
456
457   return GTK_WIDGET(image_menu_item);
458 }
459
460 /**
461  * gtk_image_menu_item_new_from_stock:
462  * @stock_id: the name of the stock item.
463  * @accel_group: the #GtkAccelGroup to add the menu items accelerator to,
464  *   or %NULL.
465  * @returns: a new #GtkImageMenuItem.
466  *
467  * Creates a new #GtkImageMenuItem containing the image and text from a 
468  * stock item. Some stock ids have preprocessor macros like #GTK_STOCK_OK 
469  * and #GTK_STOCK_APPLY.
470  *
471  * If you want this menu item to have changeable accelerators, then pass in
472  * %NULL for accel_group. Next call gtk_menu_item_set_accel_path() with an
473  * appropriate path for the menu item, use gtk_stock_lookup() to look up the
474  * standard accelerator for the stock item, and if one is found, call
475  * gtk_accel_map_add_entry() to register it.
476  **/
477 GtkWidget*
478 gtk_image_menu_item_new_from_stock (const gchar      *stock_id,
479                                     GtkAccelGroup    *accel_group)
480 {
481   GtkWidget *image;
482   GtkStockItem stock_item;
483   GtkWidget *item;
484
485   g_return_val_if_fail (stock_id != NULL, NULL);
486
487   image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_MENU);
488
489   if (gtk_stock_lookup (stock_id, &stock_item))
490     {
491       item = gtk_image_menu_item_new_with_mnemonic (stock_item.label);
492
493       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
494       
495       if (stock_item.keyval && accel_group)
496         gtk_widget_add_accelerator (item,
497                                     "activate",
498                                     accel_group,
499                                     stock_item.keyval,
500                                     stock_item.modifier,
501                                     GTK_ACCEL_VISIBLE);
502     }
503   else
504     {
505       item = gtk_image_menu_item_new_with_mnemonic (stock_id);
506
507       gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
508     }
509
510   gtk_widget_show (image);
511   return item;
512 }
513
514 /** 
515  * gtk_image_menu_item_set_image:
516  * @image_menu_item: a #GtkImageMenuItem.
517  * @image: a widget to set as the image for the menu item.
518  * 
519  * Sets the image of @image_menu_item to the given widget.
520  * Note that it depends on the show-menu-images setting whether
521  * the image will be displayed or not.
522  **/ 
523 void
524 gtk_image_menu_item_set_image (GtkImageMenuItem *image_menu_item,
525                                GtkWidget        *image)
526 {
527   g_return_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item));
528
529   if (image == image_menu_item->image)
530     return;
531
532   if (image_menu_item->image)
533     gtk_container_remove (GTK_CONTAINER (image_menu_item),
534                           image_menu_item->image);
535
536   image_menu_item->image = image;
537
538   if (image == NULL)
539     return;
540
541   gtk_widget_set_parent (image, GTK_WIDGET (image_menu_item));
542   g_object_set (image, 
543                 "visible", show_image (image_menu_item),
544                 "no-show-all", TRUE,
545                 NULL);
546
547   g_object_notify (G_OBJECT (image_menu_item), "image");
548 }
549
550 /**
551  * gtk_image_menu_item_get_image:
552  * @image_menu_item: a #GtkImageMenuItem.
553  * @returns: the widget set as image of @image_menu_item.
554  *
555  * Gets the widget that is currently set as the image of @image_menu_item.
556  * See gtk_image_menu_item_set_image().
557  **/
558 GtkWidget*
559 gtk_image_menu_item_get_image (GtkImageMenuItem *image_menu_item)
560 {
561   g_return_val_if_fail (GTK_IS_IMAGE_MENU_ITEM (image_menu_item), NULL);
562
563   return image_menu_item->image;
564 }
565
566 static void
567 gtk_image_menu_item_remove (GtkContainer *container,
568                             GtkWidget    *child)
569 {
570   GtkImageMenuItem *image_menu_item;
571
572   image_menu_item = GTK_IMAGE_MENU_ITEM (container);
573
574   if (child == image_menu_item->image)
575     {
576       gboolean widget_was_visible;
577       
578       widget_was_visible = GTK_WIDGET_VISIBLE (child);
579       
580       gtk_widget_unparent (child);
581       image_menu_item->image = NULL;
582       
583       if (GTK_WIDGET_VISIBLE (container) && widget_was_visible)
584         gtk_widget_queue_resize (GTK_WIDGET (container));
585
586       g_object_notify (G_OBJECT (image_menu_item), "image");
587     }
588   else
589     {
590       (* GTK_CONTAINER_CLASS (parent_class)->remove) (container, child);
591     }
592 }
593
594 static void 
595 show_image_change_notify (GtkImageMenuItem *image_menu_item)
596 {
597   if (image_menu_item->image)
598     {
599       if (show_image (image_menu_item))
600         gtk_widget_show (image_menu_item->image);
601       else
602         gtk_widget_hide (image_menu_item->image);
603     }
604 }
605
606 static void
607 traverse_container (GtkWidget *widget,
608                     gpointer   data)
609 {
610   if (GTK_IS_IMAGE_MENU_ITEM (widget))
611     show_image_change_notify (GTK_IMAGE_MENU_ITEM (widget));
612   else if (GTK_IS_CONTAINER (widget))
613     gtk_container_forall (GTK_CONTAINER (widget), traverse_container, NULL);
614 }
615
616 static void
617 gtk_image_menu_item_setting_changed (GtkSettings *settings)
618 {
619   GList *list, *l;
620
621   list = gtk_window_list_toplevels ();
622
623   for (l = list; l; l = l->next)
624     gtk_container_forall (GTK_CONTAINER (l->data), 
625                           traverse_container, NULL);
626
627   g_list_free (list);  
628 }
629
630 static void
631 gtk_image_menu_item_screen_changed (GtkWidget *widget,
632                                     GdkScreen *previous_screen)
633 {
634   GtkSettings *settings;
635   guint show_image_connection;
636
637   if (!gtk_widget_has_screen (widget))
638     return;
639
640   settings = gtk_widget_get_settings (widget);
641   
642   show_image_connection = 
643     GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (settings), 
644                                          "gtk-image-menu-item-connection"));
645   
646   if (show_image_connection)
647     return;
648
649   show_image_connection =
650     g_signal_connect (settings, "notify::gtk-menu-images",
651                       G_CALLBACK (gtk_image_menu_item_setting_changed), 0);
652   g_object_set_data (G_OBJECT (settings), 
653                      g_intern_static_string ("gtk-image-menu-item-connection"),
654                      GUINT_TO_POINTER (show_image_connection));
655
656   show_image_change_notify (GTK_IMAGE_MENU_ITEM (widget));
657 }
658
659 #define __GTK_IMAGE_MENU_ITEM_C__
660 #include "gtkaliasdef.c"