menu.c 106 KB
Newer Older
1 2
/*
 * GNOME panel menu module.
3
 * (C) 1997, 1998, 1999, 2000 The Free Software Foundation
4 5
 * Copyright 2000 Helix Code, Inc.
 * Copyright 2000 Eazel, Inc.
6 7 8 9 10 11
 *
 * Authors: Miguel de Icaza
 *          Federico Mena
 */

#include <config.h>
12
#include <ctype.h>
13
#include <stdio.h>
14
#include <sys/types.h>
15
#include <sys/stat.h>
16
#include <sys/wait.h>
17
#include <fcntl.h>
18
#include <dirent.h>
19
#include <unistd.h>
20
#include <string.h>
21
#include <limits.h>
22
#include <errno.h>
23
#include <math.h>
Mark McLoughlin's avatar
Mark McLoughlin committed
24

25
#include <gdk/gdkkeysyms.h>
26
#include <libgnome/libgnome.h>
27
#include <libgnome/gnome-desktop-item.h>
28
#include <libgnomeui/libgnomeui.h>
29
#include <libgnomeui/gnome-ditem-edit.h>
Mark McLoughlin's avatar
Mark McLoughlin committed
30
#include <gconf/gconf-client.h>
31
#include <libbonobo.h>
32

33
#include <libgnomevfs/gnome-vfs-mime.h>
34 35 36 37
#include <libgnomevfs/gnome-vfs-uri.h>
#include <libgnomevfs/gnome-vfs-ops.h>
#include <libgnomevfs/gnome-vfs-utils.h>

Mark McLoughlin's avatar
Mark McLoughlin committed
38 39 40 41 42 43 44
#include "aligned-widget.h"
#include "button-widget.h"
#include "distribution.h"
#include "drawer-widget.h"
#include "edge-widget.h"
#include "floating-widget.h"
#include "foobar-widget.h"
45
#include "gnome-run.h"
Mark McLoughlin's avatar
Mark McLoughlin committed
46 47
#include "launcher.h"
#include "logout.h"
48
#include "nothing.h"
Mark McLoughlin's avatar
Mark McLoughlin committed
49 50 51
#include "menu-fentry.h"
#include "menu-properties.h"
#include "menu-util.h"
52
#include "menu-ditem.h"
53
#include "multiscreen-stuff.h"
Mark McLoughlin's avatar
Mark McLoughlin committed
54
#include "panel-util.h"
55
#include "panel-gconf.h"
Mark McLoughlin's avatar
Mark McLoughlin committed
56
#include "panel-main.h"
Mark McLoughlin's avatar
Mark McLoughlin committed
57
#include "panel.h"
58
#include "panel-config-global.h"
Mark McLoughlin's avatar
Mark McLoughlin committed
59 60 61 62
#include "session.h"
#include "sliding-widget.h"
#include "status.h"
#include "swallow.h"
63
#include "panel-applet-frame.h"
64
#include "quick-desktop-reader.h"
65
#include "xstuff.h"
66

67
#undef MENU_DEBUG
68

69
#define ICON_SIZE 20
70

71
static char *gnome_folder = NULL;
72

73
extern GSList *applets;
74

75
/*list of all toplevel panel widgets (basep) created*/
76
extern GSList *panel_list;
77 78
/*list of all PanelWidgets created*/
extern GSList *panels;
79

80 81
extern gboolean commie_mode;
extern gboolean no_run_box;
82 83
extern GlobalConfig global_config;

84 85 86
extern int panels_to_sync;
extern int need_complete_save;

87 88
extern int base_panels;

89 90 91
extern char *kde_menudir;
extern char *kde_icondir;

92

Jacob Berkman's avatar
Jacob Berkman committed
93 94
extern GtkTooltips *panel_tooltips;

95 96 97 98 99 100 101 102 103 104
typedef struct _ShowItemMenu ShowItemMenu;
struct _ShowItemMenu {
	int type;
	const char *item_loc;
	MenuFinfo *mf;
	GtkWidget *menu;
	GtkWidget *menuitem;
	int applet;
};

105
typedef struct {
106
	GtkWidget *pixmap;
107 108
	char *image;
	char *fallback_image;
109
	gboolean force_image;
110
	guint size;
111
} IconToLoad;
112 113

static guint load_icons_id = 0;
114
static GHashTable *loaded_icons = NULL;
115 116
static GList *icons_to_load = NULL;

117 118
static GtkWidget * create_menu_at_fr (GtkWidget *menu,
				      FileRec *fr,
119
				      gboolean launcher_add,
120 121 122
				      const char *dir_name,
				      const char *pixmap_name,
				      gboolean fake_submenus,
123
				      gboolean force);
124 125 126 127
static GtkWidget * create_panel_submenu (GtkWidget *m,
					 gboolean fake_sub,
					 gboolean is_base);
static GtkWidget * create_desktop_menu (GtkWidget *m,
128
					gboolean fake_sub);
129

130 131
static void add_kde_submenu (GtkWidget *root_menu,
			     gboolean fake_submenus,
132
			     gboolean launcher_add);
133 134
static void add_distribution_submenu (GtkWidget *root_menu,
				      gboolean fake_submenus,
135
				      gboolean launcher_add);
136 137 138

static GtkWidget * create_add_launcher_menu (GtkWidget *menu,
					     gboolean fake_submenus);
139 140 141

static void setup_menuitem_try_pixmap (GtkWidget *menuitem,
				       const char *try_file,
142
				       const char *title);
143

144

145
static inline gboolean
146 147
panel_menu_have_icons (void)
{
148 149 150 151
	return gconf_client_get_bool (
			panel_gconf_get_client (),
			"/desktop/gnome/interface/menus_have_icons",
			NULL);
152 153
}

154 155 156 157
/*to be called on startup to load in some of the directories,
  this makes the startup a little bit slower, and take up slightly
  more ram, but it also speeds up later operation*/
