1 /* vi:set et ai sw=2 sts=2 ts=2: */
2 /*-
3 * Copyright (c) 2013 Nick Schermer <nick@xfce.org>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General
16 * Public License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <libxfce4util/libxfce4util.h>
26 #include <libxfce4ui/libxfce4ui.h>
27
28 #include <garcon-gtk/garcon-gtk-menu.h>
29
30
31 /**
32 * SECTION: garcon-gtk-menu
33 * @title: GarconGtkMenu
34 * @short_description: Create a GtkMenu for a GarconMenu.
35 * @include: garcon-gtk/garcon-gtk.h
36 *
37 * Create a complete GtkMenu for the given GarconMenu
38 **/
39
40
41
42 /* Property identifiers */
43 enum
44 {
45 PROP_0,
46 PROP_MENU,
47 PROP_SHOW_GENERIC_NAMES,
48 PROP_SHOW_MENU_ICONS,
49 PROP_SHOW_TOOLTIPS,
50 PROP_SHOW_DESKTOP_ACTIONS,
51 PROP_RIGHT_CLICK_EDITS,
52 N_PROPERTIES
53 };
54
55
56
57 static void garcon_gtk_menu_finalize (GObject *object);
58 static void garcon_gtk_menu_get_property (GObject *object,
59 guint prop_id,
60 GValue *value,
61 GParamSpec *pspec);
62 static void garcon_gtk_menu_set_property (GObject *object,
63 guint prop_id,
64 const GValue *value,
65 GParamSpec *pspec);
66 static void garcon_gtk_menu_show (GtkWidget *widget);
67 static void garcon_gtk_menu_load (GarconGtkMenu *menu);
68
69
70
71 struct _GarconGtkMenuPrivate
72 {
73 GarconMenu *menu;
74
75 guint is_loaded : 1;
76
77 /* reload idle */
78 guint reload_id;
79
80 /* settings */
81 guint show_generic_names : 1;
82 guint show_menu_icons : 1;
83 guint show_tooltips : 1;
84 guint show_desktop_actions : 1;
85 guint right_click_edits : 1;
86 };
87
88
89
90 static const GtkTargetEntry dnd_target_list[] = {
91 { "text/uri-list", 0, 0 }
92 };
93
94
95
96 static GParamSpec *menu_props[N_PROPERTIES] = { NULL, };
97
98
99
G_DEFINE_TYPE_WITH_PRIVATE(GarconGtkMenu,garcon_gtk_menu,GTK_TYPE_MENU)100 G_DEFINE_TYPE_WITH_PRIVATE (GarconGtkMenu, garcon_gtk_menu, GTK_TYPE_MENU)
101
102
103
104 static void
105 garcon_gtk_menu_class_init (GarconGtkMenuClass *klass)
106 {
107 GObjectClass *gobject_class;
108 GtkWidgetClass *gtkwidget_class;
109
110 gobject_class = G_OBJECT_CLASS (klass);
111 gobject_class->finalize = garcon_gtk_menu_finalize;
112 gobject_class->get_property = garcon_gtk_menu_get_property;
113 gobject_class->set_property = garcon_gtk_menu_set_property;
114
115 gtkwidget_class = GTK_WIDGET_CLASS (klass);
116 gtkwidget_class->show = garcon_gtk_menu_show;
117
118 /**
119 * GarconMenu:menu:
120 *
121 *
122 **/
123 menu_props[PROP_MENU] =
124 g_param_spec_object ("menu",
125 "menu",
126 "menu",
127 GARCON_TYPE_MENU,
128 G_PARAM_READWRITE
129 | G_PARAM_STATIC_STRINGS);
130
131 /**
132 * GarconMenu:show-generic-names:
133 *
134 *
135 **/
136 menu_props[PROP_SHOW_GENERIC_NAMES] =
137 g_param_spec_boolean ("show-generic-names",
138 "show-generic-names",
139 "show-generic-names",
140 FALSE,
141 G_PARAM_READWRITE
142 | G_PARAM_STATIC_STRINGS);
143
144 /**
145 * GarconMenu:show-menu-icons:
146 *
147 *
148 **/
149 menu_props[PROP_SHOW_MENU_ICONS] =
150 g_param_spec_boolean ("show-menu-icons",
151 "show-menu-icons",
152 "show-menu-icons",
153 TRUE,
154 G_PARAM_READWRITE
155 | G_PARAM_STATIC_STRINGS);
156
157 /**
158 * GarconMenu:show-tooltips:
159 *
160 *
161 **/
162 menu_props[PROP_SHOW_TOOLTIPS] =
163 g_param_spec_boolean ("show-tooltips",
164 "show-tooltips",
165 "show-tooltips",
166 FALSE,
167 G_PARAM_READWRITE
168 | G_PARAM_STATIC_STRINGS);
169
170 /**
171 * GarconMenu:show-desktop-actions:
172 *
173 *
174 **/
175 menu_props[PROP_SHOW_DESKTOP_ACTIONS] =
176 g_param_spec_boolean ("show-desktop-actions",
177 "show-desktop-actions",
178 "show desktop actions in a submenu",
179 FALSE,
180 G_PARAM_READWRITE
181 | G_PARAM_STATIC_STRINGS);
182
183 /**
184 * GarconMenu:right-click-edits:
185 *
186 *
187 **/
188 menu_props[PROP_RIGHT_CLICK_EDITS] =
189 g_param_spec_boolean ("right-click-edits",
190 "right-click-edits",
191 "right click to edit menu items",
192 FALSE,
193 G_PARAM_READWRITE
194 | G_PARAM_STATIC_STRINGS);
195
196 /* install all properties */
197 g_object_class_install_properties (gobject_class, N_PROPERTIES, menu_props);
198 }
199
200
201
202 static void
garcon_gtk_menu_init(GarconGtkMenu * menu)203 garcon_gtk_menu_init (GarconGtkMenu *menu)
204 {
205 menu->priv = garcon_gtk_menu_get_instance_private (menu);
206
207 menu->priv->show_generic_names = FALSE;
208 menu->priv->show_menu_icons = TRUE;
209 menu->priv->show_tooltips = FALSE;
210 menu->priv->show_desktop_actions = FALSE;
211 menu->priv->right_click_edits = FALSE;
212
213 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
214 }
215
216
217
218 static void
garcon_gtk_menu_finalize(GObject * object)219 garcon_gtk_menu_finalize (GObject *object)
220 {
221 GarconGtkMenu *menu = GARCON_GTK_MENU (object);
222
223 /* Stop pending reload */
224 if (menu->priv->reload_id != 0)
225 g_source_remove (menu->priv->reload_id);
226
227 /* Release menu */
228 if (menu->priv->menu != NULL)
229 g_object_unref (menu->priv->menu);
230
231 (*G_OBJECT_CLASS (garcon_gtk_menu_parent_class)->finalize) (object);
232 }
233
234
235
236 static void
garcon_gtk_menu_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)237 garcon_gtk_menu_get_property (GObject *object,
238 guint prop_id,
239 GValue *value,
240 GParamSpec *pspec)
241 {
242 GarconGtkMenu *menu = GARCON_GTK_MENU (object);
243
244 switch (prop_id)
245 {
246 case PROP_MENU:
247 g_value_set_object (value, menu->priv->menu);
248 break;
249
250 case PROP_SHOW_GENERIC_NAMES:
251 g_value_set_boolean (value, menu->priv->show_generic_names);
252 break;
253
254 case PROP_SHOW_MENU_ICONS:
255 g_value_set_boolean (value, menu->priv->show_menu_icons);
256 break;
257
258 case PROP_SHOW_TOOLTIPS:
259 g_value_set_boolean (value, menu->priv->show_tooltips);
260 break;
261
262 case PROP_SHOW_DESKTOP_ACTIONS:
263 g_value_set_boolean (value, menu->priv->show_desktop_actions);
264 break;
265
266 case PROP_RIGHT_CLICK_EDITS:
267 g_value_set_boolean (value, menu->priv->right_click_edits);
268 break;
269
270 default:
271 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
272 break;
273 }
274 }
275
276
277
278 static void
garcon_gtk_menu_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)279 garcon_gtk_menu_set_property (GObject *object,
280 guint prop_id,
281 const GValue *value,
282 GParamSpec *pspec)
283 {
284 GarconGtkMenu *menu = GARCON_GTK_MENU (object);
285
286 switch (prop_id)
287 {
288 case PROP_MENU:
289 garcon_gtk_menu_set_menu (menu, g_value_get_object (value));
290 break;
291
292 case PROP_SHOW_GENERIC_NAMES:
293 garcon_gtk_menu_set_show_generic_names (menu, g_value_get_boolean (value));
294 break;
295
296 case PROP_SHOW_MENU_ICONS:
297 garcon_gtk_menu_set_show_menu_icons (menu, g_value_get_boolean (value));
298 break;
299
300 case PROP_SHOW_TOOLTIPS:
301 garcon_gtk_menu_set_show_tooltips (menu, g_value_get_boolean (value));
302 break;
303
304 case PROP_SHOW_DESKTOP_ACTIONS:
305 garcon_gtk_menu_set_show_desktop_actions (menu, g_value_get_boolean (value));
306 break;
307
308 case PROP_RIGHT_CLICK_EDITS:
309 garcon_gtk_menu_set_right_click_edits (menu, g_value_get_boolean (value));
310 break;
311
312 default:
313 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
314 break;
315 }
316 }
317
318
319
320 static void
garcon_gtk_menu_show(GtkWidget * widget)321 garcon_gtk_menu_show (GtkWidget *widget)
322 {
323 GarconGtkMenu *menu = GARCON_GTK_MENU (widget);
324
325 /* try to load the menu if needed */
326 if (!menu->priv->is_loaded)
327 garcon_gtk_menu_load (menu);
328
329 (*GTK_WIDGET_CLASS (garcon_gtk_menu_parent_class)->show) (widget);
330 }
331
332
333
334 static void
garcon_gtk_menu_item_activate_real(GtkWidget * mi,GarconMenuItem * item,GarconMenuItemAction * action)335 garcon_gtk_menu_item_activate_real (GtkWidget *mi,
336 GarconMenuItem *item,
337 GarconMenuItemAction *action)
338 {
339 gchar *command, *uri;
340 gchar **argv;
341 const gchar *icon;
342 gboolean result = FALSE;
343 GError *error = NULL;
344
345 g_return_if_fail (GTK_IS_WIDGET (mi));
346 g_return_if_fail (GARCON_IS_MENU_ITEM (item));
347
348 if (action != NULL)
349 command = (gchar*) garcon_menu_item_action_get_command (action);
350 else
351 command = (gchar*) garcon_menu_item_get_command (item);
352
353 if (xfce_str_is_empty (command))
354 return;
355
356 /* expand the field codes */
357 icon = garcon_menu_item_get_icon_name (item);
358 uri = garcon_menu_item_get_uri (item);
359 command = xfce_expand_desktop_entry_field_codes (command, NULL, icon,
360 garcon_menu_item_get_name (item),
361 uri,
362 garcon_menu_item_requires_terminal (item));
363 g_free (uri);
364
365 /* parse and spawn command */
366 if (g_shell_parse_argv (command, NULL, &argv, &error))
367 {
368 result = xfce_spawn (gtk_widget_get_screen (mi),
369 garcon_menu_item_get_path (item),
370 argv, NULL, G_SPAWN_SEARCH_PATH,
371 garcon_menu_item_supports_startup_notification (item),
372 gtk_get_current_event_time (),
373 icon, TRUE, &error);
374
375 g_strfreev (argv);
376 }
377
378 if (G_UNLIKELY (!result))
379 {
380 xfce_dialog_show_error (NULL, error, _("Failed to execute command \"%s\"."), command);
381 g_error_free (error);
382 }
383
384 g_free (command);
385 }
386
387
388
389 static void
garcon_gtk_menu_item_edit_launcher(GarconMenuItem * item)390 garcon_gtk_menu_item_edit_launcher (GarconMenuItem *item)
391 {
392 GFile *file;
393 gchar *uri, *cmd;
394 GError *error = NULL;
395
396 file = garcon_menu_item_get_file (item);
397
398 if (file)
399 {
400 uri = g_file_get_uri (file);
401 cmd = g_strdup_printf ("exo-desktop-item-edit \"%s\"", uri);
402
403 if (!xfce_spawn_command_line (NULL, cmd, FALSE, FALSE, TRUE, &error))
404 {
405 xfce_message_dialog (NULL,
406 _("Launch Error"),
407 "dialog-error",
408 _("Unable to launch \"exo-desktop-item-edit\", which is required to create and edit menu items."),
409 error->message,
410 XFCE_BUTTON_TYPE_MIXED, "window-close-symbolic", _("_Close"), GTK_RESPONSE_ACCEPT,
411 NULL);
412
413 g_clear_error (&error);
414 }
415
416 g_free(uri);
417 g_free(cmd);
418 g_object_unref(file);
419 }
420 }
421
422
423 static void
garcon_gtk_menu_item_activate(GtkWidget * mi,GarconMenuItem * item)424 garcon_gtk_menu_item_activate (GtkWidget *mi,
425 GarconMenuItem *item)
426 {
427 GarconGtkMenu *menu = g_object_get_data (G_OBJECT (mi), "GarconGtkMenu");
428 GdkEventButton *evt;
429 guint button;
430 gboolean right_click = FALSE;
431
432 evt = (GdkEventButton *)gtk_get_current_event();
433
434 /* See if we're trying to edit the launcher */
435 if(menu->priv->right_click_edits && evt && GDK_BUTTON_RELEASE == evt->type)
436 {
437 button = evt->button;
438
439 /* right click or Shift + left can optionally edit launchers */
440 if (button == 3 || (button == 1 && (evt->state & GDK_SHIFT_MASK)))
441 {
442 garcon_gtk_menu_item_edit_launcher (item);
443 right_click = TRUE;
444 }
445 }
446
447 if (!right_click)
448 {
449 /* normal action, launch the application */
450 garcon_gtk_menu_item_activate_real (mi, item, NULL);
451 }
452
453 if (evt)
454 {
455 gdk_event_free((GdkEvent*)evt);
456 }
457 }
458
459
460
461 static void
garcon_gtk_menu_item_action_activate(GtkWidget * mi,GarconMenuItemAction * action)462 garcon_gtk_menu_item_action_activate (GtkWidget *mi,
463 GarconMenuItemAction *action)
464 {
465 GarconMenuItem *item = g_object_get_data (G_OBJECT (action), "GarconMenuItem");
466
467 if (item == NULL)
468 {
469 g_critical ("garcon_gtk_menu_item_action_activate: Failed to get the GarconMenuItem\n");
470 return;
471 }
472
473 garcon_gtk_menu_item_activate_real (mi, item, action);
474 }
475
476
477
478 static void
garcon_gtk_menu_item_drag_begin(GarconMenuItem * item,GdkDragContext * drag_context)479 garcon_gtk_menu_item_drag_begin (GarconMenuItem *item,
480 GdkDragContext *drag_context)
481 {
482 const gchar *icon_name;
483
484 g_return_if_fail (GARCON_IS_MENU_ITEM (item));
485
486 icon_name = garcon_menu_item_get_icon_name (item);
487 if (!xfce_str_is_empty (icon_name))
488 gtk_drag_set_icon_name (drag_context, icon_name, 0, 0);
489 }
490
491
492
493 static void
garcon_gtk_menu_item_drag_data_get(GarconMenuItem * item,GdkDragContext * drag_context,GtkSelectionData * selection_data,guint info,guint drag_time)494 garcon_gtk_menu_item_drag_data_get (GarconMenuItem *item,
495 GdkDragContext *drag_context,
496 GtkSelectionData *selection_data,
497 guint info,
498 guint drag_time)
499 {
500 gchar *uris[2] = { NULL, NULL };
501
502 g_return_if_fail (GARCON_IS_MENU_ITEM (item));
503
504 uris[0] = garcon_menu_item_get_uri (item);
505 if (G_LIKELY (uris[0] != NULL))
506 {
507 gtk_selection_data_set_uris (selection_data, uris);
508 g_free (uris[0]);
509 }
510 }
511
512
513
514 static void
garcon_gtk_menu_item_drag_end(GarconGtkMenu * menu)515 garcon_gtk_menu_item_drag_end (GarconGtkMenu *menu)
516 {
517 g_return_if_fail (GTK_IS_MENU (menu));
518
519 /* make sure the menu is not visible */
520 gtk_menu_popdown (GTK_MENU (menu));
521
522 /* always emit this signal */
523 g_signal_emit_by_name (G_OBJECT (menu), "selection-done", 0);
524 }
525
526
527
528 static void
garcon_gtk_menu_deactivate(GtkWidget * submenu,GarconGtkMenu * menu)529 garcon_gtk_menu_deactivate (GtkWidget *submenu,
530 GarconGtkMenu *menu)
531 {
532 garcon_gtk_menu_item_drag_end (menu);
533 }
534
535
536
537 static gboolean
garcon_gtk_menu_reload_idle(gpointer data)538 garcon_gtk_menu_reload_idle (gpointer data)
539 {
540 GarconGtkMenu *menu = GARCON_GTK_MENU (data);
541 GList *children;
542
543 /* wait until the menu is hidden */
544 if (gtk_widget_get_visible (GTK_WIDGET (menu)))
545 return TRUE;
546
547 /* destroy all menu item */
548 children = gtk_container_get_children (GTK_CONTAINER (menu));
549 g_list_free_full (children, (GDestroyNotify) gtk_widget_destroy);
550
551 /* reload the menu */
552 garcon_gtk_menu_load (menu);
553
554 /* reset */
555 menu->priv->reload_id = 0;
556
557 return FALSE;
558 }
559
560
561
562 static void
garcon_gtk_menu_reload(GarconGtkMenu * menu)563 garcon_gtk_menu_reload (GarconGtkMenu *menu)
564 {
565 /* schedule a menu reload */
566 if (menu->priv->reload_id == 0
567 && menu->priv->is_loaded)
568 {
569 menu->priv->reload_id = g_timeout_add (100, garcon_gtk_menu_reload_idle, menu);
570 }
571 }
572
573
574
575 static GtkWidget*
garcon_gtk_menu_load_icon(const gchar * icon_name)576 garcon_gtk_menu_load_icon (const gchar *icon_name)
577 {
578 GtkWidget *image = NULL;
579 gint w, h, size;
580 gchar *p, *name = NULL;
581 GdkPixbuf *pixbuf = NULL;
582 GtkIconTheme *icon_theme = gtk_icon_theme_get_default ();
583
584 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
585 size = MIN (w, h);
586
587 if (gtk_icon_theme_has_icon (icon_theme, icon_name))
588 {
589 pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name, size, 0, NULL);;
590 }
591 else
592 {
593 if (g_path_is_absolute (icon_name))
594 {
595 pixbuf = gdk_pixbuf_new_from_file_at_scale (icon_name, w, h, TRUE, NULL);
596 }
597 else
598 {
599 /* try to lookup names like application.png in the theme */
600 p = strrchr (icon_name, '.');
601 if (p)
602 {
603 name = g_strndup (icon_name, p - icon_name);
604 pixbuf = gtk_icon_theme_load_icon (icon_theme, name, size, 0, NULL);
605 g_free (name);
606 name = NULL;
607 }
608
609 /* maybe they point to a file in the pixbufs folder */
610 if (G_UNLIKELY (pixbuf == NULL))
611 {
612 gchar *filename;
613
614 filename = g_build_filename ("pixmaps", icon_name, NULL);
615 name = xfce_resource_lookup (XFCE_RESOURCE_DATA, filename);
616 g_free (filename);
617 }
618
619 if (name)
620 {
621 pixbuf = gdk_pixbuf_new_from_file_at_scale (name, w, h, TRUE, NULL);
622 g_free (name);
623 }
624 }
625 }
626
627 /* Turn the pixbuf into a gtk_image */
628 if (G_LIKELY (pixbuf))
629 {
630 /* scale the pixbuf down if it needs it */
631 GdkPixbuf *pixbuf_scaled = gdk_pixbuf_scale_simple (pixbuf, w, h, GDK_INTERP_BILINEAR);
632 g_object_unref (G_OBJECT (pixbuf));
633
634 image = gtk_image_new_from_pixbuf (pixbuf_scaled);
635 g_object_unref (G_OBJECT (pixbuf_scaled));
636 }
637 else
638 {
639 /* display the placeholder at least */
640 image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_MENU);
641 }
642
643 return image;
644 }
645
646
647
648 static GtkWidget*
garcon_gtk_menu_create_menu_item(gboolean show_menu_icons,const gchar * name,const gchar * icon_name)649 garcon_gtk_menu_create_menu_item (gboolean show_menu_icons,
650 const gchar *name,
651 const gchar *icon_name)
652 {
653 GtkWidget *mi;
654 GtkWidget *image;
655
656 if (show_menu_icons)
657 {
658 image = garcon_gtk_menu_load_icon (icon_name);
659 gtk_widget_show (image);
660 }
661 else
662 {
663 image = gtk_image_new ();
664 }
665
666 mi = xfce_gtk_image_menu_item_new (name, NULL, NULL, NULL, NULL, image, NULL);
667
668 return mi;
669 }
670
671
672
673 static void
garcon_gtk_menu_pack_actions_menu(GtkWidget * menu,GarconMenuItem * menu_item,GList * actions,const gchar * parent_icon_name,gboolean show_menu_icons)674 garcon_gtk_menu_pack_actions_menu (GtkWidget *menu,
675 GarconMenuItem *menu_item,
676 GList *actions,
677 const gchar *parent_icon_name,
678 gboolean show_menu_icons)
679 {
680 GList *iter;
681 GtkWidget *mi;
682
683 gtk_menu_set_reserve_toggle_size (GTK_MENU (menu), FALSE);
684
685 /* Add all the individual actions to the menu */
686 for (iter = g_list_first (actions); iter != NULL; iter = g_list_next (iter))
687 {
688 GarconMenuItemAction *action = garcon_menu_item_get_action (menu_item, iter->data);
689 const gchar *action_icon_name;
690
691 if (action == NULL)
692 continue;
693
694 /* If there's a custom icon associated with the action, use it.
695 * Otherwise default to the parent's icon.
696 */
697 action_icon_name = garcon_menu_item_action_get_icon_name (action);
698 if (action_icon_name == NULL)
699 {
700 action_icon_name = parent_icon_name;
701 }
702
703 mi = garcon_gtk_menu_create_menu_item (show_menu_icons,
704 garcon_menu_item_action_get_name (action),
705 action_icon_name);
706
707 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
708 g_signal_connect (G_OBJECT (mi), "activate",
709 G_CALLBACK (garcon_gtk_menu_item_action_activate), action);
710 /* we need to store the parent associated with this item so we can
711 * activate it properly */
712 g_object_set_data (G_OBJECT (action), "GarconMenuItem", menu_item);
713 gtk_widget_show (mi);
714 }
715 }
716
717
718
719 static GtkWidget*
garcon_gtk_menu_add_actions(GarconGtkMenu * menu,GarconMenuItem * menu_item,GList * actions,const gchar * parent_icon_name)720 garcon_gtk_menu_add_actions (GarconGtkMenu *menu,
721 GarconMenuItem *menu_item,
722 GList *actions,
723 const gchar *parent_icon_name)
724 {
725 GtkWidget *submenu, *mi;
726
727 submenu = gtk_menu_new ();
728
729 /* Add the parent item again, this time something the user can click to execute */
730 mi = garcon_gtk_menu_create_menu_item (menu->priv->show_menu_icons,
731 garcon_menu_item_get_name (menu_item),
732 parent_icon_name);
733 gtk_menu_shell_append (GTK_MENU_SHELL (submenu), mi);
734
735 /* we need to store the GarconGtkMenu with this item so we can
736 * use it if the user wants to edit a menu item */
737 g_object_set_data (G_OBJECT (mi), "GarconGtkMenu", menu);
738 g_signal_connect (G_OBJECT (mi), "activate",
739 G_CALLBACK (garcon_gtk_menu_item_activate), menu_item);
740 gtk_widget_show (mi);
741
742 garcon_gtk_menu_pack_actions_menu (submenu, menu_item, actions,
743 parent_icon_name, menu->priv->show_menu_icons);
744
745 return submenu;
746 }
747
748
749
750 static gboolean
garcon_gtk_menu_add(GarconGtkMenu * menu,GtkMenu * gtk_menu,GarconMenu * garcon_menu)751 garcon_gtk_menu_add (GarconGtkMenu *menu,
752 GtkMenu *gtk_menu,
753 GarconMenu *garcon_menu)
754 {
755 GList *elements, *li;
756 GtkWidget *mi;
757 const gchar *name, *icon_name;
758 const gchar *comment;
759 GtkWidget *submenu;
760 gboolean has_children = FALSE;
761 const gchar *command;
762 GarconMenuDirectory *directory;
763
764 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), FALSE);
765 g_return_val_if_fail (GTK_IS_MENU (gtk_menu), FALSE);
766 g_return_val_if_fail (GARCON_IS_MENU (garcon_menu), FALSE);
767
768 elements = garcon_menu_get_elements (garcon_menu);
769 for (li = elements; li != NULL; li = li->next)
770 {
771 g_assert (GARCON_IS_MENU_ELEMENT (li->data));
772
773 if (GARCON_IS_MENU_ITEM (li->data))
774 {
775 GList *actions = NULL;
776
777 /* watch for changes */
778 g_signal_connect_swapped (G_OBJECT (li->data), "changed",
779 G_CALLBACK (garcon_gtk_menu_reload), menu);
780
781 /* skip invisible items */
782 if (!garcon_menu_element_get_visible (li->data))
783 continue;
784
785 /* get element name */
786 name = NULL;
787 if (menu->priv->show_generic_names)
788 name = garcon_menu_item_get_generic_name (li->data);
789 if (name == NULL)
790 name = garcon_menu_item_get_name (li->data);
791
792 if (G_UNLIKELY (name == NULL))
793 continue;
794
795 icon_name = garcon_menu_item_get_icon_name (li->data);
796 if (xfce_str_is_empty (icon_name))
797 icon_name = "applications-other";
798
799 /* build the menu item */
800 mi = garcon_gtk_menu_create_menu_item (menu->priv->show_menu_icons, name, icon_name);
801 gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), mi);
802
803 /* if the menu item has actions such as "Private browsing mode"
804 * show them as well */
805 if (menu->priv->show_desktop_actions)
806 {
807 actions = garcon_menu_item_get_actions (li->data);
808 }
809
810 if (actions != NULL)
811 {
812 submenu = garcon_gtk_menu_add_actions (menu, li->data, actions, icon_name);
813 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), submenu);
814 g_list_free (actions);
815 }
816 else
817 {
818 g_signal_connect (G_OBJECT (mi), "activate",
819 G_CALLBACK (garcon_gtk_menu_item_activate), li->data);
820 /* we need to store the GarconGtkMenu with this item so we can
821 * use it if the user wants to edit a menu item */
822 g_object_set_data (G_OBJECT (mi), "GarconGtkMenu", menu);
823 }
824
825 gtk_widget_show (mi);
826
827 if (menu->priv->show_tooltips)
828 {
829 comment = garcon_menu_item_get_comment (li->data);
830 if (!xfce_str_is_empty (comment))
831 gtk_widget_set_tooltip_text (mi, comment);
832 }
833
834 /* support for dnd item to for example the xfce4-panel */
835 gtk_drag_source_set (mi, GDK_BUTTON1_MASK, dnd_target_list,
836 G_N_ELEMENTS (dnd_target_list), GDK_ACTION_COPY);
837 g_signal_connect_swapped (G_OBJECT (mi), "drag-begin",
838 G_CALLBACK (garcon_gtk_menu_item_drag_begin), li->data);
839 g_signal_connect_swapped (G_OBJECT (mi), "drag-data-get",
840 G_CALLBACK (garcon_gtk_menu_item_drag_data_get), li->data);
841 g_signal_connect_swapped (G_OBJECT (mi), "drag-end",
842 G_CALLBACK (garcon_gtk_menu_item_drag_end), menu);
843
844 /* doesn't happen, but anyway... */
845 command = garcon_menu_item_get_command (li->data);
846 if (xfce_str_is_empty (command))
847 gtk_widget_set_sensitive (mi, FALSE);
848
849 /* atleast 1 visible child */
850 has_children = TRUE;
851 }
852 else if (GARCON_IS_MENU_SEPARATOR (li->data))
853 {
854 mi = gtk_separator_menu_item_new ();
855 gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), mi);
856 gtk_widget_show (mi);
857 }
858 else if (GARCON_IS_MENU (li->data))
859 {
860 /* the element check for menu also copies the item list to
861 * check if all the elements are visible, we do that with the
862 * return value of this function, so avoid that and only check
863 * the visibility of the menu directory */
864 directory = garcon_menu_get_directory (li->data);
865 if (directory != NULL
866 && !garcon_menu_directory_get_visible (directory))
867 continue;
868
869 submenu = gtk_menu_new ();
870 gtk_menu_set_reserve_toggle_size (GTK_MENU (submenu), FALSE);
871 if (garcon_gtk_menu_add (menu, GTK_MENU (submenu), li->data))
872 {
873 /* attach submenu */
874 name = garcon_menu_element_get_name (li->data);
875
876 icon_name = garcon_menu_element_get_icon_name (li->data);
877 if (xfce_str_is_empty (icon_name))
878 icon_name = "applications-other";
879
880 /* build the menu item */
881 mi = garcon_gtk_menu_create_menu_item (menu->priv->show_menu_icons, name, icon_name);
882
883 gtk_menu_shell_append (GTK_MENU_SHELL (gtk_menu), mi);
884 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), submenu);
885 g_signal_connect (G_OBJECT (submenu), "selection-done",
886 G_CALLBACK (garcon_gtk_menu_deactivate), menu);
887 gtk_widget_show (mi);
888
889 /* atleast 1 visible child */
890 has_children = TRUE;
891 }
892 else
893 {
894 /* no visible element in the menu */
895 gtk_widget_destroy (submenu);
896 }
897 }
898 }
899
900 g_list_free (elements);
901
902 return has_children;
903 }
904
905
906
907 static void
garcon_gtk_menu_load(GarconGtkMenu * menu)908 garcon_gtk_menu_load (GarconGtkMenu *menu)
909 {
910 GError *error = NULL;
911
912 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
913 g_return_if_fail (menu->priv->menu == NULL || GARCON_IS_MENU (menu->priv->menu));
914
915 if (menu->priv->menu == NULL)
916 return;
917
918 if (garcon_menu_load (menu->priv->menu, NULL, &error))
919 {
920 garcon_gtk_menu_add (menu, GTK_MENU (menu), menu->priv->menu);
921
922 /* watch for changes */
923 g_signal_connect_swapped (G_OBJECT (menu->priv->menu), "reload-required",
924 G_CALLBACK (garcon_gtk_menu_reload), menu);
925 }
926 else
927 {
928 xfce_dialog_show_error (NULL, error, _("Failed to load the applications menu"));
929 g_error_free (error);
930 }
931
932 menu->priv->reload_id = 0;
933 menu->priv->is_loaded = TRUE;
934 }
935
936
937
938 /**
939 * garcon_gtk_menu_new:
940 * @garcon_menu :
941 *
942 * Creates a new #GarconMenu for the .menu file referred to by @file.
943 * This operation only fails @file is invalid. To load the menu
944 * tree from the file, you need to call garcon_gtk_menu_load() with the
945 * returned #GarconMenu.
946 *
947 * The caller is responsible to destroy the returned #GarconMenu
948 * using g_object_unref().
949 *
950 * For more information about the usage @see garcon_gtk_menu_new().
951 *
952 * Returns: a new #GarconMenu for @file.
953 **/
954 GtkWidget *
garcon_gtk_menu_new(GarconMenu * garcon_menu)955 garcon_gtk_menu_new (GarconMenu *garcon_menu)
956 {
957 g_return_val_if_fail (garcon_menu == NULL || GARCON_IS_MENU (garcon_menu), NULL);
958 return g_object_new (GARCON_GTK_TYPE_MENU, "menu", garcon_menu, NULL);
959 }
960
961
962
963 /**
964 * garcon_gtk_menu_set_menu:
965 * @menu : A #GarconGtkMenu
966 * @garcon_menu : The #GarconMenu to use
967 *
968 **/
969 void
garcon_gtk_menu_set_menu(GarconGtkMenu * menu,GarconMenu * garcon_menu)970 garcon_gtk_menu_set_menu (GarconGtkMenu *menu,
971 GarconMenu *garcon_menu)
972 {
973 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
974 g_return_if_fail (garcon_menu == NULL || GARCON_IS_MENU (garcon_menu));
975
976 if (menu->priv->menu == garcon_menu)
977 return;
978
979 if (menu->priv->menu != NULL)
980 {
981 g_signal_handlers_disconnect_by_func (G_OBJECT (menu->priv->menu), garcon_gtk_menu_reload, menu);
982 g_object_unref (G_OBJECT (menu->priv->menu));
983 }
984
985 if (garcon_menu != NULL)
986 menu->priv->menu = GARCON_MENU (g_object_ref (G_OBJECT (garcon_menu)));
987 else
988 menu->priv->menu = NULL;
989
990 g_object_notify_by_pspec (G_OBJECT (menu), menu_props[PROP_MENU]);
991
992 garcon_gtk_menu_reload (menu);
993 }
994
995
996
997 /**
998 * garcon_gtk_menu_get_menu:
999 * @menu : A #GarconGtkMenu
1000 *
1001 * The #GarconMenu used to create the #GtkMenu.
1002 *
1003 * The caller is responsible to releasing the returned #GarconMenu
1004 * using g_object_unref().
1005 *
1006 * Returns: (transfer full): the #GarconMenu for @menu.
1007 **/
1008 GarconMenu *
garcon_gtk_menu_get_menu(GarconGtkMenu * menu)1009 garcon_gtk_menu_get_menu (GarconGtkMenu *menu)
1010 {
1011 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), NULL);
1012 if (menu->priv->menu != NULL)
1013 return GARCON_MENU (g_object_ref (G_OBJECT (menu->priv->menu)));
1014 return NULL;
1015 }
1016
1017
1018
1019 /**
1020 * garcon_gtk_menu_set_show_generic_names:
1021 * @menu : A #GarconGtkMenu
1022 * @show_generic_names : new value
1023 *
1024 **/
1025 void
garcon_gtk_menu_set_show_generic_names(GarconGtkMenu * menu,gboolean show_generic_names)1026 garcon_gtk_menu_set_show_generic_names (GarconGtkMenu *menu,
1027 gboolean show_generic_names)
1028 {
1029 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
1030
1031 if (menu->priv->show_generic_names == show_generic_names)
1032 return;
1033
1034 menu->priv->show_generic_names = !!show_generic_names;
1035 g_object_notify_by_pspec (G_OBJECT (menu), menu_props[PROP_SHOW_GENERIC_NAMES]);
1036
1037 garcon_gtk_menu_reload (menu);
1038 }
1039
1040
1041
1042 /**
1043 * garcon_gtk_menu_get_show_generic_names:
1044 * @menu : A #GarconGtkMenu
1045 *
1046 * Return value: if generic names are shown
1047 **/
1048 gboolean
garcon_gtk_menu_get_show_generic_names(GarconGtkMenu * menu)1049 garcon_gtk_menu_get_show_generic_names (GarconGtkMenu *menu)
1050 {
1051 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), FALSE);
1052 return menu->priv->show_generic_names;
1053 }
1054
1055
1056
1057 /**
1058 * garcon_gtk_menu_set_show_menu_icons:
1059 * @menu : A #GarconGtkMenu
1060 * @show_menu_icons : new value
1061 *
1062 *
1063 **/
1064 void
garcon_gtk_menu_set_show_menu_icons(GarconGtkMenu * menu,gboolean show_menu_icons)1065 garcon_gtk_menu_set_show_menu_icons (GarconGtkMenu *menu,
1066 gboolean show_menu_icons)
1067 {
1068 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
1069
1070 if (menu->priv->show_menu_icons == show_menu_icons)
1071 return;
1072
1073 menu->priv->show_menu_icons = !!show_menu_icons;
1074 g_object_notify_by_pspec (G_OBJECT (menu), menu_props[PROP_SHOW_MENU_ICONS]);
1075
1076 garcon_gtk_menu_reload (menu);
1077 }
1078
1079
1080
1081 /**
1082 * garcon_gtk_menu_get_show_menu_icons:
1083 * @menu : A #GarconGtkMenu
1084 *
1085 * Return value: if menu icons are shown
1086 **/
1087 gboolean
garcon_gtk_menu_get_show_menu_icons(GarconGtkMenu * menu)1088 garcon_gtk_menu_get_show_menu_icons (GarconGtkMenu *menu)
1089 {
1090 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), FALSE);
1091 return menu->priv->show_menu_icons;
1092 }
1093
1094
1095
1096 /**
1097 * garcon_gtk_menu_set_show_tooltips:
1098 * @menu : A #GarconGtkMenu
1099 *
1100 *
1101 **/
1102 void
garcon_gtk_menu_set_show_tooltips(GarconGtkMenu * menu,gboolean show_tooltips)1103 garcon_gtk_menu_set_show_tooltips (GarconGtkMenu *menu,
1104 gboolean show_tooltips)
1105 {
1106 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
1107
1108 if (menu->priv->show_tooltips == show_tooltips)
1109 return;
1110
1111 menu->priv->show_tooltips = !!show_tooltips;
1112 g_object_notify_by_pspec (G_OBJECT (menu), menu_props[PROP_SHOW_TOOLTIPS]);
1113
1114 garcon_gtk_menu_reload (menu);
1115 }
1116
1117
1118
1119 /**
1120 * garcon_gtk_menu_get_show_tooltips:
1121 * @menu : A #GarconGtkMenu
1122 *
1123 * Return value: Whether descriptions are shown in the tooltip.
1124 **/
1125 gboolean
garcon_gtk_menu_get_show_tooltips(GarconGtkMenu * menu)1126 garcon_gtk_menu_get_show_tooltips (GarconGtkMenu *menu)
1127 {
1128 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), FALSE);
1129 return menu->priv->show_tooltips;
1130 }
1131
1132
1133
1134 /**
1135 * garcon_gtk_menu_set_show_desktop_actions:
1136 * @menu : A #GarconGtkMenu
1137 * @show_desktop_actions : Toggle showing the desktop actions in a submenu.
1138 *
1139 **/
1140 void
garcon_gtk_menu_set_show_desktop_actions(GarconGtkMenu * menu,gboolean show_desktop_actions)1141 garcon_gtk_menu_set_show_desktop_actions (GarconGtkMenu *menu,
1142 gboolean show_desktop_actions)
1143 {
1144 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
1145
1146 if (menu->priv->show_desktop_actions == show_desktop_actions)
1147 return;
1148
1149 menu->priv->show_desktop_actions = !!show_desktop_actions;
1150 g_object_notify_by_pspec (G_OBJECT (menu), menu_props[PROP_SHOW_DESKTOP_ACTIONS]);
1151
1152 garcon_gtk_menu_reload (menu);
1153 }
1154
1155
1156
1157 /**
1158 * garcon_gtk_menu_get_show_desktop_actions:
1159 * @menu : A #GarconGtkMenu
1160 *
1161 * Return value: Whether desktop actions are shown in a submenu.
1162 **/
1163 gboolean
garcon_gtk_menu_get_show_desktop_actions(GarconGtkMenu * menu)1164 garcon_gtk_menu_get_show_desktop_actions (GarconGtkMenu *menu)
1165 {
1166 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), FALSE);
1167 return menu->priv->show_desktop_actions;
1168 }
1169
1170
1171
1172 /**
1173 * garcon_gtk_menu_get_desktop_actions_menu:
1174 * @item : A #GarconMenuItem
1175 *
1176 * Application icons are never shown on the action menu items.
1177 *
1178 * Returns: (transfer full): a #GtkMenu holding all actions described
1179 * in the desktop file as menu items.
1180 **/
1181 GtkMenu *
garcon_gtk_menu_get_desktop_actions_menu(GarconMenuItem * item)1182 garcon_gtk_menu_get_desktop_actions_menu (GarconMenuItem *item)
1183 {
1184 GtkWidget *submenu = gtk_menu_new ();
1185 GList *actions = NULL;
1186 const gchar *parent_icon_name;
1187 gboolean show_menu_icons = FALSE;
1188
1189 actions = garcon_menu_item_get_actions (item);
1190 g_return_val_if_fail (actions != NULL, NULL);
1191
1192 parent_icon_name = garcon_menu_item_get_icon_name (item);
1193
1194 garcon_gtk_menu_pack_actions_menu (submenu, item, actions, parent_icon_name, show_menu_icons);
1195
1196 return GTK_MENU (submenu);
1197 }
1198
1199
1200
1201 /**
1202 * garcon_gtk_menu_set_right_click_edits:
1203 * @menu : A #GarconGtkMenu
1204 * @enable_right_click_edits : Toggle showing whether to launch an editor
1205 * when the menu is clicked with the secondary mouse button.
1206 *
1207 **/
1208 void
garcon_gtk_menu_set_right_click_edits(GarconGtkMenu * menu,gboolean enable_right_click_edits)1209 garcon_gtk_menu_set_right_click_edits (GarconGtkMenu *menu,
1210 gboolean enable_right_click_edits)
1211 {
1212 g_return_if_fail (GARCON_GTK_IS_MENU (menu));
1213
1214 if (menu->priv->right_click_edits == enable_right_click_edits)
1215 return;
1216
1217 menu->priv->right_click_edits = !!enable_right_click_edits;
1218 g_object_notify_by_pspec (G_OBJECT (menu), menu_props[PROP_RIGHT_CLICK_EDITS]);
1219
1220 garcon_gtk_menu_reload (menu);
1221 }
1222
1223
1224
1225 /**
1226 * garcon_gtk_menu_get_right_click_edits:
1227 * @menu : A #GarconGtkMenu
1228 *
1229 * Return value: Whether an editor will be launched on secondary mouse clicks
1230 **/
1231 gboolean
garcon_gtk_menu_get_right_click_edits(GarconGtkMenu * menu)1232 garcon_gtk_menu_get_right_click_edits (GarconGtkMenu *menu)
1233 {
1234 g_return_val_if_fail (GARCON_GTK_IS_MENU (menu), FALSE);
1235 return menu->priv->right_click_edits;
1236 }
1237