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