void
158
init_menus (void)
159
{
160
	const DistributionInfo *distribution_info = get_distribution_info ();
161
	char *menu;
162

163 164 165
	/*just load the menus from disk, don't make the widgets
	  this just reads the .desktops of the top most directory
	  and a level down*/
166
	fr_read_dir (NULL, "applications:/", 0, 2);
167

168 169
	menu = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_DATADIR, 
					  "applets", TRUE, NULL);
170 171 172 173 174
	if (menu != NULL) {
		char *uri = gnome_vfs_get_uri_from_local_path (menu);
		fr_read_dir (NULL, uri, 0, 2);
		g_free (uri);
	}
175 176 177 178
	g_free (menu);

	if (distribution_info != NULL &&
	    distribution_info->menu_init_func != NULL)
179
		distribution_info->menu_init_func ();
180
}
181

182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
static gboolean
check_for_screen (GtkWidget *w, GdkEvent *ev, gpointer data)
{
	static int times = 0;
	if (ev->type != GDK_KEY_PRESS)
		return FALSE;
	if (ev->key.keyval == GDK_f ||
	    ev->key.keyval == GDK_F) {
		times++;
		if (times == 3) {
			times = 0;
			start_screen_check ();
		}
	}
	return FALSE;
}

Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
199
/*the most important dialog in the whole application*/
200
static void
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
201 202
about_cb (GtkWidget *widget, gpointer data)
{
203
	static GtkWidget *about;
204
	GtkWidget *hbox, *l;
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
205 206 207
	GdkPixbuf *logo;
	GString *comment;
	char *logo_file;
208
	/* FIXME: fill in all the wankers who did stuff */
George Lebl's avatar
George Lebl committed
209
	char *authors[] = {
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
210
	  "George Lebl (jirka@5z.com)",
211
	  "Jacob Berkman (jberkman@andrew.cmu.edu)",
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
212 213 214
	  "Miguel de Icaza (miguel@kernel.org)",
	  "Federico Mena (quartic@gimp.org)",
	  "Tom Tromey (tromey@cygnus.com)",
George Lebl's avatar
George Lebl committed
215
	  "Ian Main (imain@gtk.org)",
216
	  "Elliot Lee (sopwith@redhat.com)",
217
	  "Owen Taylor (otaylor@redhat.com)",
218 219 220 221 222 223 224 225 226
	  "Mark McLoughlin (mark@skynet.ie)",
	  "Alex Larsson (alexl@redhat.com)",
	  "Martin Baulig (baulig@suse.de)",
	  "Seth Nickell (snickell@stanford.edu)",
	  "Darin Adler (darin@bentspoon.com)",
	  "Glynn Foster (glynn.foster@sun.com)",
	  "Stephen Browne (stephen.browne@sun.com)",
	  "Anders Carlsson (andersca@gnu.org)",
	  "Padraig O'Briain (padraig.obriain@sun.com)",
227 228 229
	N_("Many many others ..."),
	/* ... from the Monty Pythons show...  */
	N_("and finally, The Knights Who Say ... NI!"),
230
	  NULL
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
231
	  };
232
	char *documenters[] = {
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
233 234 235
		"Dave Mason (dcm@redhat.com)",
		"Dan Mueth (d-mueth@uchicago.edu)",
	        "Alexander Kirillov (kirillov@math.sunysb.edu)",
236 237
		NULL
	  };
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
238 239
	/* Translator credits */
	char *translator_credits = _("translator_credits");
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
240

Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
241
	if (about != NULL) {
242
		gtk_window_present (GTK_WINDOW (about));
243 244 245
		return;
	}

246 247 248 249 250 251 252
	{
		int i=0;
		while (authors[i] != NULL) {
		       	authors[i]=_(authors[i]);
			i++;
		}
	}
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272

	logo = NULL;
	logo_file = panel_pixmap_discovery ("gnome-gegl2.png",
					    FALSE /* fallback */);
	if (logo_file != NULL) {
		logo = gdk_pixbuf_new_from_file (logo_file, NULL /* error */);
		g_free (logo_file);
	}

	comment = g_string_new (_("This program is responsible for launching "
				  "other applications, embedding small applets "
				  "within itself, world peace, and random X crashes."));

	if (commie_mode) {
		g_string_append (comment,
				 _("\n\nRunning in \"Lockdown\" mode.  This "
				   "means your system administrator has "
				   "prohibited any changes to the panel's "
				   "configuration to take place."));
	}
273
	
274
	about = gnome_about_new ( _("The GNOME Panel"), VERSION,
275
			_("(C) 1997-2002 the Free Software Foundation"),
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
276
			comment->str,
277 278
			(const char **)authors,
			(const char **)documenters,
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
279 280 281 282 283 284
			strcmp (translator_credits, "translator_credits") != 0 ? translator_credits : NULL,
			logo);

	g_object_unref (G_OBJECT (logo));
	g_string_free (comment, TRUE);

285
	gtk_window_set_wmclass (GTK_WINDOW (about), "about_dialog", "Panel");
286 287
	g_signal_connect (G_OBJECT (about), "destroy",
			    G_CALLBACK (gtk_widget_destroyed),
288
			    &about);
289
	g_signal_connect (G_OBJECT (about), "event",
290
			  G_CALLBACK (check_for_screen), NULL);
291 292 293 294 295

	hbox = gtk_hbox_new (TRUE, 0);
	l = gnome_href_new ("http://www.wfp.org/",
			    _("End world hunger"));
	gtk_box_pack_start (GTK_BOX (hbox), l, FALSE, FALSE, 0);
296
	gtk_box_pack_start (GTK_BOX (GTK_DIALOG (about)->vbox),
297 298 299
			    hbox, TRUE, FALSE, 0);
	gtk_widget_show_all (hbox);

Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
300 301 302
	gtk_widget_show (about);
}

