1 /*
2 * Copyright 2012 Canonical Ltd.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation, version 3 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Ryan Lortie <desrt@desrt.ca>
17 * William Hua <william.hua@canonical.com>
18 */
19
20 /**
21 * SECTION:unity-gtk-menu-shell
22 * @short_description: Menu shell proxy
23 * @include: unity-gtk-parser.h
24 *
25 * A #UnityGtkMenuShell is a #GMenuModel that acts as a proxy for a
26 * #GtkMenuShell. This can be used for purposes such as exporting menu
27 * shells over DBus with g_dbus_connection_export_menu_model ().
28 *
29 * #UnityGtkMenuShell<!-- -->s are most useful when used with
30 * #UnityGtkActionGroup<!-- -->s.
31 */
32
33 #include "unity-gtk-action-group-private.h"
34 #include "unity-gtk-menu-section-private.h"
35 #include "unity-gtk-menu-shell-private.h"
36
37 G_DEFINE_QUARK(menu_shell, menu_shell);
38
39 G_DEFINE_TYPE(UnityGtkMenuShell, unity_gtk_menu_shell, G_TYPE_MENU_MODEL);
40
41 static gboolean unity_gtk_menu_shell_debug;
42
g_uintcmp(gconstpointer a,gconstpointer b,gpointer user_data)43 static gint g_uintcmp(gconstpointer a, gconstpointer b, gpointer user_data)
44 {
45 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
46 }
47
g_sequence_get_uint(GSequenceIter * iter)48 static guint g_sequence_get_uint(GSequenceIter *iter)
49 {
50 return GPOINTER_TO_UINT(g_sequence_get(iter));
51 }
52
g_sequence_set_uint(GSequenceIter * iter,guint i)53 static void g_sequence_set_uint(GSequenceIter *iter, guint i)
54 {
55 g_sequence_set(iter, GUINT_TO_POINTER(i));
56 }
57
g_sequence_insert_sorted_uint(GSequence * sequence,guint i)58 static GSequenceIter *g_sequence_insert_sorted_uint(GSequence *sequence, guint i)
59 {
60 return g_sequence_insert_sorted(sequence, GUINT_TO_POINTER(i), g_uintcmp, NULL);
61 }
62
g_sequence_lookup_uint(GSequence * sequence,guint i)63 static GSequenceIter *g_sequence_lookup_uint(GSequence *sequence, guint i)
64 {
65 return g_sequence_lookup(sequence, GUINT_TO_POINTER(i), g_uintcmp, NULL);
66 }
67
g_sequence_search_uint(GSequence * sequence,guint i)68 static GSequenceIter *g_sequence_search_uint(GSequence *sequence, guint i)
69 {
70 return g_sequence_search(sequence, GUINT_TO_POINTER(i), g_uintcmp, NULL);
71 }
72
g_sequence_search_inf_uint(GSequence * sequence,guint i)73 static GSequenceIter *g_sequence_search_inf_uint(GSequence *sequence, guint i)
74 {
75 GSequenceIter *iter = g_sequence_iter_prev(g_sequence_search_uint(sequence, i));
76
77 return !g_sequence_iter_is_end(iter) && g_sequence_get_uint(iter) <= i ? iter : NULL;
78 }
79
gtk_menu_item_handle_idle_activate(gpointer user_data)80 static gboolean gtk_menu_item_handle_idle_activate(gpointer user_data)
81 {
82 g_return_val_if_fail(GTK_IS_MENU_ITEM(user_data), G_SOURCE_REMOVE);
83
84 gtk_menu_item_activate(user_data);
85
86 return G_SOURCE_REMOVE;
87 }
88
unity_gtk_menu_shell_get_items(UnityGtkMenuShell * shell)89 static GPtrArray *unity_gtk_menu_shell_get_items(UnityGtkMenuShell *shell)
90 {
91 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL);
92
93 if (shell->items == NULL)
94 {
95 GList *children;
96 GList *iter;
97 guint i;
98
99 g_return_val_if_fail(shell->menu_shell != NULL, NULL);
100
101 shell->items = g_ptr_array_new_with_free_func(g_object_unref);
102 children = gtk_container_get_children(GTK_CONTAINER(shell->menu_shell));
103
104 for (iter = children, i = 0; iter != NULL; i++)
105 {
106 g_ptr_array_add(shell->items,
107 unity_gtk_menu_item_new(iter->data, shell, i));
108 iter = g_list_next(iter);
109 }
110
111 g_list_free(children);
112 }
113
114 return shell->items;
115 }
116
unity_gtk_menu_shell_get_sections(UnityGtkMenuShell * shell)117 static GPtrArray *unity_gtk_menu_shell_get_sections(UnityGtkMenuShell *shell)
118 {
119 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL);
120
121 if (shell->sections == NULL)
122 {
123 GSequence *separator_indices = unity_gtk_menu_shell_get_separator_indices(shell);
124 guint n = g_sequence_get_length(separator_indices);
125 guint i;
126
127 shell->sections = g_ptr_array_new_full(n + 1, g_object_unref);
128
129 for (i = 0; i <= n; i++)
130 g_ptr_array_add(shell->sections, unity_gtk_menu_section_new(shell, i));
131 }
132
133 return shell->sections;
134 }
135
unity_gtk_menu_shell_show_item(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)136 static void unity_gtk_menu_shell_show_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item)
137 {
138 GSequence *visible_indices;
139
140 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
141 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
142 g_warn_if_fail(item->parent_shell == shell);
143
144 visible_indices = shell->visible_indices;
145
146 if (visible_indices != NULL)
147 {
148 GSequence *separator_indices = shell->separator_indices;
149 guint item_index = item->item_index;
150 GSequenceIter *insert_iter = g_sequence_lookup_uint(visible_indices, item_index);
151 gboolean already_visible = insert_iter != NULL;
152
153 if (!already_visible)
154 insert_iter = g_sequence_insert_sorted_uint(visible_indices, item_index);
155 else
156 g_warn_if_reached();
157
158 if (shell->action_group != NULL)
159 {
160 unity_gtk_action_group_connect_item(shell->action_group, item);
161
162 if (item->child_shell != NULL)
163 {
164 if (item->child_shell_valid)
165 unity_gtk_action_group_connect_shell(shell->action_group,
166 item->child_shell);
167 else
168 g_warn_if_reached();
169 }
170 }
171
172 if (separator_indices != NULL)
173 {
174 GPtrArray *sections = shell->sections;
175 GSequenceIter *separator_iter =
176 g_sequence_search_inf_uint(separator_indices, item_index);
177 guint section_index =
178 separator_iter == NULL
179 ? 0
180 : g_sequence_iter_get_position(separator_iter) + 1;
181 gboolean separator_already_visible =
182 separator_iter != NULL &&
183 g_sequence_get_uint(separator_iter) == item_index;
184
185 if (!separator_already_visible)
186 {
187 if (unity_gtk_menu_item_is_separator(item))
188 {
189 g_sequence_insert_sorted_uint(separator_indices,
190 item_index);
191
192 if (sections != NULL)
193 {
194 UnityGtkMenuSection *section =
195 g_ptr_array_index(sections, section_index);
196 GSequenceIter *section_iter =
197 unity_gtk_menu_section_get_begin_iter(section);
198 guint position =
199 g_sequence_iter_get_position(insert_iter) -
200 g_sequence_iter_get_position(section_iter);
201 UnityGtkMenuSection *new_section =
202 unity_gtk_menu_section_new(shell,
203 section_index + 1);
204 guint removed = g_menu_model_get_n_items(
205 G_MENU_MODEL(new_section));
206 guint i;
207
208 g_ptr_array_insert(sections,
209 section_index + 1,
210 new_section);
211
212 for (i = section_index + 2; i < sections->len; i++)
213 UNITY_GTK_MENU_SECTION(
214 g_ptr_array_index(sections, i))
215 ->section_index = i;
216
217 if (removed)
218 g_menu_model_items_changed(G_MENU_MODEL(
219 section),
220 position,
221 removed,
222 0);
223
224 g_menu_model_items_changed(G_MENU_MODEL(shell),
225 section_index + 1,
226 0,
227 1);
228 }
229 }
230 else
231 {
232 if (sections != NULL)
233 {
234 UnityGtkMenuSection *section =
235 g_ptr_array_index(sections, section_index);
236 GSequenceIter *section_iter =
237 unity_gtk_menu_section_get_begin_iter(section);
238 guint position =
239 g_sequence_iter_get_position(insert_iter) -
240 g_sequence_iter_get_position(section_iter);
241
242 g_menu_model_items_changed(G_MENU_MODEL(section),
243 position,
244 0,
245 1);
246 }
247 }
248 }
249 else
250 g_warn_if_reached();
251 }
252 }
253 }
254
unity_gtk_menu_shell_hide_item(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)255 static void unity_gtk_menu_shell_hide_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item)
256 {
257 GSequence *visible_indices;
258
259 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
260 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
261 g_warn_if_fail(item->parent_shell == shell);
262
263 visible_indices = shell->visible_indices;
264
265 if (visible_indices != NULL)
266 {
267 GSequence *separator_indices = shell->separator_indices;
268 guint item_index = item->item_index;
269 GSequenceIter *visible_iter = g_sequence_lookup_uint(visible_indices, item_index);
270
271 if (shell->action_group != NULL)
272 {
273 if (item->child_shell != NULL)
274 {
275 if (item->child_shell_valid)
276 unity_gtk_action_group_disconnect_shell(shell->action_group,
277 item->child_shell);
278 else
279 g_warn_if_reached();
280 }
281
282 unity_gtk_action_group_disconnect_item(shell->action_group, item);
283 }
284
285 if (separator_indices != NULL)
286 {
287 if (unity_gtk_menu_item_is_separator(item))
288 {
289 GSequenceIter *separator_iter =
290 g_sequence_lookup_uint(separator_indices, item_index);
291
292 if (separator_iter != NULL)
293 {
294 GPtrArray *sections = shell->sections;
295 guint section_index =
296 g_sequence_iter_get_position(separator_iter);
297
298 if (shell->sections != NULL)
299 {
300 UnityGtkMenuSection *section =
301 g_ptr_array_index(sections, section_index);
302 UnityGtkMenuSection *next_section =
303 g_ptr_array_index(sections, section_index + 1);
304 guint position =
305 g_menu_model_get_n_items(G_MENU_MODEL(section));
306 guint added = g_menu_model_get_n_items(
307 G_MENU_MODEL(next_section));
308 guint i;
309
310 g_sequence_remove(separator_iter);
311
312 if (visible_iter != NULL)
313 g_sequence_remove(visible_iter);
314 else
315 g_warn_if_reached();
316
317 g_menu_model_items_changed(G_MENU_MODEL(shell),
318 section_index + 1,
319 1,
320 0);
321
322 if (added)
323 g_menu_model_items_changed(G_MENU_MODEL(
324 section),
325 position,
326 0,
327 added);
328
329 g_ptr_array_remove_index(sections,
330 section_index + 1);
331
332 for (i = section_index + 1; i < sections->len; i++)
333 UNITY_GTK_MENU_SECTION(
334 g_ptr_array_index(sections, i))
335 ->section_index = i;
336 }
337 else
338 {
339 g_sequence_remove(separator_iter);
340
341 if (visible_iter != NULL)
342 g_sequence_remove(visible_iter);
343 else
344 g_warn_if_reached();
345 }
346 }
347 else
348 {
349 g_warn_if_reached();
350
351 if (visible_iter != NULL)
352 g_sequence_remove(visible_iter);
353 else
354 g_warn_if_reached();
355 }
356 }
357 else
358 {
359 if (visible_iter != NULL)
360 {
361 GPtrArray *sections = shell->sections;
362 GSequenceIter *separator_iter =
363 g_sequence_search_inf_uint(separator_indices,
364 item_index);
365 guint section_index =
366 separator_iter == NULL
367 ? 0
368 : g_sequence_iter_get_position(separator_iter) + 1;
369
370 if (shell->sections != NULL)
371 {
372 UnityGtkMenuSection *section =
373 g_ptr_array_index(sections, section_index);
374 GSequenceIter *section_iter =
375 unity_gtk_menu_section_get_begin_iter(section);
376 guint position =
377 g_sequence_iter_get_position(visible_iter) -
378 g_sequence_iter_get_position(section_iter);
379
380 g_sequence_remove(visible_iter);
381 g_menu_model_items_changed(G_MENU_MODEL(section),
382 position,
383 1,
384 0);
385 }
386 }
387 else
388 g_warn_if_reached();
389 }
390 }
391 else
392 {
393 if (visible_iter != NULL)
394 g_sequence_remove(visible_iter);
395 else
396 g_warn_if_reached();
397 }
398 }
399 }
400
unity_gtk_menu_shell_update_item(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)401 static void unity_gtk_menu_shell_update_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item)
402 {
403 GSequence *visible_indices;
404 GSequenceIter *visible_iter;
405
406 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
407 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
408 g_warn_if_fail(item->parent_shell == shell);
409
410 visible_indices = unity_gtk_menu_shell_get_visible_indices(shell);
411 visible_iter = g_sequence_lookup_uint(visible_indices, item->item_index);
412
413 if (visible_iter != NULL)
414 {
415 GSequence *separator_indices;
416 GSequenceIter *separator_iter;
417 guint section_index;
418 GPtrArray *sections;
419 UnityGtkMenuSection *section;
420 GSequenceIter *section_iter;
421 guint position;
422
423 separator_indices = unity_gtk_menu_shell_get_separator_indices(shell);
424 separator_iter = g_sequence_search_inf_uint(separator_indices, item->item_index);
425 section_index =
426 separator_iter == NULL ? 0 : g_sequence_iter_get_position(separator_iter) + 1;
427 sections = unity_gtk_menu_shell_get_sections(shell);
428 section = g_ptr_array_index(sections, section_index);
429 section_iter = unity_gtk_menu_section_get_begin_iter(section);
430 position = g_sequence_iter_get_position(visible_iter) -
431 g_sequence_iter_get_position(section_iter);
432
433 g_menu_model_items_changed(G_MENU_MODEL(section), position, 1, 1);
434 }
435 }
436
unity_gtk_menu_shell_handle_item_visible(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)437 static void unity_gtk_menu_shell_handle_item_visible(UnityGtkMenuShell *shell,
438 UnityGtkMenuItem *item)
439 {
440 GSequence *visible_indices;
441
442 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
443 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
444 g_warn_if_fail(item->parent_shell == shell);
445
446 visible_indices = shell->visible_indices;
447
448 if (visible_indices != NULL)
449 {
450 GSequenceIter *visible_iter =
451 g_sequence_lookup_uint(visible_indices, item->item_index);
452 gboolean was_visible = visible_iter != NULL;
453 gboolean is_visible = unity_gtk_menu_item_is_visible(item);
454
455 if (!was_visible && is_visible)
456 unity_gtk_menu_shell_show_item(shell, item);
457 else if (was_visible && !is_visible)
458 unity_gtk_menu_shell_hide_item(shell, item);
459 }
460 }
461
unity_gtk_menu_shell_handle_item_sensitive(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)462 static void unity_gtk_menu_shell_handle_item_sensitive(UnityGtkMenuShell *shell,
463 UnityGtkMenuItem *item)
464 {
465 GActionGroup *action_group;
466 UnityGtkAction *action;
467
468 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
469 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
470 g_warn_if_fail(item->parent_shell == shell);
471
472 action_group = G_ACTION_GROUP(shell->action_group);
473 action = item->action;
474
475 if (action_group != NULL && action != NULL)
476 {
477 gboolean enabled = unity_gtk_menu_item_is_sensitive(item);
478
479 g_action_group_action_enabled_changed(action_group, action->name, enabled);
480 }
481 }
482
unity_gtk_menu_shell_handle_item_label(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)483 static void unity_gtk_menu_shell_handle_item_label(UnityGtkMenuShell *shell, UnityGtkMenuItem *item)
484 {
485 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
486 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
487 g_warn_if_fail(item->parent_shell == shell);
488
489 g_free(item->label_label);
490 item->label_label = NULL;
491
492 unity_gtk_menu_shell_update_item(shell, item);
493 }
494
unity_gtk_menu_shell_handle_item_use_underline(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)495 static void unity_gtk_menu_shell_handle_item_use_underline(UnityGtkMenuShell *shell,
496 UnityGtkMenuItem *item)
497 {
498 unity_gtk_menu_shell_handle_item_label(shell, item);
499 }
500
unity_gtk_menu_shell_handle_item_accel_path(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)501 static void unity_gtk_menu_shell_handle_item_accel_path(UnityGtkMenuShell *shell,
502 UnityGtkMenuItem *item)
503 {
504 unity_gtk_menu_shell_update_item(shell, item);
505 }
506
unity_gtk_menu_shell_handle_item_active(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)507 static void unity_gtk_menu_shell_handle_item_active(UnityGtkMenuShell *shell,
508 UnityGtkMenuItem *item)
509 {
510 GActionGroup *action_group;
511 UnityGtkAction *action;
512
513 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
514 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
515 g_warn_if_fail(item->parent_shell == shell);
516
517 action_group = G_ACTION_GROUP(shell->action_group);
518 action = item->action;
519
520 if (action_group != NULL && action != NULL)
521 {
522 if (action->items_by_name != NULL)
523 {
524 const char *name = NULL;
525 GHashTableIter iter;
526 gpointer key;
527 gpointer value;
528
529 g_hash_table_iter_init(&iter, action->items_by_name);
530 while (name == NULL && g_hash_table_iter_next(&iter, &key, &value))
531 if (unity_gtk_menu_item_is_active(value))
532 name = key;
533
534 if (name != NULL)
535 {
536 GVariant *state = g_variant_new_string(name);
537
538 g_action_group_action_state_changed(action_group,
539 action->name,
540 state);
541 }
542 else
543 g_action_group_action_state_changed(action_group,
544 action->name,
545 NULL);
546 }
547 else if (unity_gtk_menu_item_is_check(item))
548 {
549 gboolean active = unity_gtk_menu_item_is_active(item);
550 GVariant *state = g_variant_new_boolean(active);
551
552 g_action_group_action_state_changed(action_group, action->name, state);
553 }
554 }
555 }
556
unity_gtk_menu_shell_handle_item_parent(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)557 static void unity_gtk_menu_shell_handle_item_parent(UnityGtkMenuShell *shell,
558 UnityGtkMenuItem *item)
559 {
560 GtkMenuItem *menu_item;
561 GtkWidget *parent;
562
563 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
564 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
565 g_warn_if_fail(item->parent_shell == shell);
566
567 menu_item = item->menu_item;
568 parent = gtk_widget_get_parent(GTK_WIDGET(menu_item));
569
570 if (parent == NULL)
571 {
572 GPtrArray *items = shell->items;
573
574 if (unity_gtk_menu_item_is_visible(item))
575 unity_gtk_menu_shell_hide_item(shell, item);
576
577 if (items != NULL)
578 {
579 GSequence *visible_indices = shell->visible_indices;
580 GSequence *separator_indices = shell->separator_indices;
581 guint item_index = item->item_index;
582 guint i;
583
584 g_ptr_array_remove_index(items, item_index);
585
586 for (i = item_index; i < items->len; i++)
587 UNITY_GTK_MENU_ITEM(g_ptr_array_index(items, i))->item_index = i;
588
589 if (visible_indices != NULL)
590 {
591 GSequenceIter *iter =
592 g_sequence_search_uint(visible_indices, item_index);
593
594 while (!g_sequence_iter_is_end(iter))
595 {
596 g_sequence_set_uint(iter, g_sequence_get_uint(iter) - 1);
597 iter = g_sequence_iter_next(iter);
598 }
599 }
600
601 if (separator_indices != NULL)
602 {
603 GSequenceIter *iter =
604 g_sequence_search_uint(separator_indices, item_index);
605
606 while (!g_sequence_iter_is_end(iter))
607 {
608 g_sequence_set_uint(iter, g_sequence_get_uint(iter) - 1);
609 iter = g_sequence_iter_next(iter);
610 }
611 }
612 }
613 }
614 }
615
unity_gtk_menu_shell_handle_item_submenu(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)616 static void unity_gtk_menu_shell_handle_item_submenu(UnityGtkMenuShell *shell,
617 UnityGtkMenuItem *item)
618 {
619 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
620 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
621 g_warn_if_fail(item->parent_shell == shell);
622
623 if (shell->action_group != NULL)
624 {
625 /* If a submenu was added or removed, we need to update the submenu action. */
626 unity_gtk_action_group_disconnect_item(shell->action_group, item);
627 unity_gtk_action_group_connect_item(shell->action_group, item);
628 }
629
630 if (item->child_shell_valid)
631 {
632 GtkMenuShell *old_submenu =
633 item->child_shell != NULL ? item->child_shell->menu_shell : NULL;
634 GtkMenuShell *new_submenu =
635 item->menu_item != NULL
636 ? GTK_MENU_SHELL(gtk_menu_item_get_submenu(item->menu_item))
637 : NULL;
638
639 if (new_submenu != old_submenu)
640 {
641 UnityGtkMenuShell *child_shell = item->child_shell;
642 GSequence *visible_indices =
643 unity_gtk_menu_shell_get_visible_indices(shell);
644 GSequence *separator_indices =
645 unity_gtk_menu_shell_get_separator_indices(shell);
646 GSequenceIter *separator_iter =
647 g_sequence_search_inf_uint(separator_indices, item->item_index);
648 guint section_index =
649 separator_iter == NULL
650 ? 0
651 : g_sequence_iter_get_position(separator_iter) + 1;
652 GPtrArray *sections = unity_gtk_menu_shell_get_sections(shell);
653 UnityGtkMenuSection *section = g_ptr_array_index(sections, section_index);
654 GSequenceIter *section_iter =
655 unity_gtk_menu_section_get_begin_iter(section);
656 GSequenceIter *visible_iter =
657 g_sequence_lookup_uint(visible_indices, item->item_index);
658 guint position = g_sequence_iter_get_position(visible_iter) -
659 g_sequence_iter_get_position(section_iter);
660
661 if (child_shell != NULL)
662 {
663 item->child_shell = NULL;
664 g_object_unref(child_shell);
665 }
666
667 item->child_shell_valid = FALSE;
668
669 g_menu_model_items_changed(G_MENU_MODEL(section), position, 1, 1);
670 }
671 }
672 }
673
unity_gtk_menu_shell_handle_shell_insert(GtkMenuShell * menu_shell,GtkWidget * child,gint position,gpointer user_data)674 static void unity_gtk_menu_shell_handle_shell_insert(GtkMenuShell *menu_shell, GtkWidget *child,
675 gint position, gpointer user_data)
676 {
677 UnityGtkMenuShell *shell;
678 GPtrArray *items;
679
680 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(user_data));
681
682 if (unity_gtk_menu_shell_is_debug())
683 g_print("%s ((%s *) %p, (%s *) %p \"%s\", %d, (%s *) %p)\n",
684 G_STRFUNC,
685 G_OBJECT_TYPE_NAME(menu_shell),
686 menu_shell,
687 G_OBJECT_TYPE_NAME(child),
688 child,
689 gtk_menu_item_get_label(GTK_MENU_ITEM(child)),
690 position,
691 G_OBJECT_TYPE_NAME(user_data),
692 user_data);
693
694 shell = UNITY_GTK_MENU_SHELL(user_data);
695 items = shell->items;
696
697 if (items != NULL)
698 {
699 UnityGtkMenuItem *item;
700 GtkMenuItem *menu_item;
701 GSequence *visible_indices;
702 GSequence *separator_indices;
703 guint i;
704
705 if (position < 0)
706 position = items->len;
707
708 menu_item = GTK_MENU_ITEM(child);
709 item = unity_gtk_menu_item_new(menu_item, shell, position);
710 g_ptr_array_insert(items, position, item);
711
712 for (i = position + 1; i < items->len; i++)
713 UNITY_GTK_MENU_ITEM(g_ptr_array_index(items, i))->item_index = i;
714
715 visible_indices = shell->visible_indices;
716 separator_indices = shell->separator_indices;
717
718 if (visible_indices != NULL)
719 {
720 GSequenceIter *iter = g_sequence_search_uint(visible_indices, position - 1);
721
722 while (!g_sequence_iter_is_end(iter))
723 {
724 g_sequence_set_uint(iter, g_sequence_get_uint(iter) + 1);
725 iter = g_sequence_iter_next(iter);
726 }
727 }
728
729 if (separator_indices != NULL)
730 {
731 GSequenceIter *iter =
732 g_sequence_search_uint(separator_indices, position - 1);
733
734 while (!g_sequence_iter_is_end(iter))
735 {
736 g_sequence_set_uint(iter, g_sequence_get_uint(iter) + 1);
737 iter = g_sequence_iter_next(iter);
738 }
739 }
740
741 if (unity_gtk_menu_item_is_visible(item))
742 unity_gtk_menu_shell_show_item(shell, item);
743 }
744 }
745
unity_gtk_menu_shell_set_has_mnemonics(UnityGtkMenuShell * shell,gboolean has_mnemonics)746 static void unity_gtk_menu_shell_set_has_mnemonics(UnityGtkMenuShell *shell, gboolean has_mnemonics)
747 {
748 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
749
750 if (has_mnemonics != shell->has_mnemonics)
751 {
752 shell->has_mnemonics = has_mnemonics;
753
754 if (shell->items != NULL)
755 {
756 guint i;
757
758 for (i = 0; i < shell->items->len; i++)
759 unity_gtk_menu_shell_handle_item_label(
760 shell, g_ptr_array_index(shell->items, i));
761 }
762 }
763 }
764
unity_gtk_menu_shell_handle_settings_notify(GObject * object,GParamSpec * pspec,gpointer user_data)765 static void unity_gtk_menu_shell_handle_settings_notify(GObject *object, GParamSpec *pspec,
766 gpointer user_data)
767 {
768 gboolean has_mnemonics;
769
770 g_return_if_fail(GTK_IS_SETTINGS(object));
771 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(user_data));
772
773 g_object_get(GTK_SETTINGS(object), "gtk-enable-mnemonics", &has_mnemonics, NULL);
774
775 unity_gtk_menu_shell_set_has_mnemonics(UNITY_GTK_MENU_SHELL(user_data), has_mnemonics);
776 }
777
778 static void unity_gtk_menu_shell_clear_menu_shell(UnityGtkMenuShell *shell);
779
unity_gtk_menu_shell_set_menu_shell(UnityGtkMenuShell * shell,GtkMenuShell * menu_shell)780 static void unity_gtk_menu_shell_set_menu_shell(UnityGtkMenuShell *shell, GtkMenuShell *menu_shell)
781 {
782 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
783
784 if (menu_shell != shell->menu_shell)
785 {
786 GPtrArray *items = shell->items;
787 GPtrArray *sections = shell->sections;
788 GSequence *visible_indices = shell->visible_indices;
789 GSequence *separator_indices = shell->separator_indices;
790
791 if (shell->action_group != NULL)
792 unity_gtk_action_group_disconnect_shell(shell->action_group, shell);
793
794 if (shell->menu_shell != NULL)
795 g_signal_handlers_disconnect_by_data(shell->menu_shell, shell);
796
797 if (separator_indices != NULL)
798 {
799 shell->separator_indices = NULL;
800 g_sequence_free(separator_indices);
801 }
802
803 if (visible_indices != NULL)
804 {
805 shell->visible_indices = NULL;
806 g_sequence_free(visible_indices);
807 }
808
809 if (sections != NULL)
810 {
811 shell->sections = NULL;
812 g_ptr_array_unref(sections);
813 }
814
815 if (items != NULL)
816 {
817 shell->items = NULL;
818 g_ptr_array_unref(items);
819 }
820
821 if (shell->menu_shell != NULL)
822 g_object_steal_qdata(G_OBJECT(shell->menu_shell), menu_shell_quark());
823
824 shell->menu_shell = menu_shell;
825
826 if (menu_shell != NULL)
827 {
828 g_object_set_qdata_full(G_OBJECT(menu_shell),
829 menu_shell_quark(),
830 shell,
831 (GDestroyNotify)
832 unity_gtk_menu_shell_clear_menu_shell);
833
834 g_signal_connect(menu_shell,
835 "insert",
836 G_CALLBACK(unity_gtk_menu_shell_handle_shell_insert),
837 shell);
838 }
839 }
840 }
841
unity_gtk_menu_shell_clear_menu_shell(UnityGtkMenuShell * shell)842 static void unity_gtk_menu_shell_clear_menu_shell(UnityGtkMenuShell *shell)
843 {
844 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
845
846 unity_gtk_menu_shell_set_menu_shell(shell, NULL);
847 }
848
unity_gtk_menu_shell_dispose(GObject * object)849 static void unity_gtk_menu_shell_dispose(GObject *object)
850 {
851 UnityGtkMenuShell *shell;
852 GtkSettings *settings;
853
854 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(object));
855
856 shell = UNITY_GTK_MENU_SHELL(object);
857 settings = gtk_settings_get_default();
858
859 unity_gtk_menu_shell_set_menu_shell(shell, NULL);
860
861 if (settings != NULL)
862 g_signal_handlers_disconnect_by_data(settings, shell);
863
864 G_OBJECT_CLASS(unity_gtk_menu_shell_parent_class)->dispose(object);
865 }
866
unity_gtk_menu_shell_is_mutable(GMenuModel * model)867 static gboolean unity_gtk_menu_shell_is_mutable(GMenuModel *model)
868 {
869 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(model), TRUE);
870
871 return TRUE;
872 }
873
unity_gtk_menu_shell_get_n_items(GMenuModel * model)874 static gint unity_gtk_menu_shell_get_n_items(GMenuModel *model)
875 {
876 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(model), 0);
877
878 return unity_gtk_menu_shell_get_sections(UNITY_GTK_MENU_SHELL(model))->len;
879 }
880
unity_gtk_menu_shell_get_item_attributes(GMenuModel * model,gint item_index,GHashTable ** attributes)881 static void unity_gtk_menu_shell_get_item_attributes(GMenuModel *model, gint item_index,
882 GHashTable **attributes)
883 {
884 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(model));
885 g_return_if_fail(0 <= item_index && item_index < g_menu_model_get_n_items(model));
886 g_return_if_fail(attributes != NULL);
887
888 *attributes =
889 g_hash_table_new_full(g_str_hash, g_str_equal, NULL, (GDestroyNotify)g_variant_unref);
890 }
891
unity_gtk_menu_shell_get_item_links(GMenuModel * model,gint item_index,GHashTable ** links)892 static void unity_gtk_menu_shell_get_item_links(GMenuModel *model, gint item_index,
893 GHashTable **links)
894 {
895 UnityGtkMenuShell *shell;
896 GPtrArray *sections;
897 UnityGtkMenuSection *section;
898
899 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(model));
900 g_return_if_fail(0 <= item_index && item_index < g_menu_model_get_n_items(model));
901 g_return_if_fail(links != NULL);
902
903 shell = UNITY_GTK_MENU_SHELL(model);
904 sections = unity_gtk_menu_shell_get_sections(shell);
905 section = g_ptr_array_index(sections, item_index);
906
907 *links = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref);
908 g_hash_table_insert(*links, G_MENU_LINK_SECTION, g_object_ref(section));
909 }
910
unity_gtk_menu_shell_class_init(UnityGtkMenuShellClass * klass)911 static void unity_gtk_menu_shell_class_init(UnityGtkMenuShellClass *klass)
912 {
913 GObjectClass *object_class = G_OBJECT_CLASS(klass);
914 GMenuModelClass *menu_model_class = G_MENU_MODEL_CLASS(klass);
915
916 object_class->dispose = unity_gtk_menu_shell_dispose;
917 menu_model_class->is_mutable = unity_gtk_menu_shell_is_mutable;
918 menu_model_class->get_n_items = unity_gtk_menu_shell_get_n_items;
919 menu_model_class->get_item_attributes = unity_gtk_menu_shell_get_item_attributes;
920 menu_model_class->get_item_links = unity_gtk_menu_shell_get_item_links;
921 }
922
unity_gtk_menu_shell_init(UnityGtkMenuShell * self)923 static void unity_gtk_menu_shell_init(UnityGtkMenuShell *self)
924 {
925 self->has_mnemonics = TRUE;
926 }
927
928 /**
929 * unity_gtk_menu_shell_new:
930 * @menu_shell: a #GtkMenuShell to watch.
931 *
932 * Creates a new #UnityGtkMenuShell based on the contents of the given
933 * @menu_shell. Any subsequent changes to @menu_shell are reflected in
934 * the returned #UnityGtkMenuShell.
935 *
936 * Returns: a new #UnityGtkMenuShell based on @menu_shell.
937 */
unity_gtk_menu_shell_new(GtkMenuShell * menu_shell)938 UnityGtkMenuShell *unity_gtk_menu_shell_new(GtkMenuShell *menu_shell)
939 {
940 UnityGtkMenuShell *shell = g_object_new(UNITY_GTK_TYPE_MENU_SHELL, NULL);
941 GtkSettings *settings = gtk_settings_get_default();
942
943 if (settings != NULL)
944 {
945 g_signal_connect(settings,
946 "notify::gtk-enable-mnemonics",
947 G_CALLBACK(unity_gtk_menu_shell_handle_settings_notify),
948 shell);
949 g_object_get(settings, "gtk-enable-mnemonics", &shell->has_mnemonics, NULL);
950 }
951
952 unity_gtk_menu_shell_set_menu_shell(shell, menu_shell);
953
954 return shell;
955 }
956
unity_gtk_menu_shell_new_internal(GtkMenuShell * menu_shell)957 UnityGtkMenuShell *unity_gtk_menu_shell_new_internal(GtkMenuShell *menu_shell)
958 {
959 UnityGtkMenuShell *shell = g_object_new(UNITY_GTK_TYPE_MENU_SHELL, NULL);
960
961 unity_gtk_menu_shell_set_menu_shell(shell, menu_shell);
962
963 return shell;
964 }
965
unity_gtk_menu_shell_get_item(UnityGtkMenuShell * shell,guint index)966 UnityGtkMenuItem *unity_gtk_menu_shell_get_item(UnityGtkMenuShell *shell, guint index)
967 {
968 GPtrArray *items;
969
970 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL);
971
972 items = unity_gtk_menu_shell_get_items(shell);
973
974 g_return_val_if_fail(index < items->len, NULL);
975
976 return g_ptr_array_index(items, index);
977 }
978
unity_gtk_menu_shell_get_visible_indices(UnityGtkMenuShell * shell)979 GSequence *unity_gtk_menu_shell_get_visible_indices(UnityGtkMenuShell *shell)
980 {
981 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL);
982
983 if (shell->visible_indices == NULL)
984 {
985 GPtrArray *items = unity_gtk_menu_shell_get_items(shell);
986 guint i;
987
988 shell->visible_indices = g_sequence_new(NULL);
989
990 for (i = 0; i < items->len; i++)
991 {
992 UnityGtkMenuItem *item = g_ptr_array_index(items, i);
993
994 if (unity_gtk_menu_item_is_visible(item))
995 g_sequence_append(shell->visible_indices, GUINT_TO_POINTER(i));
996 }
997
998 if (shell->action_group != NULL)
999 unity_gtk_action_group_connect_shell(shell->action_group, shell);
1000 }
1001
1002 return shell->visible_indices;
1003 }
1004
unity_gtk_menu_shell_get_separator_indices(UnityGtkMenuShell * shell)1005 GSequence *unity_gtk_menu_shell_get_separator_indices(UnityGtkMenuShell *shell)
1006 {
1007 g_return_val_if_fail(UNITY_GTK_IS_MENU_SHELL(shell), NULL);
1008
1009 unity_gtk_menu_shell_get_visible_indices(shell);
1010
1011 if (shell->separator_indices == NULL)
1012 {
1013 GPtrArray *items = unity_gtk_menu_shell_get_items(shell);
1014 guint i;
1015
1016 shell->separator_indices = g_sequence_new(NULL);
1017
1018 for (i = 0; i < items->len; i++)
1019 {
1020 UnityGtkMenuItem *item = g_ptr_array_index(items, i);
1021
1022 if (unity_gtk_menu_item_is_visible(item) &&
1023 unity_gtk_menu_item_is_separator(item))
1024 g_sequence_append(shell->separator_indices, GUINT_TO_POINTER(i));
1025 }
1026 }
1027
1028 return shell->separator_indices;
1029 }
1030
unity_gtk_menu_shell_handle_item_notify(UnityGtkMenuShell * shell,UnityGtkMenuItem * item,const char * property)1031 void unity_gtk_menu_shell_handle_item_notify(UnityGtkMenuShell *shell, UnityGtkMenuItem *item,
1032 const char *property)
1033 {
1034 static const char *visible_name;
1035 static const char *sensitive_name;
1036 static const char *label_name;
1037 static const char *use_underline_name;
1038 static const char *accel_path_name;
1039 static const char *active_name;
1040 static const char *parent_name;
1041 static const char *submenu_name;
1042
1043 const char *name;
1044
1045 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
1046 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
1047
1048 if (G_UNLIKELY(visible_name == NULL))
1049 visible_name = g_intern_static_string("visible");
1050 if (G_UNLIKELY(sensitive_name == NULL))
1051 sensitive_name = g_intern_static_string("sensitive");
1052 if (G_UNLIKELY(label_name == NULL))
1053 label_name = g_intern_static_string("label");
1054 if (G_UNLIKELY(use_underline_name == NULL))
1055 use_underline_name = g_intern_static_string("use-underline");
1056 if (G_UNLIKELY(accel_path_name == NULL))
1057 accel_path_name = g_intern_static_string("accel-path");
1058 if (G_UNLIKELY(active_name == NULL))
1059 active_name = g_intern_static_string("active");
1060 if (G_UNLIKELY(parent_name == NULL))
1061 parent_name = g_intern_static_string("parent");
1062 if (G_UNLIKELY(submenu_name == NULL))
1063 submenu_name = g_intern_static_string("submenu");
1064
1065 name = g_intern_string(property);
1066
1067 if (unity_gtk_menu_shell_is_debug())
1068 g_print("%s ((%s *) %p, (%s *) %p { \"%s\" }, %s)\n",
1069 G_STRFUNC,
1070 G_OBJECT_TYPE_NAME(shell),
1071 shell,
1072 G_OBJECT_TYPE_NAME(item),
1073 item,
1074 unity_gtk_menu_item_get_label(item),
1075 name);
1076
1077 if (name == visible_name)
1078 unity_gtk_menu_shell_handle_item_visible(shell, item);
1079 else if (name == sensitive_name)
1080 unity_gtk_menu_shell_handle_item_sensitive(shell, item);
1081 else if (name == label_name)
1082 unity_gtk_menu_shell_handle_item_label(shell, item);
1083 else if (name == use_underline_name)
1084 unity_gtk_menu_shell_handle_item_use_underline(shell, item);
1085 else if (name == accel_path_name)
1086 unity_gtk_menu_shell_handle_item_accel_path(shell, item);
1087 else if (name == active_name)
1088 unity_gtk_menu_shell_handle_item_active(shell, item);
1089 else if (name == parent_name)
1090 unity_gtk_menu_shell_handle_item_parent(shell, item);
1091 else if (name == submenu_name)
1092 unity_gtk_menu_shell_handle_item_submenu(shell, item);
1093 }
1094
unity_gtk_menu_shell_activate_item(UnityGtkMenuShell * shell,UnityGtkMenuItem * item)1095 void unity_gtk_menu_shell_activate_item(UnityGtkMenuShell *shell, UnityGtkMenuItem *item)
1096 {
1097 g_return_if_fail(UNITY_GTK_IS_MENU_SHELL(shell));
1098 g_return_if_fail(UNITY_GTK_IS_MENU_ITEM(item));
1099
1100 if (item->menu_item != NULL)
1101 {
1102 if (GTK_IS_MENU(shell->menu_shell))
1103 gtk_menu_set_active(GTK_MENU(shell->menu_shell), item->item_index);
1104
1105 /*
1106 * We dispatch the menu item activation in an idle to fix LP: #1258669.
1107 *
1108 * We get a deadlock when the menu item is activated if something like
1109 * gtk_dialog_run () is called. gtk_dialog_run () releases the GDK lock
1110 * just before starting its own main loop, and tries to re-acquire it
1111 * once it terminates. For whatever reason, a direct call to
1112 * gtk_menu_item_activate () here causes the GDK lock to be acquired
1113 * before gtk_dialog_run () tries to acquire it, whereas dispatching it
1114 * using gdk_threads_add_idle_full () seems to cleanly acquire the lock
1115 * once only at the beginning, preventing the deadlock.
1116 *
1117 * Suspicion is that this was executing during the main context
1118 * iteration of gtk_main_iteration (), which grabs the GDK lock
1119 * immediately after. But it's still not clear how that's possible....
1120 */
1121
1122 gdk_threads_add_idle_full(G_PRIORITY_DEFAULT_IDLE,
1123 gtk_menu_item_handle_idle_activate,
1124 g_object_ref(item->menu_item),
1125 g_object_unref);
1126 }
1127 }
1128
unity_gtk_menu_shell_print(UnityGtkMenuShell * shell,guint indent)1129 void unity_gtk_menu_shell_print(UnityGtkMenuShell *shell, guint indent)
1130 {
1131 char *space;
1132
1133 g_return_if_fail(shell == NULL || UNITY_GTK_IS_MENU_SHELL(shell));
1134
1135 space = g_strnfill(indent, ' ');
1136
1137 if (shell != NULL)
1138 {
1139 g_print("%s(%s *) %p\n",
1140 space,
1141 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(shell)),
1142 shell);
1143
1144 if (shell->menu_shell != NULL)
1145 g_print("%s (%s *) %p\n",
1146 space,
1147 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(shell->menu_shell)),
1148 shell->menu_shell);
1149
1150 if (shell->items != NULL)
1151 {
1152 guint i;
1153
1154 for (i = 0; i < shell->items->len; i++)
1155 unity_gtk_menu_item_print(g_ptr_array_index(shell->items, i),
1156 indent + 2);
1157 }
1158
1159 if (shell->sections != NULL)
1160 {
1161 guint i;
1162
1163 for (i = 0; i < shell->sections->len; i++)
1164 unity_gtk_menu_section_print(g_ptr_array_index(shell->sections, i),
1165 indent + 2);
1166 }
1167
1168 if (shell->visible_indices != NULL)
1169 {
1170 GSequenceIter *iter = g_sequence_get_begin_iter(shell->visible_indices);
1171
1172 g_print("%s ", space);
1173
1174 while (!g_sequence_iter_is_end(iter))
1175 {
1176 g_print(" %u", g_sequence_get_uint(iter));
1177 iter = g_sequence_iter_next(iter);
1178 }
1179
1180 g_print("\n");
1181 }
1182
1183 if (shell->separator_indices != NULL)
1184 {
1185 GSequenceIter *iter = g_sequence_get_begin_iter(shell->separator_indices);
1186
1187 g_print("%s ", space);
1188
1189 while (!g_sequence_iter_is_end(iter))
1190 {
1191 g_print(" %u", g_sequence_get_uint(iter));
1192 iter = g_sequence_iter_next(iter);
1193 }
1194
1195 g_print("\n");
1196 }
1197
1198 if (shell->action_group != NULL)
1199 g_print("%s (%s *) %p\n",
1200 space,
1201 G_OBJECT_CLASS_NAME(G_OBJECT_GET_CLASS(shell->action_group)),
1202 shell->action_group);
1203 }
1204 else
1205 g_print("%sNULL\n", space);
1206
1207 g_free(space);
1208 }
1209
unity_gtk_menu_shell_is_debug(void)1210 gboolean unity_gtk_menu_shell_is_debug(void)
1211 {
1212 return unity_gtk_menu_shell_debug;
1213 }
1214
1215 /**
1216 * unity_gtk_menu_shell_set_debug:
1217 * @debug: #TRUE to enable debugging output
1218 *
1219 * Sets if menu shell changes should be logged using g_print ().
1220 */
unity_gtk_menu_shell_set_debug(gboolean debug)1221 void unity_gtk_menu_shell_set_debug(gboolean debug)
1222 {
1223 unity_gtk_menu_shell_debug = debug;
1224 }
1225