2
* This file is a part of the Cairo-Dock project
4
* Copyright : (C) see the 'copyright' file.
5
* E-mail : see the 'copyright' file.
7
* This program is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU General Public License
9
* as published by the Free Software Foundation; either version 3
10
* of the License, or (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
16
* You should have received a copy of the GNU General Public License
17
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21
#include <cairo-dock.h>
23
#include "applet-struct.h"
24
#include "applet-menu-callbacks.h"
25
#include "applet-util.h"
26
#include "applet-menu.h"
29
GtkWidget * add_menu_separator (GtkWidget *menu)
33
menuitem = gtk_separator_menu_item_new ();
34
gtk_widget_set_sensitive (menuitem, FALSE);
35
gtk_widget_show (menuitem);
36
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
41
/* REM: if this function is used in a thread (myData.bLaunchInThread == TRUE)
42
* submenu_to_display will be launched there but no notification will be send
43
* after having created this fake menu
45
GtkWidget * create_fake_menu (GMenuTreeDirectory *directory)
50
menu = create_empty_menu ();
52
g_object_set_data_full (G_OBJECT (menu),
53
"panel-menu-tree-directory",
54
gmenu_tree_item_ref (directory),
55
(GDestroyNotify) gmenu_tree_item_unref);
57
g_object_set_data (G_OBJECT (menu),
58
"panel-menu-needs-loading",
59
GUINT_TO_POINTER (TRUE));
61
g_signal_connect (menu, "show",
62
G_CALLBACK (submenu_to_display), NULL);
64
if (! myData.bLoadInThread)
66
idle_id = g_idle_add_full (G_PRIORITY_LOW,
67
submenu_to_display_in_idle,
70
g_object_set_data_full (G_OBJECT (menu),
72
GUINT_TO_POINTER (idle_id),
73
remove_submenu_to_display_idle);
76
submenu_to_display (menu);
81
void image_menu_destroy (GtkWidget *image, gpointer *data)
83
myData.image_menu_items = g_slist_remove (myData.image_menu_items, image);
84
if (myConfig.bLoadIconsAtStartup && ! myData.bIconsLoaded && myData.pPreloadedImagesList && data)
85
{ // we want to preload icon, the task has not been launched, the list is not empty and we receive data
86
myData.pPreloadedImagesList = g_list_remove (myData.pPreloadedImagesList, data);
92
void reload_image_menu_items (void)
96
for (l = myData.image_menu_items; l; l = l->next) {
97
GtkWidget *image = l->data;
100
///is_mapped = GTK_WIDGET_MAPPED (image);
101
is_mapped = gtk_widget_get_mapped (image);
104
gtk_widget_unmap (image);
106
gtk_image_set_from_pixbuf (GTK_IMAGE (image), NULL);
109
gtk_widget_map (image);
115
remove_pixmap_from_loaded (gpointer data, GObject *where_the_object_was)
119
if (myData.loaded_icons != NULL)
120
g_hash_table_remove (myData.loaded_icons, key);
124
GdkPixbuf * panel_make_menu_icon (GtkIconTheme *icon_theme,
126
const char *fallback,
128
gboolean *long_operation)
134
g_return_val_if_fail (size > 0, NULL);
138
file = panel_find_icon (icon_theme, icon, size);
139
if (file == NULL && fallback != NULL)
140
file = panel_find_icon (icon_theme, fallback, size);
145
if (long_operation != NULL)
146
*long_operation = TRUE;
152
key = g_strdup_printf ("%d:%s", size, file);
154
if (myData.loaded_icons != NULL &&
155
(pb = g_hash_table_lookup (myData.loaded_icons, key)) != NULL) {
157
g_object_ref (G_OBJECT (pb));
161
pb = gdk_pixbuf_new_from_file (file, NULL);
165
width = gdk_pixbuf_get_width (pb);
166
height = gdk_pixbuf_get_height (pb);
168
/* if we want 24 and we get 22, do nothing;
170
if (!(size - 2 <= width && width <= size &&
171
size - 2 <= height && height <= size)) {
174
tmp = gdk_pixbuf_scale_simple (pb, size, size,
175
GDK_INTERP_BILINEAR);
182
/* add icon to the hash table so we don't load it again */
193
(gdk_pixbuf_get_width (pb) != size &&
194
gdk_pixbuf_get_height (pb) != size)) {
201
width = gdk_pixbuf_get_width (pb);
202
height = gdk_pixbuf_get_height (pb);
204
if (height > width) {
205
dest_width = (size * width) / height;
209
dest_height = (size * height) / width;
212
pb2 = gdk_pixbuf_scale_simple (pb, dest_width, dest_height,
213
GDK_INTERP_BILINEAR);
214
g_object_unref (G_OBJECT (pb));
219
if (myData.loaded_icons == NULL)
220
myData.loaded_icons = g_hash_table_new_full
221
(g_str_hash, g_str_equal,
222
(GDestroyNotify) g_free,
223
(GDestroyNotify) g_object_unref);
224
g_hash_table_replace (myData.loaded_icons,
226
g_object_ref (G_OBJECT (pb)));
227
g_object_weak_ref (G_OBJECT (pb),
228
(GWeakNotify) remove_pixmap_from_loaded,
231
/* we didn't load from disk */
232
if (long_operation != NULL)
233
*long_operation = FALSE;
242
void panel_load_menu_image_deferred (GtkWidget *image_menu_item,
243
GtkIconSize icon_size,
244
///const char *stock_id,
246
const char *image_filename,
247
const char *fallback_image_filename)
251
int icon_height = myData.iPanelDefaultMenuIconSize;
253
icon = g_new0 (IconToLoad, 1);
255
gtk_icon_size_lookup (icon_size, NULL, &icon_height);
257
image = gtk_image_new ();
258
///image->requisition.width = icon_height;
259
///image->requisition.height = icon_height;
260
gtk_widget_set_size_request (image, icon_height, icon_height);
262
/* this takes over the floating ref */
263
icon->pixmap = g_object_ref (G_OBJECT (image));
264
g_object_ref_sink (G_OBJECT (image));
266
/**icon->stock_id = stock_id;
268
icon->gicon = g_object_ref (gicon);
270
icon->gicon = NULL;*/
271
icon->image = g_strdup (image_filename);
272
icon->fallback_image = g_strdup (fallback_image_filename);
273
icon->icon_size = icon_size;
275
/**g_object_set_data_full (G_OBJECT (image_menu_item),
277
g_object_ref (image),
278
(GDestroyNotify) g_object_unref);*/
280
g_signal_connect_data (image, "map",
281
G_CALLBACK (image_menu_shown), icon,
282
(GClosureNotify) icon_to_load_free, 0);
284
// pre-load all icons
285
gpointer *data = NULL;
286
if (myConfig.bLoadIconsAtStartup && ! myData.bIconsLoaded)
288
data = g_new0 (gpointer, 2);
291
myData.pPreloadedImagesList = g_list_append (myData.pPreloadedImagesList, data);
294
_gtk_image_menu_item_set_image (
295
GTK_IMAGE_MENU_ITEM (image_menu_item), image);
297
gtk_widget_show (image);
299
g_signal_connect (image, "destroy",
300
G_CALLBACK (image_menu_destroy), data);
302
myData.image_menu_items = g_slist_prepend (myData.image_menu_items, image);
304
GtkWidget * create_submenu_entry (GtkWidget *menu,
305
GMenuTreeDirectory *directory)
309
menuitem = gtk_image_menu_item_new ();
311
panel_load_menu_image_deferred (menuitem,
312
32, //panel_menu_icon_get_size (),
314
gmenu_tree_directory_get_icon (directory),
317
setup_menuitem (menuitem,
318
32, //panel_menu_icon_get_size (),
320
gmenu_tree_directory_get_name (directory));
322
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
324
gtk_widget_show (menuitem);
329
void create_submenu (GtkWidget *menu,
330
GMenuTreeDirectory *directory,
331
GMenuTreeDirectory *alias_directory)
337
menuitem = create_submenu_entry (menu, alias_directory);
339
menuitem = create_submenu_entry (menu, directory);
341
submenu = create_fake_menu (directory);
344
gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu);
347
void create_header (GtkWidget *menu,
348
GMenuTreeHeader *header)
350
GMenuTreeDirectory *directory;
353
directory = gmenu_tree_header_get_directory (header);
354
menuitem = create_submenu_entry (menu, directory);
355
gmenu_tree_item_unref (directory);
357
g_object_set_data_full (G_OBJECT (menuitem),
358
"panel-gmenu-tree.header",
359
gmenu_tree_item_ref (header),
360
(GDestroyNotify) gmenu_tree_item_unref);
362
g_signal_connect (menuitem, "activate",
363
G_CALLBACK (gtk_false), NULL);
366
void create_menuitem (GtkWidget *menu,
367
GMenuTreeEntry *entry,
368
GMenuTreeDirectory *alias_directory)
372
menuitem = gtk_image_menu_item_new ();
374
g_object_set_data_full (G_OBJECT (menuitem),
375
"panel-menu-tree-entry",
376
gmenu_tree_item_ref (entry),
377
(GDestroyNotify) gmenu_tree_item_unref);
380
//FIXME: we should probably use this data when we do dnd or
381
//context menu for this menu item
382
g_object_set_data_full (G_OBJECT (menuitem),
383
"panel-menu-tree-alias-directory",
384
gmenu_tree_item_ref (alias_directory),
385
(GDestroyNotify) gmenu_tree_item_unref);
387
panel_load_menu_image_deferred (menuitem,
388
myData.iPanelDefaultMenuIconSize, //panel_menu_icon_get_size (),
390
alias_directory ? gmenu_tree_directory_get_icon (alias_directory) :
391
gmenu_tree_entry_get_icon (entry),
394
setup_menuitem (menuitem,
395
myData.iPanelDefaultMenuIconSize, //panel_menu_icon_get_size (),
397
alias_directory ? gmenu_tree_directory_get_name (alias_directory) :
398
gmenu_tree_entry_get_name (entry));
400
if ((alias_directory &&
401
gmenu_tree_directory_get_comment (alias_directory)) ||
403
gmenu_tree_entry_get_comment (entry)))
404
panel_util_set_tooltip_text (menuitem,
406
gmenu_tree_directory_get_comment (alias_directory) :
407
gmenu_tree_entry_get_comment (entry));
409
/*g_signal_connect_after (menuitem, "button_press_event",
410
G_CALLBACK (menuitem_button_press_event), NULL);*/
412
//if (!panel_lockdown_get_locked_down ()) {
414
static GtkTargetEntry menu_item_targets[] = {
415
{ (gchar*)"text/uri-list", 0, 0 }
418
gtk_drag_source_set (menuitem,
419
GDK_BUTTON1_MASK | GDK_BUTTON2_MASK,
420
menu_item_targets, 1,
423
if (gmenu_tree_entry_get_icon (entry) != NULL) {
427
icon = gmenu_tree_entry_get_icon (entry);
428
if (!g_path_is_absolute (icon)) {
429
icon_no_ext = panel_util_icon_remove_extension (icon);
430
gtk_drag_source_set_icon_name (menuitem,
432
g_free (icon_no_ext);
436
///g_signal_connect (G_OBJECT (menuitem), "drag_begin",
437
/// G_CALLBACK (drag_begin_menu_cb), NULL);
438
g_signal_connect (menuitem, "drag_data_get",
439
G_CALLBACK (drag_data_get_menu_cb), entry);
440
///g_signal_connect (menuitem, "drag_end",
441
/// G_CALLBACK (drag_end_menu_cb), NULL);
444
gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem);
446
g_signal_connect (menuitem, "activate",
447
G_CALLBACK (activate_app_def), entry);
449
gtk_widget_show (menuitem);
452
void create_menuitem_from_alias (GtkWidget *menu,
453
GMenuTreeAlias *alias)
455
GMenuTreeItem *aliased_item;
457
aliased_item = gmenu_tree_alias_get_item (alias);
459
switch (gmenu_tree_item_get_type (aliased_item)) {
460
case GMENU_TREE_ITEM_DIRECTORY:
461
create_submenu (menu,
462
GMENU_TREE_DIRECTORY (aliased_item),
463
gmenu_tree_alias_get_directory (alias));
466
case GMENU_TREE_ITEM_ENTRY:
467
create_menuitem (menu,
468
GMENU_TREE_ENTRY (aliased_item),
469
gmenu_tree_alias_get_directory (alias));
476
gmenu_tree_item_unref (aliased_item);
481
image_menuitem_size_request (GtkWidget *menuitem,
482
GtkRequisition *requisition,
485
GtkIconSize icon_size = (GtkIconSize) GPOINTER_TO_INT (data);
489
if (!gtk_icon_size_lookup (icon_size, NULL, &icon_height))
492
// If we don't have a pixmap for this menuitem
493
// at least make sure its the same height as
495
// This is a bit ugly, since we should keep this in sync with what's in
496
// gtk_menu_item_size_request()
497
req_height = icon_height;
498
req_height += (GTK_CONTAINER (menuitem)->border_width +
499
menuitem->style->thickness) * 2;
500
requisition->height = MAX (requisition->height, req_height);
502
void setup_menuitem (GtkWidget *menuitem,
503
GtkIconSize icon_size,
511
/* this creates a label with an invisible mnemonic */
512
label = g_object_new (GTK_TYPE_ACCEL_LABEL, NULL);
513
_title = menu_escape_underscores_and_prepend (title);
514
gtk_label_set_text_with_mnemonic (GTK_LABEL (label), _title);
517
gtk_label_set_pattern (GTK_LABEL (label), "");
519
gtk_accel_label_set_accel_widget (GTK_ACCEL_LABEL (label), menuitem);
521
gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
522
gtk_widget_show (label);
524
gtk_container_add (GTK_CONTAINER (menuitem), label);
527
g_object_set_data_full (G_OBJECT (menuitem),
529
g_object_ref (image),
530
(GDestroyNotify) g_object_unref);
531
gtk_widget_show (image);
532
_gtk_image_menu_item_set_image (
533
GTK_IMAGE_MENU_ITEM (menuitem), image);
534
} else if (icon_size != GTK_ICON_SIZE_INVALID)
535
g_signal_connect (menuitem, "size_request",
536
G_CALLBACK (image_menuitem_size_request),
537
GINT_TO_POINTER (icon_size));
539
gtk_widget_show (menuitem);
542
GtkWidget * populate_menu_from_directory (GtkWidget *menu,
543
GMenuTreeDirectory *directory)
547
gboolean add_separator;
549
///add_separator = (GTK_MENU_SHELL (menu)->children != NULL);
550
GList *children = gtk_container_get_children (GTK_CONTAINER (menu));
551
add_separator = (children != NULL);
552
g_list_free (children); // not very optimized ...
554
items = gmenu_tree_directory_get_contents (directory);
556
for (l = items; l; l = l->next) {
557
GMenuTreeItem *item = l->data;
560
gmenu_tree_item_get_type (item) == GMENU_TREE_ITEM_SEPARATOR) {
561
add_menu_separator (menu);
562
add_separator = FALSE;
565
switch (gmenu_tree_item_get_type (item)) {
566
case GMENU_TREE_ITEM_DIRECTORY:
567
create_submenu (menu, GMENU_TREE_DIRECTORY (item), NULL);
570
case GMENU_TREE_ITEM_ENTRY:
571
create_menuitem (menu, GMENU_TREE_ENTRY (item), NULL);
574
case GMENU_TREE_ITEM_SEPARATOR :
578
case GMENU_TREE_ITEM_ALIAS:
579
create_menuitem_from_alias (menu, GMENU_TREE_ALIAS (item));
582
case GMENU_TREE_ITEM_HEADER:
583
create_header (menu, GMENU_TREE_HEADER (item));
590
gmenu_tree_item_unref (item);
593
g_slist_free (items);
600
/**void icon_theme_changed (GtkIconTheme *icon_theme,
603
reload_image_menu_items ();
606
static inline GtkWidget * panel_create_menu (void)
609
static gboolean registered_icon_theme_changer = FALSE;
611
if (!registered_icon_theme_changer) {
612
registered_icon_theme_changer = TRUE;
614
g_signal_connect (gtk_icon_theme_get_default (), "changed",
615
G_CALLBACK (icon_theme_changed), NULL);
618
retval = gtk_menu_new ();
622
GtkWidget * create_empty_menu (void)
626
///retval = panel_create_menu ();
627
retval = gtk_menu_new ();
632
static void _on_remove_tree (GMenuTree *tree)
634
cd_message ("%s (%x)", __func__, tree);
635
//gmenu_tree_unref (tree);
638
/* REM: if this function is used in a thread (myData.bLaunchInThread == TRUE)
639
* submenu_to_display should be launched after having set data to the menu
640
* for keys "panel-menu-append-callback*"
642
GtkWidget * create_applications_menu (const char *menu_file,
643
const char *menu_path, GtkWidget *parent_menu)
649
menu = (parent_menu ? parent_menu : create_empty_menu ());
651
cd_message ("%s (%s)", __func__, menu_file);
652
tree = gmenu_tree_lookup (menu_file, GMENU_TREE_FLAGS_NONE);
653
cd_debug (" tree : %x", tree);
655
g_object_set_data_full (G_OBJECT (menu),
657
gmenu_tree_ref (tree),
658
(GDestroyNotify) _on_remove_tree);
660
g_object_set_data_full (G_OBJECT (menu),
661
"panel-menu-tree-path",
662
g_strdup (menu_path ? menu_path : "/"),
663
(GDestroyNotify) g_free);
665
g_object_set_data (G_OBJECT (menu),
666
"panel-menu-needs-loading",
667
GUINT_TO_POINTER (TRUE));
669
// load the menu in idle, and force the loading if it's shown before.
670
g_signal_connect (menu, "show",
671
G_CALLBACK (submenu_to_display), NULL);
673
if (! myData.bLoadInThread)
675
idle_id = g_idle_add_full (G_PRIORITY_LOW,
676
submenu_to_display_in_idle,
679
g_object_set_data_full (G_OBJECT (menu),
680
"panel-menu-idle-id",
681
GUINT_TO_POINTER (idle_id),
682
remove_submenu_to_display_idle); // => g_source_remove (idle_id);
684
// else: submenu_to_display should be launched after...
686
gmenu_tree_add_monitor (tree,
687
(GMenuTreeChangedFunc) handle_gmenu_tree_changed,
689
g_signal_connect (menu, "destroy",
690
G_CALLBACK (remove_gmenu_tree_monitor), tree);
692
gmenu_tree_unref (tree);
697
// $XDG_CONFIG_DIRS => /etc/xdg/xdg-cairo-dock:/etc/xdg
698
// http://developer.gnome.org/menu-spec/
699
gchar ** cd_gmenu_get_xdg_menu_dirs (void)
701
const gchar *cMenuPrefix = g_getenv ("XDG_CONFIG_DIRS");
702
if (! cMenuPrefix || *cMenuPrefix == '\0')
703
cMenuPrefix = "/etc/xdg/menus";
705
return g_strsplit (cMenuPrefix, ":", 0);
708
// check if the file exists and if yes, *cMenuName is created
709
gboolean _check_file_exists (const gchar *cDir, const gchar *cPrefix, gchar **cMenuName)
711
gchar *cMenuFilePathWithPrefix = g_strdup_printf ("%s/menus/%sapplications.menu", cDir, cPrefix);
713
gboolean bFileExists = g_file_test (cMenuFilePathWithPrefix, G_FILE_TEST_EXISTS);
715
*cMenuName = g_strdup_printf ("%sapplications.menu", cPrefix);
717
cd_debug ("Check: %s: %d", cMenuFilePathWithPrefix, bFileExists);
718
g_free (cMenuFilePathWithPrefix);
722
static const gchar *cPrefixNames[] = {"", "gnome-", "kde-", "kde4-", "xfce-", "lxde-", NULL};
724
GtkWidget * create_main_menu (CairoDockModuleInstance *myApplet)
726
GtkWidget *main_menu;
728
gchar *cMenuFileName = NULL, *cXdgMenuPath = NULL;
729
const gchar *cMenuPrefix = g_getenv ("XDG_MENU_PREFIX"); // e.g. on xfce, it contains "xfce-", nothing on gnome
730
gchar **cXdgPath = cd_gmenu_get_xdg_menu_dirs ();
733
for (i = 0; cXdgPath[i] != NULL; i++)
735
g_free (cXdgMenuPath);
736
cXdgMenuPath = g_strdup_printf ("%s/menus", cXdgPath[i]);
737
if (! g_file_test (cXdgMenuPath, G_FILE_TEST_IS_DIR)) // cXdgPath can contain an invalid dir
740
// this test should be the good one: with or without the prefix
741
if (_check_file_exists (cXdgPath[i], cMenuPrefix ? cMenuPrefix : "", &cMenuFileName))
744
// let's check with common prefixes
745
for (int iPrefix = 0; cPrefixNames[iPrefix] != NULL; iPrefix++)
747
if (_check_file_exists (cXdgPath[i], cPrefixNames[iPrefix], &cMenuFileName))
751
if (cMenuFileName == NULL) // let's check any *-applications.menu
753
const gchar *cFileName;
754
GDir *dir = g_dir_open (cXdgPath[i], 0, NULL);
757
while ((cFileName = g_dir_read_name (dir)))
759
if (g_str_has_suffix (cFileName, "-applications.menu"))
761
cMenuFileName = g_strdup (cFileName);
766
if (cMenuFileName != NULL)
772
cd_debug ("Menu: Found %s in %s (%s)", cMenuFileName, cXdgPath[i], cXdgMenuPath);
774
if (cMenuFileName == NULL) // arf
775
cMenuFileName = g_strdup ("applications.menu");
777
main_menu = create_applications_menu (cMenuFileName, NULL, NULL);
779
g_object_set_data (G_OBJECT (main_menu),
780
"panel-menu-append-callback",
782
g_object_set_data (G_OBJECT (main_menu),
783
"panel-menu-append-callback-data",
786
if (myData.bLoadInThread) // load submenu in a thread
787
submenu_to_display (main_menu);
789
g_strfreev (cXdgPath);
790
g_free (cMenuFileName);
791
g_free (cXdgMenuPath);