303
static void
304 305
about_gnome_cb(GtkObject *object, char *program_path)
{
306
	if (gnome_execute_async (g_get_home_dir (), 1, &program_path)<0)
307 308
		panel_error_dialog ("cannot_exec_about_gnome",
				    _("Can't execute 'About GNOME'"));
309 310
}

311
static void
312
activate_app_def (GtkWidget *widget, const char *item_loc)
313
{
314
	GError *error = NULL;
315
	GnomeDesktopItem *item = gnome_desktop_item_new_from_uri
316
		(item_loc,
317
		 GNOME_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS,
318
		 &error);
319
	if (item != NULL) {
320 321 322
		gnome_desktop_item_launch (item,
					   NULL /* file_list */,
					   0 /* flags */,
323 324
					   &error);
		if (error != NULL) {
325
			panel_error_dialog ("cant_launch_entry",
326 327 328 329
					    _("<b>Can't launch entry</b>\n\n"
					      "Details: %s"), error->message);
			g_clear_error (&error);
		}
330
		gnome_desktop_item_unref (item);
331
	} else {
332 333 334 335 336
		g_assert (error != NULL);
		panel_error_dialog ("cant_load_entry",
				    _("<b>Can't load entry</b>\n\n"
				      "Details: %s"), error->message);
		g_clear_error (&error);
337
	}
338 339
}

340
static PanelWidget *
341
get_panel_from_menu_data(GtkWidget *menu, gboolean must_have)
342 343 344 345
{
	g_return_val_if_fail (menu != NULL, NULL);
	g_return_val_if_fail (GTK_IS_MENU(menu) || GTK_IS_MENU_ITEM(menu),
			      NULL);
346

347 348
	if(GTK_IS_MENU_ITEM(menu))
		menu = menu->parent;
349

350
	while(menu) {
351 352
		PanelWidget *panel = g_object_get_data (G_OBJECT (menu),
							"menu_panel");
353

354
		if (panel != NULL) {
355
			if(PANEL_IS_WIDGET(panel))
356 357 358 359
				return panel;
			else
				g_warning("Menu is on crack");
		}
360
		menu = gtk_menu_get_attach_widget (GTK_MENU (menu))->parent;
361 362 363
		/* only GtkMenu's qualify */
		if ( ! GTK_IS_MENU (menu))
			menu = NULL;
364
	}
365 366 367 368 369 370 371
	if (must_have) {
		g_warning("Something went quite terribly wrong and we can't "
			  "find where this menu belongs");
		return panels->data;
	} else {
		return NULL;
	}
372 373 374
}

static void
375
setup_menu_panel (GtkWidget *menu)
376
{
377 378
	PanelWidget *menu_panel = g_object_get_data (G_OBJECT (menu),
						     "menu_panel");
379 380
	if (menu_panel != NULL) {
		menu_panel = get_panel_from_menu_data (menu, TRUE);
381
		g_object_set_data (G_OBJECT (menu), "menu_panel", menu_panel);
382 383 384
	}
}

385
static void
386 387 388 389
menus_have_icons_changed (GConfClient *client,
			  guint        cnxn_id,
			  GConfEntry  *entry,
			  GtkWidget   *menu)
390
{
391 392 393 394 395
	GConfValue *value;
	GList      *list, *l;
	gboolean    have_icons = TRUE;

	value = gconf_entry_get_value (entry);
396 397

	if (value->type == GCONF_VALUE_BOOL)
398
		have_icons = gconf_value_get_bool (value);
399 400

	list = g_list_copy (GTK_MENU_SHELL (menu)->children);
401 402
	for (l = list; l; l = l->next) {
		GtkWidget *item = l->data;
403 404 405
		GtkWidget *cur_image;
		GtkWidget *image;

406
		if (!GTK_IS_IMAGE_MENU_ITEM (item))
407 408 409
			continue;

		image = g_object_get_data (G_OBJECT (item), "Panel:Image");
410
		if (!image)
411 412 413
			continue;

		/* A forced image is always on */
414
		if (g_object_get_data (G_OBJECT (item), "Panel:ForceImage"))
415 416
			continue;

417 418
		cur_image = gtk_image_menu_item_get_image (
					GTK_IMAGE_MENU_ITEM (item));
419

420
		if (have_icons) {
421
			if (cur_image != image) {
422 423
				gtk_image_menu_item_set_image (
					GTK_IMAGE_MENU_ITEM (item), image);
424 425 426
				gtk_widget_show (image);
			}
		} else {
427 428
			gtk_image_menu_item_set_image (
				GTK_IMAGE_MENU_ITEM (item), NULL);
429 430 431 432 433
		}
	}
	g_list_free (list);
}

434 435 436 437 438 439
typedef struct
{
	GtkMenuPositionFunc orig_func;
	gpointer orig_data;
} MenuReposition;

