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