1 /*
2  * Copyright © 2011, 2013 Canonical Limited
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 licence, 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, see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Ryan Lortie <desrt@desrt.ca>
18  */
19 
20 #include "config.h"
21 
22 #include "gtkmodelmenuitem.h"
23 
24 #include "gtkaccellabel.h"
25 #include "gtkcheckmenuitemprivate.h"
26 #include "gtkimage.h"
27 #include "gtkbox.h"
28 
29 struct _GtkModelMenuItem
30 {
31   GtkCheckMenuItem parent_instance;
32   GtkMenuTrackerItemRole role;
33   gboolean has_indicator;
34 };
35 
36 typedef GtkCheckMenuItemClass GtkModelMenuItemClass;
37 
38 G_DEFINE_TYPE (GtkModelMenuItem, gtk_model_menu_item, GTK_TYPE_CHECK_MENU_ITEM)
39 
40 enum
41 {
42   PROP_0,
43   PROP_ACTION_ROLE,
44   PROP_ICON,
45   PROP_TEXT,
46   PROP_TOGGLED,
47   PROP_ACCEL
48 };
49 
50 static void
gtk_model_menu_item_toggle_size_request(GtkMenuItem * menu_item,gint * requisition)51 gtk_model_menu_item_toggle_size_request (GtkMenuItem *menu_item,
52                                          gint        *requisition)
53 {
54   GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (menu_item);
55 
56   if (item->has_indicator)
57     GTK_MENU_ITEM_CLASS (gtk_model_menu_item_parent_class)
58       ->toggle_size_request (menu_item, requisition);
59 
60   else
61     *requisition = 0;
62 }
63 
64 static void
gtk_model_menu_item_activate(GtkMenuItem * item)65 gtk_model_menu_item_activate (GtkMenuItem *item)
66 {
67   /* block the automatic toggle behaviour -- just do nothing */
68 }
69 
70 static void
gtk_model_menu_item_draw_indicator(GtkCheckMenuItem * check_item,cairo_t * cr)71 gtk_model_menu_item_draw_indicator (GtkCheckMenuItem *check_item,
72                                     cairo_t          *cr)
73 {
74   GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (check_item);
75 
76   if (item->has_indicator)
77     GTK_CHECK_MENU_ITEM_CLASS (gtk_model_menu_item_parent_class)
78       ->draw_indicator (check_item, cr);
79 }
80 
81 static void
gtk_model_menu_item_set_has_indicator(GtkModelMenuItem * item,gboolean has_indicator)82 gtk_model_menu_item_set_has_indicator (GtkModelMenuItem *item,
83                                        gboolean          has_indicator)
84 {
85   if (has_indicator == item->has_indicator)
86     return;
87 
88   item->has_indicator = has_indicator;
89 
90   gtk_widget_queue_resize (GTK_WIDGET (item));
91 }
92 
93 static void
gtk_model_menu_item_set_action_role(GtkModelMenuItem * item,GtkMenuTrackerItemRole role)94 gtk_model_menu_item_set_action_role (GtkModelMenuItem       *item,
95                                      GtkMenuTrackerItemRole  role)
96 {
97   AtkObject *accessible;
98   AtkRole a11y_role;
99 
100   if (role == item->role)
101     return;
102 
103   gtk_check_menu_item_set_draw_as_radio (GTK_CHECK_MENU_ITEM (item), role == GTK_MENU_TRACKER_ITEM_ROLE_RADIO);
104   gtk_model_menu_item_set_has_indicator (item, role != GTK_MENU_TRACKER_ITEM_ROLE_NORMAL);
105 
106   accessible = gtk_widget_get_accessible (GTK_WIDGET (item));
107   switch (role)
108     {
109     case GTK_MENU_TRACKER_ITEM_ROLE_NORMAL:
110       a11y_role = ATK_ROLE_MENU_ITEM;
111       break;
112 
113     case GTK_MENU_TRACKER_ITEM_ROLE_CHECK:
114       a11y_role = ATK_ROLE_CHECK_MENU_ITEM;
115       break;
116 
117     case GTK_MENU_TRACKER_ITEM_ROLE_RADIO:
118       a11y_role = ATK_ROLE_RADIO_MENU_ITEM;
119       break;
120 
121     default:
122       g_assert_not_reached ();
123     }
124 
125   atk_object_set_role (accessible, a11y_role);
126   g_object_notify (G_OBJECT (item), "action-role");
127 }
128 
129 static void
gtk_model_menu_item_set_icon(GtkModelMenuItem * item,GIcon * icon)130 gtk_model_menu_item_set_icon (GtkModelMenuItem *item,
131                               GIcon            *icon)
132 {
133   GtkWidget *child;
134 
135   g_return_if_fail (GTK_IS_MODEL_MENU_ITEM (item));
136   g_return_if_fail (icon == NULL || G_IS_ICON (icon));
137 
138   child = gtk_bin_get_child (GTK_BIN (item));
139 
140   /* There are only three possibilities here:
141    *
142    *   - no child
143    *   - accel label child
144    *   - already a box
145    *
146    * Handle the no-child case by having GtkMenuItem create the accel
147    * label, then we will only have two possible cases.
148    */
149   if (child == NULL)
150     {
151       gtk_menu_item_get_label (GTK_MENU_ITEM (item));
152       child = gtk_bin_get_child (GTK_BIN (item));
153       g_assert (GTK_IS_LABEL (child));
154     }
155 
156   /* If it is a box, make sure there are no images inside of it already.
157    */
158   if (GTK_IS_BOX (child))
159     {
160       GList *children;
161 
162       children = gtk_container_get_children (GTK_CONTAINER (child));
163       while (children)
164         {
165           if (GTK_IS_IMAGE (children->data))
166             gtk_widget_destroy (children->data);
167 
168           children = g_list_delete_link (children, children);
169         }
170     }
171   else
172     {
173       GtkWidget *box;
174 
175       if (icon == NULL)
176         return;
177 
178       box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
179 
180       /* Reparent the child without destroying it */
181       g_object_ref (child);
182       gtk_container_remove (GTK_CONTAINER (item), child);
183       gtk_box_pack_end (GTK_BOX (box), child, TRUE, TRUE, 0);
184       g_object_unref (child);
185 
186       gtk_container_add (GTK_CONTAINER (item), box);
187       gtk_widget_show (box);
188 
189       /* Now we have a box */
190       child = box;
191     }
192 
193   g_assert (GTK_IS_BOX (child));
194 
195   /* child is now a box containing a label and no image.  Add the icon,
196    * if appropriate.
197    */
198   if (icon != NULL)
199     {
200       GtkWidget *image;
201 
202       image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_MENU);
203       gtk_image_set_pixel_size (GTK_IMAGE (image), 16);
204       gtk_box_pack_start (GTK_BOX (child), image, FALSE, FALSE, 0);
205       gtk_widget_show (image);
206     }
207 
208   g_object_notify (G_OBJECT (item), "icon");
209 }
210 
211 static GIcon *
gtk_model_menu_item_get_icon(GtkModelMenuItem * item)212 gtk_model_menu_item_get_icon (GtkModelMenuItem *item)
213 {
214   GtkWidget *child;
215   GIcon *icon = NULL;
216 
217   child = gtk_bin_get_child (GTK_BIN (item));
218   if (GTK_IS_BOX (child))
219     {
220       GList *children, *l;
221 
222       children = gtk_container_get_children (GTK_CONTAINER (child));
223       for (l = children; l; l = l->next)
224         {
225           if (GTK_IS_IMAGE (l->data))
226             {
227               gtk_image_get_gicon (GTK_IMAGE (l->data), &icon, NULL);
228               break;
229             }
230         }
231       g_list_free (children);
232     }
233 
234   return icon;
235 }
236 
237 static void
gtk_model_menu_item_set_text(GtkModelMenuItem * item,const gchar * text)238 gtk_model_menu_item_set_text (GtkModelMenuItem *item,
239                               const gchar      *text)
240 {
241   GtkWidget *child;
242   GList *children;
243 
244   child = gtk_bin_get_child (GTK_BIN (item));
245   if (child == NULL)
246     {
247       gtk_menu_item_get_label (GTK_MENU_ITEM (item));
248       child = gtk_bin_get_child (GTK_BIN (item));
249       g_assert (GTK_IS_LABEL (child));
250     }
251 
252   if (GTK_IS_LABEL (child))
253     {
254       gtk_label_set_text_with_mnemonic (GTK_LABEL (child), text);
255       return;
256     }
257 
258   if (!GTK_IS_CONTAINER (child))
259     return;
260 
261   children = gtk_container_get_children (GTK_CONTAINER (child));
262 
263   while (children)
264     {
265       if (GTK_IS_LABEL (children->data))
266         gtk_label_set_label (GTK_LABEL (children->data), text);
267 
268       children = g_list_delete_link (children, children);
269     }
270 
271   g_object_notify (G_OBJECT (item), "text");
272 }
273 
274 static const gchar *
gtk_model_menu_item_get_text(GtkModelMenuItem * item)275 gtk_model_menu_item_get_text (GtkModelMenuItem *item)
276 {
277   GtkWidget *child;
278 
279   child = gtk_bin_get_child (GTK_BIN (item));
280 
281   if (GTK_IS_LABEL (child))
282     return gtk_label_get_text (GTK_LABEL (child));
283 
284   if (GTK_IS_CONTAINER (child))
285     {
286       GList *children, *l;
287       const gchar *text = NULL;
288 
289       children = gtk_container_get_children (GTK_CONTAINER (child));
290       for (l = children; l; l = l->next)
291         {
292           if (GTK_IS_LABEL (l->data))
293             {
294               text = gtk_label_get_text (GTK_LABEL (l->data));
295               break;
296             }
297         }
298       g_list_free (children);
299 
300       return text;
301     }
302 
303   return NULL;
304 }
305 
306 static void
gtk_model_menu_item_set_accel(GtkModelMenuItem * item,const gchar * accel)307 gtk_model_menu_item_set_accel (GtkModelMenuItem *item,
308                                const gchar      *accel)
309 {
310   GtkWidget *child;
311   GList *children;
312   GdkModifierType modifiers;
313   guint key;
314 
315   if (accel)
316     {
317       gtk_accelerator_parse (accel, &key, &modifiers);
318       if (!key)
319         modifiers = 0;
320     }
321   else
322     {
323       key = 0;
324       modifiers = 0;
325     }
326 
327   child = gtk_bin_get_child (GTK_BIN (item));
328   if (child == NULL)
329     {
330       gtk_menu_item_get_label (GTK_MENU_ITEM (item));
331       child = gtk_bin_get_child (GTK_BIN (item));
332       g_assert (GTK_IS_LABEL (child));
333     }
334 
335   if (GTK_IS_ACCEL_LABEL (child))
336     {
337       gtk_accel_label_set_accel (GTK_ACCEL_LABEL (child), key, modifiers);
338       return;
339     }
340 
341   if (!GTK_IS_CONTAINER (child))
342     return;
343 
344   children = gtk_container_get_children (GTK_CONTAINER (child));
345 
346   while (children)
347     {
348       if (GTK_IS_ACCEL_LABEL (children->data))
349         gtk_accel_label_set_accel (children->data, key, modifiers);
350 
351       children = g_list_delete_link (children, children);
352     }
353 }
354 
355 static gchar *
gtk_model_menu_item_get_accel(GtkModelMenuItem * item)356 gtk_model_menu_item_get_accel (GtkModelMenuItem *item)
357 {
358   GtkWidget *child;
359   GtkWidget *accel_label = NULL;
360 
361   child = gtk_bin_get_child (GTK_BIN (item));
362 
363   if (GTK_IS_ACCEL_LABEL (child))
364     accel_label = child;
365   else if (GTK_IS_CONTAINER (child))
366     {
367       GList *children, *l;
368 
369       children = gtk_container_get_children (GTK_CONTAINER (child));
370       for (l = children; l; l = l->next)
371         {
372           if (GTK_IS_ACCEL_LABEL (l->data))
373             {
374               accel_label = GTK_WIDGET (l->data);
375               break;
376             }
377         }
378       g_list_free (children);
379     }
380 
381   if (accel_label)
382     {
383       guint key;
384       GdkModifierType mods;
385 
386       gtk_accel_label_get_accel (GTK_ACCEL_LABEL (accel_label), &key, &mods);
387 
388       return gtk_accelerator_name (key, mods);
389     }
390 
391   return NULL;
392 }
393 
394 static void
gtk_model_menu_item_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)395 gtk_model_menu_item_get_property (GObject    *object,
396                                   guint       prop_id,
397                                   GValue     *value,
398                                   GParamSpec *pspec)
399 {
400   GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object);
401 
402   switch (prop_id)
403     {
404     case PROP_ACTION_ROLE:
405       g_value_set_enum (value, item->role);
406       break;
407 
408     case PROP_ICON:
409       g_value_set_object (value, gtk_model_menu_item_get_icon (item));
410       break;
411 
412     case PROP_TEXT:
413       g_value_set_string (value, gtk_model_menu_item_get_text (item));
414       break;
415 
416     case PROP_TOGGLED:
417       g_value_set_boolean (value, gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)));
418       break;
419 
420     case PROP_ACCEL:
421       g_value_take_string (value, gtk_model_menu_item_get_accel (item));
422       break;
423 
424     default:
425       g_assert_not_reached ();
426     }
427 }
428 
429 static void
gtk_model_menu_item_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)430 gtk_model_menu_item_set_property (GObject      *object,
431                                   guint         prop_id,
432                                   const GValue *value,
433                                   GParamSpec   *pspec)
434 {
435   GtkModelMenuItem *item = GTK_MODEL_MENU_ITEM (object);
436 
437   switch (prop_id)
438     {
439     case PROP_ACTION_ROLE:
440       gtk_model_menu_item_set_action_role (item, g_value_get_enum (value));
441       break;
442 
443     case PROP_ICON:
444       gtk_model_menu_item_set_icon (item, g_value_get_object (value));
445       break;
446 
447     case PROP_TEXT:
448       gtk_model_menu_item_set_text (item, g_value_get_string (value));
449       break;
450 
451     case PROP_TOGGLED:
452       _gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), g_value_get_boolean (value));
453       g_object_notify (object, "active");
454       break;
455 
456     case PROP_ACCEL:
457       gtk_model_menu_item_set_accel (item, g_value_get_string (value));
458       break;
459 
460     default:
461       g_assert_not_reached ();
462     }
463 }
464 
465 static void
gtk_model_menu_item_init(GtkModelMenuItem * item)466 gtk_model_menu_item_init (GtkModelMenuItem *item)
467 {
468 }
469 
470 static void
gtk_model_menu_item_class_init(GtkModelMenuItemClass * class)471 gtk_model_menu_item_class_init (GtkModelMenuItemClass *class)
472 {
473   GtkCheckMenuItemClass *check_class = GTK_CHECK_MENU_ITEM_CLASS (class);
474   GtkMenuItemClass *item_class = GTK_MENU_ITEM_CLASS (class);
475   GObjectClass *object_class = G_OBJECT_CLASS (class);
476 
477   check_class->draw_indicator = gtk_model_menu_item_draw_indicator;
478 
479   item_class->toggle_size_request = gtk_model_menu_item_toggle_size_request;
480   item_class->activate = gtk_model_menu_item_activate;
481 
482   object_class->get_property = gtk_model_menu_item_get_property;
483   object_class->set_property = gtk_model_menu_item_set_property;
484 
485   g_object_class_install_property (object_class, PROP_ACTION_ROLE,
486                                    g_param_spec_enum ("action-role", "action role", "action role",
487                                                       GTK_TYPE_MENU_TRACKER_ITEM_ROLE,
488                                                       GTK_MENU_TRACKER_ITEM_ROLE_NORMAL,
489                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
490   g_object_class_install_property (object_class, PROP_ICON,
491                                    g_param_spec_object ("icon", "icon", "icon", G_TYPE_ICON,
492                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
493   g_object_class_install_property (object_class, PROP_TEXT,
494                                    g_param_spec_string ("text", "text", "text", NULL,
495                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
496   g_object_class_install_property (object_class, PROP_TOGGLED,
497                                    g_param_spec_boolean ("toggled", "toggled", "toggled", FALSE,
498                                                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
499   g_object_class_install_property (object_class, PROP_ACCEL,
500                                    g_param_spec_string ("accel", "accel", "accel", NULL,
501                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY));
502 
503   gtk_widget_class_set_accessible_role (GTK_WIDGET_CLASS (class), ATK_ROLE_MENU_ITEM);
504 }
505 
506 GtkWidget *
gtk_model_menu_item_new(void)507 gtk_model_menu_item_new (void)
508 {
509   return g_object_new (GTK_TYPE_MODEL_MENU_ITEM, NULL);
510 }
511