Mark McLoughlin's avatar
Mark McLoughlin committed
440
/* FIXME:
441
 * Stolen mostly from GTK+ and modified for our purposes of multiscreen
Mark McLoughlin's avatar
Mark McLoughlin committed
442 443 444 445 446
 * things.  Kind of evil, but oh well
 *
 * This is deprecated in gtk now, that's why we need the GTK_MENU_INTERNALS
 * define in Makefile.am
 */
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648
static void
our_menu_item_position_menu (GtkMenu  *menu,
			     int       screen_width,
			     int       screen_height,
			     int       screen_basex,
			     int       screen_basey,
			     int      *x,
			     int      *y)
{
	GtkMenuItem *menu_item;
	GtkWidget *widget;
	GtkWidget *parent_menu_item;
	int twidth, theight;
	int tx, ty;

	g_return_if_fail (menu != NULL);
	g_return_if_fail (x != NULL);
	g_return_if_fail (y != NULL);

	menu_item = GTK_MENU_ITEM (menu->parent_menu_item);
	widget = GTK_WIDGET (menu_item);

	twidth = GTK_WIDGET (menu)->requisition.width;
	theight = GTK_WIDGET (menu)->requisition.height;

	if ( ! gdk_window_get_origin (widget->window, &tx, &ty)) {
		g_warning ("Menu not on screen");
		return;
	}

	tx -= screen_basex;
	ty -= screen_basey;

	tx += widget->allocation.x;
	ty += widget->allocation.y;

	switch (menu_item->submenu_placement)
	{
	case GTK_TOP_BOTTOM:
		if ((ty + widget->allocation.height + theight) <= screen_height)
			ty += widget->allocation.height;
		else if ((ty - theight) >= 0)
			ty -= theight;
		else if (screen_height - (ty + widget->allocation.height) > ty)
			ty += widget->allocation.height;
		else
			ty -= theight;
		break;

	case GTK_LEFT_RIGHT:
		menu_item->submenu_direction = GTK_DIRECTION_RIGHT;
		parent_menu_item = GTK_MENU (widget->parent)->parent_menu_item;
		if (parent_menu_item)
			menu_item->submenu_direction = GTK_MENU_ITEM (parent_menu_item)->submenu_direction;

		switch (menu_item->submenu_direction)
		{
		case GTK_DIRECTION_LEFT:
			if ((tx - twidth) >= 0)
				tx -= twidth;
			else
			{
				menu_item->submenu_direction = GTK_DIRECTION_RIGHT;
				tx += widget->allocation.width - 5;
			}
			break;

		case GTK_DIRECTION_RIGHT:
			if ((tx + widget->allocation.width + twidth - 5) <= screen_width)
				tx += widget->allocation.width - 5;
			else
			{
				menu_item->submenu_direction = GTK_DIRECTION_LEFT;
				tx -= twidth;
			}
			break;
		}

		ty += widget->allocation.height / 4;

		/* If the height of the menu doesn't fit we move it upward. */
		ty = CLAMP (ty, 0, MAX (0, screen_height - theight));
		break;
	}

	/* If we have negative, tx, here it is because we can't get
	 * the menu all the way on screen. Favor the left portion.
	 */
	tx = CLAMP (tx, 0, MAX (0, screen_width - twidth));

	*x = tx + screen_basex;
	*y = ty + screen_basey;
}

static void
menu_on_screen (GtkMenu  *menu,
		gint     *x,
		gint     *y,
		gboolean *push_in,
		gpointer  data)
{
	MenuReposition *repo = data;
	int screen;
	int screen_width, screen_height, screen_basex, screen_basey;
	GtkRequisition req;

	PanelWidget *menu_panel = get_panel_from_menu_data
		(GTK_WIDGET (menu), FALSE /* must_have */);

	gtk_widget_get_child_requisition (GTK_WIDGET (menu), &req);

	if (menu_panel != NULL) {
		screen = multiscreen_screen_from_panel
			(menu_panel->panel_parent);
	} else {
		screen = multiscreen_screen_from_pos (*x, *y);
	}

	if (screen < 0) {
		screen_width = gdk_screen_width ();
		screen_height = gdk_screen_height ();
		screen_basex = 0;
		screen_basey = 0;
	} else {
		screen_width = multiscreen_width (screen);
		screen_height = multiscreen_height (screen);
		screen_basex = multiscreen_x (screen);
		screen_basey = multiscreen_y (screen);
	}

	if (repo->orig_func != NULL) {
		repo->orig_func (menu, x, y, push_in, repo->orig_data);

		if (menu->parent_menu_item != NULL) {
			/* This is a submenu so behave submenuish */
			if (*x < screen_basex ||
			    *x + req.width > screen_basex + screen_width ||
			    *y < screen_basey ||
			    *y + req.height > screen_basey + screen_height) {
				/* Offscreen! EVIL, ignore the position
				 * and recalculate using our hack */
				our_menu_item_position_menu (menu,
							     screen_width,
							     screen_height,
							     screen_basex,
							     screen_basey,
							     x,
							     y);
			}
		} else {
			/* just make sure the menu is within screen */
			*x -= screen_basex;
			*y -= screen_basey;

			if ((*x + req.width) > screen_width)
				*x -= ((*x + req.width) - screen_width);
			if (*x < 0)
				*x = 0;
			if ((*y + req.height) > screen_height)
				*y -= ((*y + req.height) - screen_height);
			if (*y < 0)
				*y = 0;

			*x += screen_basex;
			*y += screen_basey;
		}
	} else {
		*x -= screen_basex;
		*y -= screen_basey;
		*x = CLAMP (*x - 2, 0, MAX (0, screen_width - req.width));
		*y = CLAMP (*y - 2, 0, MAX (0, screen_height - req.height));
		*x += screen_basex;
		*y += screen_basey;
		*push_in = TRUE;
	}
}

void
panel_make_sure_menu_within_screen (GtkMenu *menu)
{
	MenuReposition *repo;

	/* if already set to a standard pos func, just ignore */
	if (menu->position_func == menu_on_screen ||
	    menu->position_func == panel_menu_position ||
	    menu->position_func == applet_menu_position)
		return;

	repo = g_new0 (MenuReposition, 1);
	g_object_weak_ref (G_OBJECT (menu),
			   (GWeakNotify) g_free,
			   repo);

	repo->orig_func = menu->position_func;
	repo->orig_data = menu->position_func_data;

	menu->position_func = menu_on_screen;
	menu->position_func_data = repo;

	our_gtk_menu_position (menu);
}

649 650 651 652
GtkWidget *
panel_menu_new (void)
{
	GtkWidget *menu;
653

654
	menu = gtk_menu_new ();
655 656 657

	panel_gconf_notify_add_while_alive ("/desktop/gnome/interface/menus_have_icons",
					    (GConfClientNotifyFunc) menus_have_icons_changed,
658
					    G_OBJECT (menu));
659 660 661 662 663

	g_signal_connect_after (menu, "show",
				G_CALLBACK (panel_make_sure_menu_within_screen),
				NULL);

664 665 666
	return menu;
}

667
static GtkWidget *
668
menu_new (void)
669 670
{
	GtkWidget *ret;
671
	ret = panel_menu_new ();
672 673
	g_signal_connect (G_OBJECT (ret), "show",
			  G_CALLBACK (setup_menu_panel), NULL);
674 675 676 677

	return ret;
}

678 679 680 681
static void
icon_to_load_free (IconToLoad *icon)
{
	if (icon != NULL) {
682 683
	        g_object_unref (G_OBJECT (icon->pixmap));
	        icon->pixmap = NULL;
684 685 686 687 688 689 690 691 692 693 694 695 696 697
	        g_free (icon->image);
	        icon->image = NULL;
		g_free (icon->fallback_image);
		icon->fallback_image = NULL;
		g_free (icon);
	}
}

static IconToLoad *
icon_to_load_copy (IconToLoad *icon)
{
	IconToLoad *new_icon = NULL;
	if (icon != NULL) {
		new_icon = g_new0 (IconToLoad, 1);
698
	        new_icon->pixmap = g_object_ref (G_OBJECT (icon->pixmap));
699 700
	        new_icon->image = g_strdup (icon->image);
		new_icon->fallback_image = g_strdup (icon->fallback_image);
701
		new_icon->force_image = icon->force_image;
702
		new_icon->size = icon->size;
703 704 705 706
	}
	return new_icon;
}

707
static void
708
remove_pixmap_from_loaded (gpointer data, GObject *where_the_object_was)
709
{
710 711 712 713 714 715
	char *key = data;

	if (loaded_icons != NULL)
		g_hash_table_remove (loaded_icons, key);

	g_free (key);
716 717
}

718 719 720 721 722
GdkPixbuf *
panel_make_menu_icon (const char *icon,
		      const char *fallback,
		      int size,
		      gboolean *long_operation)
723
{
724
	GdkPixbuf *pb;
725
	char *file, *key;
726 727
	gboolean loaded;

728 729
	if (long_operation != NULL)
		*long_operation = TRUE;
730

731 732
	file = gnome_desktop_item_find_icon (icon,
					     size /* desired size */,
733
					     0 /* flags */);
734 735 736
	if (file == NULL && fallback != NULL)
		file = gnome_desktop_item_find_icon (fallback,
						     size /* desired size */,
737
						     0 /* flags */);
738 739

	if (file == NULL) {
740 741 742 743
		/* we didn't do anything long/hard */
		if (long_operation != NULL)
			*long_operation = FALSE;
		return NULL;
744 745 746 747 748 749
	}

	pb = NULL;

	loaded = FALSE;

750
	key = g_strdup_printf ("%d:%s", size, file);
751

752
	if (loaded_icons != NULL &&
753
	    (pb = g_hash_table_lookup (loaded_icons, key)) != NULL) {
754
		if (pb != NULL)
755
			g_object_ref (G_OBJECT (pb));
756 757
	}

758
	if (pb == NULL) {
759
		pb = gdk_pixbuf_new_from_file (file, NULL);
760 761
		
		/* add icon to the hash table so we don't load it again */
762 763
		loaded = TRUE;
	}
764

765 766
	if (pb == NULL) {
		g_free (file);
767
		g_free (key);
768
		return NULL;
Jacob Berkman's avatar
Jacob Berkman committed
769
	}
770

771 772 773
	if (loaded &&
	    (gdk_pixbuf_get_width (pb) != size ||
	     gdk_pixbuf_get_height (pb) != size)) {
774
		GdkPixbuf *pb2;
775
		pb2 = gdk_pixbuf_scale_simple (pb, size, size,
776
					       GDK_INTERP_BILINEAR);
777
		g_object_unref (G_OBJECT (pb));
778 779
		pb = pb2;
	}
780

781 782 783 784 785 786 787 788 789 790 791 792 793
	if (loaded) {
		if (loaded_icons == NULL)
			loaded_icons = g_hash_table_new_full
				(g_str_hash, g_str_equal,
				 (GDestroyNotify) g_free,
				 (GDestroyNotify) g_object_unref);
		g_hash_table_replace (loaded_icons,
				      g_strdup (key),
				      g_object_ref (G_OBJECT (pb)));
		g_object_weak_ref (G_OBJECT (pb),
				   (GWeakNotify) remove_pixmap_from_loaded,
				   g_strdup (key));
	} else {
794 795 796 797
		/* we didn't load from disk */
		if (long_operation != NULL)
			*long_operation = FALSE;
	}
Mark McLoughlin's avatar
Mark McLoughlin committed
798

799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
	g_free (file);
	g_free (key);

	return pb;
}

static gboolean
load_icons_handler (gpointer data)
{
	GdkPixbuf *pb;
	IconToLoad *icon;
	gboolean long_operation;

load_icons_handler_again:

	if (icons_to_load == NULL) {
		load_icons_id = 0;
		return FALSE;
	}

	icon = icons_to_load->data;
	icons_to_load = g_list_remove (icons_to_load, icon);

	/* if not visible anymore, just ignore */
	if ( ! GTK_WIDGET_VISIBLE (icon->pixmap)) {
		icon_to_load_free (icon);
		/* we didn't do anything long/hard, so just do this again,
826 827 828
		 * this is fun, don't go back to main loop */
		goto load_icons_handler_again;
	}
829

830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
	pb = panel_make_menu_icon (icon->image,
				   icon->fallback_image,
				   icon->size,
				   &long_operation);
	if (pb == NULL) {
		icon_to_load_free (icon);
		if (long_operation) {
			/* this may have been a long operation so jump back to
			 * the main loop for a while */
			return TRUE;
		} else {
			/* we didn't do anything long/hard, so just do this again,
			 * this is fun, don't go back to main loop */
			goto load_icons_handler_again;
		}
	}

	gtk_image_set_from_pixbuf (GTK_IMAGE (icon->pixmap), pb);
	g_object_unref (G_OBJECT (pb));

Mark McLoughlin's avatar
Mark McLoughlin committed
850
	icon_to_load_free (icon);
851

852 853 854 855 856 857
	if ( ! long_operation) {
		/* we didn't do anything long/hard, so just do this again,
		 * this is fun, don't go back to main loop */
		goto load_icons_handler_again;
	}

Jacob Berkman's avatar
Jacob Berkman committed
858 859
	/* if still more we'll come back */
	return TRUE;
860 861
}

862
static void
863
add_new_app_to_menu (GtkWidget *widget, const char *item_loc)
864
{
865
	panel_new_launcher (item_loc);
866 867
}

868
static void
869
remove_menuitem (GtkWidget *widget, ShowItemMenu *sim)
870
{
871
	char *file;
872 873
	char *dir, *directory_file;
	GnomeDesktopItem *ditem;
874

875 876 877 878
	g_return_if_fail (sim->item_loc != NULL);
	g_return_if_fail (sim->menuitem != NULL);

	gtk_widget_hide (sim->menuitem);
879

880
	if (unlink (sim->item_loc) < 0) {
881
		panel_error_dialog("cant_remove_menu_item",
882 883
				   _("<b>Could not remove the menu item %s</b>\n\n"
				     "Details: %s\n"), 
884
				    sim->item_loc, g_strerror (errno));
885 886 887
		return;
	}

888
	file = g_path_get_basename (sim->item_loc);
889 890
	if (file == NULL) {
		g_warning (_("Could not get file name from path: %s"),
891
			  sim->item_loc);
892 893 894
		return;
	}

895
	dir = g_path_get_dirname (sim->item_loc);
896 897
	if (dir == NULL) {
		g_warning (_("Could not get directory name from path: %s"),
898
			  sim->item_loc);
899
		g_free (file);
900 901
		return;
	}
902

903 904 905 906 907
	directory_file = g_build_path ("/", dir, ".directory", NULL);
	ditem = gnome_desktop_item_new_from_uri (directory_file,
						 0 /* flags */,
						 NULL /* error */);
	g_free (directory_file);
908

909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932
	if (ditem != NULL) {
		char **sort_order = gnome_desktop_item_get_strings (ditem,
								    GNOME_DESKTOP_ITEM_SORT_ORDER);
		if (sort_order != NULL) {
			int i, j;
			j = 0;
			for (i = 0; sort_order[i] != NULL; i++) {
				if (strcmp (file, sort_order[i]) != 0) {
					sort_order[j++] = sort_order[i];
				} else {
					g_free (sort_order[i]);
					sort_order[i] = NULL;
				}
			}
			sort_order[j++] = NULL;
			gnome_desktop_item_set_strings (ditem,
							GNOME_DESKTOP_ITEM_SORT_ORDER,
							sort_order);
			g_strfreev (sort_order);

			gnome_desktop_item_save (ditem,
						 NULL /* under */,
						 TRUE /* force */,
						 NULL /* error */);
933

934 935
			/* ignore errors, it's not at all important if we failed,
			 * no point bothering the user with it. */
936

937 938
		}
		gnome_desktop_item_unref (ditem);
939
	}
940 941
}

942
static void
943 944
add_to_run_dialog (GtkWidget *widget, const char *item_loc)
{
945
	GError *error = NULL;
946
	GnomeDesktopItem *item =
947 948
		gnome_desktop_item_new_from_uri (item_loc,
						 GNOME_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS,
949
						 &error);
950
	if (item != NULL) {
951 952 953 954 955 956 957 958 959 960
		const char *exec;

		exec = gnome_desktop_item_get_string
			(item, GNOME_DESKTOP_ITEM_EXEC);
		if (exec == NULL)
			exec = gnome_desktop_item_get_string
				(item, GNOME_DESKTOP_ITEM_URL);

		if (exec != NULL) {
			show_run_dialog_with_text (exec);
961
		} else {
962 963
			panel_error_dialog ("no_exec_or_url_field",
					    _("No 'Exec' or 'URL' field in entry"));
964
		}
965
		gnome_desktop_item_unref (item);
966
	} else {
967
		g_assert (error != NULL);
968
		panel_error_dialog ("cant_load_entry",
969 970 971
				    _("<b>Can't load entry</b>\n\n"
				      "Details: %s"), error->message);
		g_clear_error (&error);
972 973 974
	}
}

975 976 977
static void
show_help_on (GtkWidget *widget, const char *item_loc)
{
978
	GError *error = NULL;
979
	GnomeDesktopItem *item =
980 981
		gnome_desktop_item_new_from_uri (item_loc,
						 GNOME_DESKTOP_ITEM_LOAD_NO_TRANSLATIONS,
982
						 &error);
983
	if (item != NULL) {
984 985
		const char *docpath = gnome_desktop_item_get_string
			(item, "DocPath");
986 987 988 989 990
		if ( ! panel_show_gnome_kde_help (docpath, &error)) {
			panel_error_dialog ("cannot_show_gnome_kde_help",
					    _("<b>Cannot display help document</b>\n\n"
					      "Details: %s"), error->message);
			g_clear_error (&error);
991
		}
992

993
		gnome_desktop_item_unref (item);
994
	} else {
995
		g_assert (error != NULL);
996
		panel_error_dialog ("cant_load_entry",
997 998 999
				    _("<b>Can't load entry</b>\n\n"
				      "Details: %s"), error->message);
		g_clear_error (&error);
1000 1001 1002
	}
}

1003
static void
1004
add_app_to_panel (GtkWidget *widget, const char *item_loc)
1005
{
1006
	Launcher *launcher;
1007 1008 1009
	PanelWidget *panel = get_panel_from_menu_data (widget, TRUE);
	PanelData *pd;
	int insertion_pos = -1;
1010

1011 1012 1013 1014 1015
	pd = g_object_get_data (G_OBJECT (panel->panel_parent), "PanelData");
	if (pd != NULL)
		insertion_pos = pd->insertion_pos;

	launcher = load_launcher_applet (item_loc, panel, insertion_pos, FALSE, NULL);
1016 1017 1018

	if (launcher != NULL)
		launcher_hoard (launcher);
1019 1020 1021 1022
}


static void
1023 1024
add_drawers_from_dir (const char *dirname, const char *name,
		      int pos, PanelWidget *panel)
1025 1026 1027
{
	Drawer *drawer;
	PanelWidget *newpanel;
1028
	QuickDesktopItem *item_info;
1029
	char *dentry_name;
1030
	const char *subdir_name;
1031
	char *pixmap_name;
1032
	char *filename = NULL;
1033
	GSList *list, *li;
1034

1035 1036 1037 1038 1039 1040 1041
	dentry_name = g_build_path ("/",
				    dirname,
				    ".directory",
				    NULL);
	item_info = quick_desktop_item_load_uri (dentry_name,
						 NULL /* expected_type */,
						 FALSE /* run_tryexec */);
1042 1043
	g_free (dentry_name);

1044 1045
	if (name == NULL)
		subdir_name = item_info != NULL ? item_info->name : NULL;
1046 1047
	else
		subdir_name = name;
1048
	pixmap_name = item_info != NULL ? item_info->icon : NULL;
1049

1050
	drawer = load_drawer_applet (NULL, pixmap_name, subdir_name, panel, pos, FALSE, NULL);
1051
	if (!drawer) {
1052 1053 1054
		g_warning("Can't load a drawer");
		return;
	}
1055

1056
	newpanel = PANEL_WIDGET(BASEP_WIDGET(drawer->drawer)->panel);
1057

1058
	list = get_mfiles_from_menudir (dirname, NULL /* sorted */);
1059
	for(li = list; li!= NULL; li = li->next) {
1060
		MFile *mfile = li->data;
1061
		GnomeDesktopItem *dentry;
1062 1063

		g_free (filename);
1064
		filename = g_build_filename (dirname, mfile->name, NULL);
1065

1066
		if ( ! mfile->verified) {
1067
			continue;
1068
		}
1069

1070
		if (mfile->is_dir) {
1071 1072
			add_drawers_from_dir (filename, NULL, G_MAXINT/2,
					      newpanel);
1073 1074 1075
			continue;
		}
			
1076
		if (is_ext2 (mfile->name, ".desktop", ".kdelnk")) {
1077 1078
			/*we load the applet at the right
			  side, that is end of the drawer*/
1079 1080
			dentry = gnome_desktop_item_new_from_uri (filename,
								  GNOME_DESKTOP_ITEM_LOAD_ONLY_IF_EXISTS, NULL);
1081 1082 1083 1084 1085 1086 1087 1088
			if (dentry) {
				Launcher *launcher;

				launcher =
					load_launcher_applet_full (filename,
								   dentry,
								   newpanel,
								   G_MAXINT/2,
1089 1090
								   FALSE,
								   NULL);
1091 1092 1093 1094

				if (launcher != NULL)
					launcher_hoard (launcher);
			}
1095 1096
		}
	}
1097 1098
	g_free (filename);

1099
	free_mfile_list (list);
1100 1101 1102 1103
}

/*add a drawer with the contents of a menu to the panel*/
static void
1104
add_menudrawer_to_panel(GtkWidget *widget, gpointer data)
1105 1106
{
	MenuFinfo *mf = data;
1107 1108 1109 1110 1111 1112 1113 1114 1115
	PanelWidget *panel = get_panel_from_menu_data (widget, TRUE);
	PanelData *pd;
	int insertion_pos = -1;

	g_return_if_fail (mf != 0);

	pd = g_object_get_data (G_OBJECT (panel->panel_parent), "PanelData");
	if (pd != NULL)
		insertion_pos = pd->insertion_pos;
1116
	
1117
	add_drawers_from_dir (mf->menudir, mf->dir_name, insertion_pos, panel);
1118 1119 1120
}

static void
1121
add_menu_to_panel (GtkWidget *widget, gpointer data)
1122
{
1123 1124
	const char *menudir = data;
	gboolean main_menu;
1125
	PanelWidget *panel;
1126 1127
	PanelData *pd;
	int insertion_pos = -1;
Jiri (George) Lebl's avatar
Jiri (George) Lebl committed
1128
	int flags = get_default_menu_flags ();
1129

1130
	panel = get_panel_from_menu_data (widget, TRUE);
1131

1132 1133 1134 1135
	pd = g_object_get_data (G_OBJECT (panel->panel_parent), "PanelData");
	if (pd != NULL)
		insertion_pos = pd->insertion_pos;

1136 1137
	if (menudir == NULL) {
		main_menu = TRUE;
1138
		menudir = "applications:/";
1139 1140 1141 1142 1143 1144 1145 1146
	} else {
		main_menu = FALSE;
	}

	load_menu_applet (menudir, main_menu, flags,
			  TRUE /* global_main */,
			  FALSE /* custom_icon */,
			  NULL /* custom_icon_file */,
1147 1148
			  panel /* panel */,
			  insertion_pos /* pos */,
Mark McLoughlin's avatar
Mark McLoughlin committed
1149 1150
			  FALSE /* exactpos */,
			  NULL);
1151 1152
}

1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
/*most of this function stolen from the real gtk_menu_popup*/
static void
restore_grabs(GtkWidget *w, gpointer data)
{
	GtkWidget *menu_item = data;
	GtkMenu *menu = GTK_MENU(menu_item->parent); 
	GtkWidget *xgrab_shell;
	GtkWidget *parent;
	/* Find the last viewable ancestor, and make an X grab on it
	 */
	parent = GTK_WIDGET (menu);
	xgrab_shell = NULL;
	while (parent) {
		gboolean viewable = TRUE;
		GtkWidget *tmp = parent;

		while (tmp) {
			if (!GTK_WIDGET_MAPPED (tmp)) {
				viewable = FALSE;
				break;
			}
			tmp = tmp->parent;
		}

		if (viewable)
			xgrab_shell = parent;

		parent = GTK_MENU_SHELL (parent)->parent_menu_shell;
	}

	/*only grab if this HAD a grab before*/
	if (xgrab_shell && (GTK_MENU_SHELL (xgrab_shell)->have_xgrab)) {
		GdkCursor *cursor = gdk_cursor_new (GDK_ARROW);

		GTK_MENU_SHELL (xgrab_shell)->have_xgrab = 
			(gdk_pointer_grab (xgrab_shell->window, TRUE,
1189 1190 1191 1192
					   GDK_BUTTON_PRESS_MASK |
					   GDK_BUTTON_RELEASE_MASK |
					   GDK_ENTER_NOTIFY_MASK |
					   GDK_LEAVE_NOTIFY_MASK,
1193
					   NULL, cursor, 0) == 0);
1194
		gdk_cursor_unref (cursor);
1195
	}
1196
	
1197 1198
	gtk_grab_add (GTK_WIDGET (menu));
}
1199 1200

static void
1201
edit_dentry (GtkWidget *widget, ShowItemMenu *sim)
1202
{
1203 1204
	g_return_if_fail (sim != NULL);
	g_return_if_fail (sim->item_loc != NULL);
1205

1206 1207
	panel_edit_dentry (sim->item_loc,
			   sim->mf != NULL ? sim->mf->menudir : NULL);
1208 1209 1210
}

static void
1211
edit_direntry (GtkWidget *widget, ShowItemMenu *sim)
1212
{
1213 1214
	g_return_if_fail (sim != NULL);
	g_return_if_fail (sim->mf != NULL);
1215

1216 1217
	panel_edit_direntry (sim->mf->menudir,
			     sim->mf->dir_name);
1218 1219
}

1220
static void
1221
show_item_menu (GtkWidget *item, GdkEventButton *bevent, ShowItemMenu *sim)
1222
{
1223
	GtkWidget *menuitem;
1224
	char *tmp;
1225
	GnomeDesktopItem *ii;
1226

1227
	if (sim->menu == NULL) {
1228
		sim->menu = menu_new ();
1229

1230 1231 1232
		g_object_set_data (G_OBJECT (sim->menu), "menu_panel",
				   get_panel_from_menu_data (sim->menuitem,
							     TRUE));
1233
		
1234 1235 1236
		g_signal_connect (G_OBJECT (sim->menu), "deactivate",
				  G_CALLBACK (restore_grabs),
				  item);
1237

1238
		if (sim->type == 1) {
1239
			ii = gnome_desktop_item_new_from_uri (sim->item_loc, 0, NULL);
1240

1241 1242 1243 1244 1245 1246 1247
			/* eek */
			if (ii == NULL) {
				gtk_widget_destroy (sim->menu);
				sim->menu = NULL;
				return;
			}

1248
			menuitem = gtk_image_menu_item_new ();
1249
			if ( ! sim->applet)
1250
				setup_menuitem (menuitem, NULL,
1251
						_("Add this launcher to panel"));
1252
			gtk_menu_shell_append (GTK_MENU_SHELL (sim->menu), menuitem);
1253 1254
			g_signal_connect (G_OBJECT(menuitem), "activate",
					    G_CALLBACK(add_app_to_panel),
1255
					    (gpointer)sim->item_loc);
1256

1257
			menuitem = gtk_image_menu_item_new ();
1258
			setup_menuitem (menuitem, NULL,
1259
					_("Remove this item"));
1260
			gtk_menu_shell_append (GTK_MENU_SHELL (sim->menu), menuitem);
1261 1262
			g_signal_connect (G_OBJECT(menuitem), "activate",
					    G_CALLBACK (remove_menuitem),
1263
					    sim);
1264
			tmp = g_path_get_dirname(sim->item_loc);
1265
			if (access (tmp, W_OK) != 0)
1266
				gtk_widget_set_sensitive(menuitem,FALSE);
1267
			g_free (tmp);
1268
			g_signal_connect_swapped (G_OBJECT (menuitem),
1269
						   "activate",
1270 1271
						   G_CALLBACK (gtk_menu_shell_deactivate),
						   G_OBJECT (item->parent));
1272

1273
			if ( ! sim->applet) {
1274
				menuitem = gtk_image_menu_item_new ();
1275
				setup_menuitem (menuitem, NULL,
1276
						_("Put into run dialog"));
1277
				gtk_menu_shell_append (GTK_MENU_SHELL (sim->menu),
1278
						 menuitem);
1279 1280 1281 1282 1283
				g_signal_connect (G_OBJECT(menuitem), "activate",
					          G_CALLBACK(add_to_run_dialog),
					          (gpointer)sim->item_loc);
				g_signal_connect_swapped
					(G_OBJECT(menuitem),
1284
					 "activate",
1285
					 G_CALLBACK(gtk_menu_shell_deactivate),
1286
					 G_OBJECT(item->parent));
1287
			}
1288

1289
			if (gnome_desktop_item_get_string (ii, "DocPath") != NULL) {
1290
				char *title;
1291
				const char *name;
1292

1293
				menuitem = gtk_image_menu_item_new ();