Merging upstream version 0.5.9.
[debian/lxpanel.git] / src / plugins / menu.c
1 /**
2 * Copyright (c) 2006 LxDE Developers, see the file AUTHORS for details.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software Foundation,
16 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <gdk-pixbuf/gdk-pixbuf.h>
23 #include <glib.h>
24 #include <glib/gi18n.h>
25
26 #include <menu-cache.h>
27
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <fcntl.h>
32
33 #include "panel.h"
34 #include "misc.h"
35 #include "plugin.h"
36 #include "bg.h"
37 #include "menu-policy.h"
38
39 #include "dbg.h"
40
41 #define DEFAULT_MENU_ICON PACKAGE_DATA_DIR "/lxpanel/images/my-computer.png"
42 /*
43 * SuxPanel version 0.1
44 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
45 */
46
47 /*
48 * menu style code was taken from suxpanel
49 */
50
51 typedef struct {
52 GtkWidget *menu, *box, *img, *label;
53 char *fname, *caption;
54 gulong handler_id;
55 int iconsize, paneliconsize;
56 GSList *files;
57 gboolean has_system_menu;
58 char* config_data;
59 int sysmenu_pos;
60 char *config_start, *config_end;
61
62 MenuCache* menu_cache;
63 guint visibility_flags;
64 gpointer reload_notify;
65 } menup;
66
67 static guint idle_loader = 0;
68
69 GQuark SYS_MENU_ITEM_ID = 0;
70
71 /* a single-linked list storing all panels */
72 extern GSList* all_panels;
73
74
75 static void
76 menu_destructor(Plugin *p)
77 {
78 menup *m = (menup *)p->priv;
79
80 if( G_UNLIKELY( idle_loader ) )
81 {
82 g_source_remove( idle_loader );
83 idle_loader = 0;
84 }
85
86 if( m->has_system_menu )
87 p->panel->system_menus = g_slist_remove( p->panel->system_menus, p );
88
89 g_signal_handler_disconnect(G_OBJECT(m->img), m->handler_id);
90 gtk_widget_destroy(m->menu);
91
92 if( m->menu_cache )
93 {
94 menu_cache_remove_reload_notify(m->menu_cache, m->reload_notify);
95 menu_cache_unref( m->menu_cache );
96 }
97
98 g_free(m->fname);
99 g_free(m->caption);
100 g_free(m);
101 RET();
102 }
103
104 static void
105 spawn_app(GtkWidget *widget, gpointer data)
106 {
107 GError *error = NULL;
108
109 ENTER;
110 if (data) {
111 if (! g_spawn_command_line_async(data, &error) ) {
112 ERR("can't spawn %s\nError is %s\n", (char *)data, error->message);
113 g_error_free (error);
114 }
115 }
116 RET();
117 }
118
119
120 static void
121 run_command(GtkWidget *widget, void (*cmd)(void))
122 {
123 ENTER;
124 cmd();
125 RET();
126 }
127
128 static void
129 menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget)
130 {
131 int ox, oy, w, h;
132 Plugin *p;
133 #if GTK_CHECK_VERSION(2,18,0)
134 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
135 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
136 #endif
137 ENTER;
138 p = g_object_get_data(G_OBJECT(widget), "plugin");
139 #if GTK_CHECK_VERSION(2,14,0)
140 gdk_window_get_origin(gtk_widget_get_window(widget), &ox, &oy);
141 #else
142 gdk_window_get_origin(widget->window, &ox, &oy);
143 #endif
144 #if GTK_CHECK_VERSION(2,20,0)
145 GtkRequisition requisition;
146 gtk_widget_get_requisition(GTK_WIDGET(menu), &requisition);
147 w = requisition.width;
148 h = requisition.height;
149
150 #else
151 w = GTK_WIDGET(menu)->requisition.width;
152 h = GTK_WIDGET(menu)->requisition.height;
153 #endif
154 if (p->panel->orientation == ORIENT_HORIZ) {
155 *x = ox;
156 if (*x + w > gdk_screen_width())
157 #if GTK_CHECK_VERSION(2,18,0)
158 *x = ox + allocation->width - w;
159 #else
160 *x = ox + widget->allocation.width - w;
161 #endif
162 *y = oy - h;
163 if (*y < 0)
164 #if GTK_CHECK_VERSION(2,18,0)
165 *y = oy + allocation->height;
166 #else
167 *y = oy + widget->allocation.height;
168 #endif
169 } else {
170 #if GTK_CHECK_VERSION(2,18,0)
171 *x = ox + allocation->width;
172 #else
173 *x = ox + widget->allocation.width;
174 #endif
175 if (*x > gdk_screen_width())
176 *x = ox - w;
177 *y = oy;
178 if (*y + h > gdk_screen_height())
179 #if GTK_CHECK_VERSION(2,18,0)
180 *y = oy + allocation->height - h;
181 #else
182 *y = oy + widget->allocation.height - h;
183 #endif
184 }
185 DBG("widget: x,y=%d,%d w,h=%d,%d\n", ox, oy,
186 #if GTK_CHECK_VERSION(2,18,0)
187 allocation->width, allocation->height );
188 #else
189 widget->allocation.width, widget->allocation.height );
190 #endif
191 DBG("w-h %d %d\n", w, h);
192 *push_in = TRUE;
193 #if GTK_CHECK_VERSION(2,18,0)
194 g_free (allocation);
195 #endif
196 RET();
197 }
198
199 static void on_menu_item( GtkMenuItem* mi, MenuCacheItem* item )
200 {
201 lxpanel_launch_app( menu_cache_app_get_exec(MENU_CACHE_APP(item)),
202 NULL, menu_cache_app_get_use_terminal(MENU_CACHE_APP(item)));
203 }
204
205 /* load icon when mapping the menu item to speed up */
206 static void on_menu_item_map(GtkWidget* mi, MenuCacheItem* item)
207 {
208 GtkImage* img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(mi)));
209 if( img )
210 {
211 if( gtk_image_get_storage_type(img) == GTK_IMAGE_EMPTY )
212 {
213 GdkPixbuf* icon;
214 int w, h;
215 /* FIXME: this is inefficient */
216 gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &w, &h);
217 item = g_object_get_qdata(G_OBJECT(mi), SYS_MENU_ITEM_ID);
218 icon = lxpanel_load_icon(menu_cache_item_get_icon(item), w, h, TRUE);
219 if (icon)
220 {
221 gtk_image_set_from_pixbuf(img, icon);
222 g_object_unref(icon);
223 }
224 }
225 }
226 }
227
228 static void on_menu_item_style_set(GtkWidget* mi, GtkStyle* prev, MenuCacheItem* item)
229 {
230 /* reload icon */
231 on_menu_item_map(mi, item);
232 }
233
234 static void on_add_menu_item_to_desktop(GtkMenuItem* item, MenuCacheApp* app)
235 {
236 char* dest;
237 char* src;
238 const char* desktop = g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP);
239 int dir_len = strlen(desktop);
240 int basename_len = strlen(menu_cache_item_get_id(MENU_CACHE_ITEM(app)));
241 int dest_fd;
242
243 dest = g_malloc( dir_len + basename_len + 6 + 1 + 1 );
244 memcpy(dest, desktop, dir_len);
245 dest[dir_len] = '/';
246 memcpy(dest + dir_len + 1, menu_cache_item_get_id(MENU_CACHE_ITEM(app)), basename_len + 1);
247
248 /* if the destination file already exists, make a unique name. */
249 if( g_file_test( dest, G_FILE_TEST_EXISTS ) )
250 {
251 memcpy( dest + dir_len + 1 + basename_len - 8 /* .desktop */, "XXXXXX.desktop", 15 );
252 dest_fd = g_mkstemp(dest);
253 if( dest_fd >= 0 )
254 chmod(dest, 0600);
255 }
256 else
257 {
258 dest_fd = creat(dest, 0600);
259 }
260
261 if( dest_fd >=0 )
262 {
263 char* data;
264 gsize len;
265 src = menu_cache_item_get_file_path(MENU_CACHE_ITEM(app));
266 if( g_file_get_contents(src, &data, &len, NULL) )
267 {
268 write( dest_fd, data, len );
269 g_free(data);
270 }
271 close(dest_fd);
272 g_free(src);
273 }
274 g_free(dest);
275 }
276
277 /* TODO: add menu item to panel */
278 #if 0
279 static void on_add_menu_item_to_panel(GtkMenuItem* item, MenuCacheApp* app)
280 {
281 /* Find a penel containing launchbar applet.
282 * The launchbar with most buttons will be choosen if
283 * there are several launchbar applets loaded.
284 */
285 GSList* l;
286 Plugin* lb = NULL;
287 int n_btns = -1;
288
289 for(l = all_panels; !lb && l; l = l->next)
290 {
291 Panel* panel = (Panel*)l->data;
292 GList* pl;
293 for(pl=panel->plugins; pl; pl = pl->next)
294 {
295 Plugin* plugin = (Plugin*)pl;
296 if( strcmp(plugin->class->type, "launchbar") == 0 )
297 {
298 /* FIXME: should we let the users choose which launcherbar to add the btn? */
299 break;
300 #if 0
301 int n = launchbar_get_n_btns(plugin);
302 if( n > n_btns )
303 {
304 lb = plugin;
305 n_btns = n;
306 }
307 #endif
308 }
309 }
310 }
311
312 if( ! lb ) /* launchbar is not currently in use */
313 {
314 /* FIXME: add a launchbar plugin to the panel which has a menu, too. */
315 }
316
317 if( lb )
318 {
319
320 }
321 }
322 #endif
323
324 static void on_menu_item_properties(GtkMenuItem* item, MenuCacheApp* app)
325 {
326 /* FIXME: if the source desktop is in AppDir other then default
327 * applications dirs, where should we store the user-specific file?
328 */
329 char* ifile = menu_cache_item_get_file_path(MENU_CACHE_ITEM(app));
330 char* ofile = g_build_filename(g_get_user_data_dir(), "applications",
331 menu_cache_item_get_file_basename(MENU_CACHE_ITEM(app)), NULL);
332 char* argv[] = {
333 "lxshortcut",
334 "-i",
335 NULL,
336 "-o",
337 NULL,
338 NULL};
339 argv[2] = ifile;
340 argv[4] = ofile;
341 g_spawn_async( NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL );
342 g_free( ifile );
343 g_free( ofile );
344 }
345
346 /* This following function restore_grabs is taken from menu.c of
347 * gnome-panel.
348 */
349 /*most of this function stolen from the real gtk_menu_popup*/
350 static void restore_grabs(GtkWidget *w, gpointer data)
351 {
352 GtkWidget *menu_item = data;
353 GtkMenu *menu = GTK_MENU(gtk_widget_get_parent(menu_item));
354 GtkWidget *xgrab_shell;
355 GtkWidget *parent;
356
357 /* Find the last viewable ancestor, and make an X grab on it
358 */
359 parent = GTK_WIDGET (menu);
360 xgrab_shell = NULL;
361 while (parent)
362 {
363 gboolean viewable = TRUE;
364 GtkWidget *tmp = parent;
365
366 while (tmp)
367 {
368 if (!GTK_WIDGET_MAPPED (tmp))
369 {
370 viewable = FALSE;
371 break;
372 }
373 tmp = gtk_widget_get_parent(tmp);
374 }
375
376 if (viewable)
377 xgrab_shell = parent;
378
379 parent = gtk_widget_get_parent(parent);
380 }
381
382 /*only grab if this HAD a grab before*/
383 #if GTK_CHECK_VERSION(2,18,0)
384 if (xgrab_shell && (gtk_widget_has_focus(xgrab_shell)))
385 #else
386 if (xgrab_shell && (GTK_MENU_SHELL (xgrab_shell)->have_xgrab))
387 #endif
388 {
389 #if GTK_CHECK_VERSION(2,14,0)
390 if (gdk_pointer_grab (gtk_widget_get_window(xgrab_shell), TRUE,
391 #else
392 if (gdk_pointer_grab (xgrab_shell->window, TRUE,
393 #endif
394 GDK_BUTTON_PRESS_MASK |
395 GDK_BUTTON_RELEASE_MASK |
396 GDK_ENTER_NOTIFY_MASK |
397 GDK_LEAVE_NOTIFY_MASK,
398 NULL, NULL, 0) == 0)
399 {
400 #if GTK_CHECK_VERSION(2,14,0)
401 if (gdk_keyboard_grab (gtk_widget_get_window(xgrab_shell), TRUE,
402 #else
403 if (gdk_keyboard_grab (xgrab_shell->window, TRUE,
404 #endif
405 GDK_CURRENT_TIME) == 0)
406 #if GTK_CHECK_VERSION(2,18,0)
407 gtk_widget_grab_focus (xgrab_shell);
408 #else
409 GTK_MENU_SHELL (xgrab_shell)->have_xgrab = TRUE;
410 #endif
411 else
412 gdk_pointer_ungrab (GDK_CURRENT_TIME);
413 }
414 }
415 gtk_grab_add (GTK_WIDGET (menu));
416 }
417
418 static gboolean on_menu_button_press(GtkWidget* mi, GdkEventButton* evt, MenuCacheItem* data)
419 {
420 if( evt->button == 3 ) /* right */
421 {
422 char* tmp;
423 GtkWidget* item;
424 GtkMenu* p = GTK_MENU(gtk_menu_new());
425
426 item = gtk_menu_item_new_with_label(_("Add to desktop"));
427 g_signal_connect(item, "activate", G_CALLBACK(on_add_menu_item_to_desktop), data);
428 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
429
430 tmp = g_find_program_in_path("lxshortcut");
431 if( tmp )
432 {
433 item = gtk_separator_menu_item_new();
434 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
435
436 item = gtk_menu_item_new_with_label(_("Properties"));
437 g_signal_connect(item, "activate", G_CALLBACK(on_menu_item_properties), data);
438 gtk_menu_shell_append(GTK_MENU_SHELL(p), item);
439 g_free(tmp);
440 }
441 g_signal_connect(p, "selection-done", G_CALLBACK(gtk_widget_destroy), NULL);
442 g_signal_connect(p, "deactivate", G_CALLBACK(restore_grabs), mi);
443
444 gtk_widget_show_all(GTK_WIDGET(p));
445 gtk_menu_popup(p, NULL, NULL, NULL, NULL, 0, evt->time);
446 return TRUE;
447 }
448 return FALSE;
449 }
450
451 static GtkWidget* create_item( MenuCacheItem* item )
452 {
453 GtkWidget* mi;
454 if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_SEP )
455 mi = gtk_separator_menu_item_new();
456 else
457 {
458 GtkWidget* img;
459 mi = gtk_image_menu_item_new_with_label( menu_cache_item_get_name(item) );
460 img = gtk_image_new();
461 gtk_image_menu_item_set_image( GTK_IMAGE_MENU_ITEM(mi), img );
462 if( menu_cache_item_get_type(item) == MENU_CACHE_TYPE_APP )
463 {
464 gtk_widget_set_tooltip_text( mi, menu_cache_item_get_comment(item) );
465 g_signal_connect( mi, "activate", G_CALLBACK(on_menu_item), item );
466 }
467 g_signal_connect(mi, "map", G_CALLBACK(on_menu_item_map), item);
468 g_signal_connect(mi, "style-set", G_CALLBACK(on_menu_item_style_set), item);
469 g_signal_connect(mi, "button-press-event", G_CALLBACK(on_menu_button_press), item);
470 }
471 gtk_widget_show( mi );
472 g_object_set_qdata_full( G_OBJECT(mi), SYS_MENU_ITEM_ID, menu_cache_item_ref(item), (GDestroyNotify) menu_cache_item_unref );
473 return mi;
474 }
475
476 static int load_menu(menup* m, MenuCacheDir* dir, GtkWidget* menu, int pos )
477 {
478 GSList * l;
479 /* number of visible entries */
480 gint count = 0;
481 for( l = menu_cache_dir_get_children(dir); l; l = l->next )
482 {
483 MenuCacheItem* item = MENU_CACHE_ITEM(l->data);
484
485 gboolean is_visible = ((menu_cache_item_get_type(item) != MENU_CACHE_TYPE_APP) ||
486 (panel_menu_item_evaluate_visibility(item, m->visibility_flags)));
487
488 if (is_visible)
489 {
490 GtkWidget * mi = create_item(item);
491 count++;
492 if (mi != NULL)
493 gtk_menu_shell_insert( (GtkMenuShell*)menu, mi, pos );
494 if( pos >= 0 )
495 ++pos;
496 /* process subentries */
497 if (menu_cache_item_get_type(item) == MENU_CACHE_TYPE_DIR)
498 {
499 GtkWidget* sub = gtk_menu_new();
500 /* always pass -1 for position */
501 gint s_count = load_menu( m, MENU_CACHE_DIR(item), sub, -1 );
502 if (s_count)
503 gtk_menu_item_set_submenu( GTK_MENU_ITEM(mi), sub );
504 else
505 {
506 /* don't keep empty submenus */
507 gtk_widget_destroy( sub );
508 gtk_widget_destroy( mi );
509 if (pos > 0)
510 pos--;
511 }
512 }
513 }
514 }
515 return count;
516 }
517
518
519
520 static gboolean sys_menu_item_has_data( GtkMenuItem* item )
521 {
522 return (g_object_get_qdata( G_OBJECT(item), SYS_MENU_ITEM_ID ) != NULL);
523 }
524
525 static void unload_old_icons(GtkMenu* menu, GtkIconTheme* theme)
526 {
527 GList *children, *child;
528 GtkMenuItem* item;
529 GtkWidget* sub_menu=NULL;
530
531 children = gtk_container_get_children( GTK_CONTAINER(menu) );
532 for( child = children; child; child = child->next )
533 {
534 item = GTK_MENU_ITEM( child->data );
535 if( sys_menu_item_has_data( item ) )
536 {
537 GtkImage* img;
538 item = GTK_MENU_ITEM( child->data );
539 if( GTK_IS_IMAGE_MENU_ITEM(item) )
540 {
541 img = GTK_IMAGE(gtk_image_menu_item_get_image(GTK_IMAGE_MENU_ITEM(item)));
542 gtk_image_clear(img);
543 if( GTK_WIDGET_MAPPED(img) )
544 on_menu_item_map(GTK_WIDGET(item),
545 (MenuCacheItem*)g_object_get_qdata(G_OBJECT(item), SYS_MENU_ITEM_ID) );
546 }
547 }
548 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
549 {
550 unload_old_icons( GTK_MENU(sub_menu), theme );
551 }
552 }
553 g_list_free( children );
554 }
555
556 static void remove_change_handler(gpointer id, GObject* menu)
557 {
558 g_signal_handler_disconnect(gtk_icon_theme_get_default(), GPOINTER_TO_INT(id));
559 }
560
561 /*
562 * Insert application menus into specified menu
563 * menu: The parent menu to which the items should be inserted
564 * pisition: Position to insert items.
565 Passing -1 in this parameter means append all items
566 at the end of menu.
567 */
568 static void sys_menu_insert_items( menup* m, GtkMenu* menu, int position )
569 {
570 MenuCacheDir* dir;
571 guint change_handler;
572
573 if( G_UNLIKELY( SYS_MENU_ITEM_ID == 0 ) )
574 SYS_MENU_ITEM_ID = g_quark_from_static_string( "SysMenuItem" );
575
576 dir = menu_cache_get_root_dir( m->menu_cache );
577 if(dir)
578 load_menu( m, dir, GTK_WIDGET(menu), position );
579 else /* menu content is empty */
580 {
581 /* add a place holder */
582 GtkWidget* mi = gtk_menu_item_new();
583 g_object_set_qdata( G_OBJECT(mi), SYS_MENU_ITEM_ID, GINT_TO_POINTER(1) );
584 gtk_menu_shell_insert(GTK_MENU_SHELL(menu), mi, position);
585 }
586
587 change_handler = g_signal_connect_swapped( gtk_icon_theme_get_default(), "changed", G_CALLBACK(unload_old_icons), menu );
588 g_object_weak_ref( G_OBJECT(menu), remove_change_handler, GINT_TO_POINTER(change_handler) );
589 }
590
591
592 static void
593 reload_system_menu( menup* m, GtkMenu* menu )
594 {
595 GList *children, *child;
596 GtkMenuItem* item;
597 GtkWidget* sub_menu;
598 gint idx;
599 gboolean found = FALSE;
600
601 children = gtk_container_get_children( GTK_CONTAINER(menu) );
602 for( child = children, idx = 0; child; child = child->next, ++idx )
603 {
604 item = GTK_MENU_ITEM( child->data );
605 if( sys_menu_item_has_data( item ) )
606 {
607 do
608 {
609 item = GTK_MENU_ITEM( child->data );
610 child = child->next;
611 gtk_widget_destroy( GTK_WIDGET(item) );
612 }while( child && sys_menu_item_has_data( child->data ) );
613 sys_menu_insert_items( m, menu, idx );
614 if( ! child )
615 break;
616 found = TRUE;
617 }
618 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) )
619 {
620 reload_system_menu( m, GTK_MENU(sub_menu) );
621 }
622 }
623 g_list_free( children );
624 }
625
626 static void show_menu( GtkWidget* widget, Plugin* p, int btn, guint32 time )
627 {
628 menup* m = (menup*)p->priv;
629 gtk_menu_popup(GTK_MENU(m->menu),
630 NULL, NULL,
631 (GtkMenuPositionFunc)menu_pos, widget,
632 btn, time);
633 }
634
635 static gboolean
636 my_button_pressed(GtkWidget *widget, GdkEventButton *event, Plugin* plugin)
637 {
638 ENTER;
639 #if GTK_CHECK_VERSION(2,18,0)
640 GtkAllocation *allocation = g_new0 (GtkAllocation, 1);
641 gtk_widget_get_allocation(GTK_WIDGET(widget), allocation);
642 #endif
643
644 /* Standard right-click handling. */
645 if (plugin_button_press_event(widget, event, plugin))
646 return TRUE;
647
648 if ((event->type == GDK_BUTTON_PRESS)
649 #if GTK_CHECK_VERSION(2,18,0)
650 && (event->x >=0 && event->x < allocation->width)
651 && (event->y >=0 && event->y < allocation->height)) {
652 #else
653 && (event->x >=0 && event->x < widget->allocation.width)
654 && (event->y >=0 && event->y < widget->allocation.height)) {
655 #endif
656 show_menu( widget, plugin, event->button, event->time );
657 }
658 #if GTK_CHECK_VERSION(2,18,0)
659 g_free (allocation);
660 #endif
661 RET(TRUE);
662 }
663
664 gboolean show_system_menu( gpointer system_menu )
665 {
666 Plugin* p = (Plugin*)system_menu;
667 menup* m = (menup*)p->priv;
668 show_menu( m->img, p, 0, GDK_CURRENT_TIME );
669 return FALSE;
670 }
671
672 static GtkWidget *
673 make_button(Plugin *p, gchar *fname, gchar *name, GdkColor* tint, GtkWidget *menu)
674 {
675 char* title = NULL;
676 menup *m;
677
678 ENTER;
679 m = (menup *)p->priv;
680 m->menu = menu;
681
682 if( name )
683 {
684 /* load the name from *.directory file if needed */
685 if( g_str_has_suffix( name, ".directory" ) )
686 {
687 GKeyFile* kf = g_key_file_new();
688 char* dir_file = g_build_filename( "desktop-directories", name, NULL );
689 if( g_key_file_load_from_data_dirs( kf, dir_file, NULL, 0, NULL ) )
690 {
691 title = g_key_file_get_locale_string( kf, "Desktop Entry", "Name", NULL, NULL );
692 }
693 g_free( dir_file );
694 g_key_file_free( kf );
695 }
696 else
697 title = name;
698
699 m->img = fb_button_new_from_file_with_label(fname, -1, p->panel->icon_size, gcolor2rgb24(tint), TRUE, p->panel, title);
700
701 if( title != name )
702 g_free( title );
703 }
704 else
705 {
706 m->img = fb_button_new_from_file(fname, -1, p->panel->icon_size, gcolor2rgb24(tint), TRUE);
707 }
708
709 gtk_widget_show(m->img);
710 gtk_box_pack_start(GTK_BOX(m->box), m->img, FALSE, FALSE, 0);
711
712 m->handler_id = g_signal_connect (G_OBJECT (m->img), "button-press-event",
713 G_CALLBACK (my_button_pressed), p);
714 g_object_set_data(G_OBJECT(m->img), "plugin", p);
715
716 RET(m->img);
717 }
718
719
720 static GtkWidget *
721 read_item(Plugin *p, char** fp)
722 {
723 line s;
724 gchar *name, *fname, *action;
725 GtkWidget *item;
726 menup *m = (menup *)p->priv;
727 Command *cmd_entry = NULL;
728
729 ENTER;
730 s.len = 256;
731 name = fname = action = NULL;
732
733 if( fp )
734 {
735 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
736 if (s.type == LINE_VAR) {
737 if (!g_ascii_strcasecmp(s.t[0], "image"))
738 fname = expand_tilda(s.t[1]);
739 else if (!g_ascii_strcasecmp(s.t[0], "name"))
740 name = g_strdup(s.t[1]);
741 else if (!g_ascii_strcasecmp(s.t[0], "action"))
742 action = g_strdup(s.t[1]);
743 else if (!g_ascii_strcasecmp(s.t[0], "command")) {
744 Command *tmp;
745
746 for (tmp = commands; tmp->name; tmp++) {
747 if (!g_ascii_strcasecmp(s.t[1], tmp->name)) {
748 cmd_entry = tmp;
749 break;
750 }
751 }
752 } else {
753 ERR( "menu/item: unknown var %s\n", s.t[0]);
754 goto error;
755 }
756 }
757 }
758 }
759 /* menu button */
760 if( cmd_entry ) /* built-in commands */
761 {
762 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
763 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
764 }
765 else
766 {
767 item = gtk_image_menu_item_new_with_label(name ? name : "");
768 if (action) {
769 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, action);
770 }
771 }
772 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
773 g_free(name);
774 if (fname) {
775 GtkWidget *img;
776
777 img = _gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
778 gtk_widget_show(img);
779 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
780 g_free(fname);
781 }
782 RET(item);
783
784 error:
785 g_free(fname);
786 g_free(name);
787 g_free(action);
788 RET(NULL);
789 }
790
791 static GtkWidget *
792 read_separator(Plugin *p, char **fp)
793 {
794 line s;
795
796 ENTER;
797 s.len = 256;
798 if( fp )
799 {
800 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
801 ERR("menu: error - separator can not have paramteres\n");
802 RET(NULL);
803 }
804 }
805 RET(gtk_separator_menu_item_new());
806 }
807
808 static void on_reload_menu( MenuCache* cache, menup* m )
809 {
810 /* g_debug("reload system menu!!"); */
811 reload_system_menu( m, GTK_MENU(m->menu) );
812 }
813
814 static void
815 read_system_menu(GtkMenu* menu, Plugin *p, char** fp)
816 {
817 line s;
818 menup *m = (menup *)p->priv;
819
820 if (m->menu_cache == NULL)
821 {
822 guint32 flags;
823 m->menu_cache = panel_menu_cache_new(&flags);
824 if (m->menu_cache == NULL)
825 {
826 ERR("error loading applications menu");
827 return;
828 }
829 m->visibility_flags = flags;
830 m->reload_notify = menu_cache_add_reload_notify(m->menu_cache, (GFunc) on_reload_menu, m);
831 }
832
833 s.len = 256;
834 if( fp )
835 {
836 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
837 ERR("menu: error - system can not have paramteres\n");
838 return;
839 }
840 }
841
842 sys_menu_insert_items( m, menu, -1 );
843 m->has_system_menu = TRUE;
844
845 p->panel->system_menus = g_slist_append( p->panel->system_menus, p );
846 }
847
848 static void
849 read_include(Plugin *p, char **fp)
850 {
851 ENTER;
852 #if 0
853 gchar *name;
854 line s;
855 menup *m = (menup *)p->priv;
856 /* FIXME: this is disabled */
857 ENTER;
858 s.len = 256;
859 name = NULL;
860 if( fp )
861 {
862 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
863 if (s.type == LINE_VAR) {
864 if (!g_ascii_strcasecmp(s.t[0], "name"))
865 name = expand_tilda(s.t[1]);
866 else {
867 ERR( "menu/include: unknown var %s\n", s.t[0]);
868 RET();
869 }
870 }
871 }
872 }
873 if ((fp = fopen(name, "r"))) {
874 LOG(LOG_INFO, "Including %s\n", name);
875 m->files = g_slist_prepend(m->files, fp);
876 p->fp = fp;
877 } else {
878 ERR("Can't include %s\n", name);
879 }
880 if (name) g_free(name);
881 #endif
882 RET();
883 }
884
885 static GtkWidget *
886 read_submenu(Plugin *p, char** fp, gboolean as_item)
887 {
888 line s;
889 GtkWidget *mi, *menu;
890 gchar *name, *fname;
891 menup *m = (menup *)p->priv;
892 GdkColor color={0, 0, 36 * 0xffff / 0xff, 96 * 0xffff / 0xff};
893
894 ENTER;
895
896 s.len = 256;
897 menu = gtk_menu_new ();
898 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
899
900 fname = NULL;
901 name = NULL;
902 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
903 if (s.type == LINE_BLOCK_START) {
904 mi = NULL;
905 if (!g_ascii_strcasecmp(s.t[0], "item")) {
906 mi = read_item(p, fp);
907 } else if (!g_ascii_strcasecmp(s.t[0], "separator")) {
908 mi = read_separator(p, fp);
909 } else if (!g_ascii_strcasecmp(s.t[0], "system")) {
910 read_system_menu(GTK_MENU(menu), p, fp); /* add system menu items */
911 continue;
912 } else if (!g_ascii_strcasecmp(s.t[0], "menu")) {
913 mi = read_submenu(p, fp, TRUE);
914 } else if (!g_ascii_strcasecmp(s.t[0], "include")) {
915 read_include(p, fp);
916 continue;
917 } else {
918 ERR("menu: unknown block %s\n", s.t[0]);
919 goto error;
920 }
921 if (!mi) {
922 ERR("menu: can't create menu item\n");
923 goto error;
924 }
925 gtk_widget_show(mi);
926 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
927 } else if (s.type == LINE_VAR) {
928 m->config_start = *fp;
929 if (!g_ascii_strcasecmp(s.t[0], "image"))
930 fname = expand_tilda(s.t[1]);
931 else if (!g_ascii_strcasecmp(s.t[0], "name"))
932 name = g_strdup(s.t[1]);
933 /* FIXME: tintcolor will not be saved. */
934 else if (!g_ascii_strcasecmp(s.t[0], "tintcolor"))
935 gdk_color_parse( s.t[1], &color);
936 else {
937 ERR("menu: unknown var %s\n", s.t[0]);
938 }
939 } else if (s.type == LINE_NONE) {
940 if (m->files) {
941 /*
942 fclose(p->fp);
943 p->fp = m->files->data;
944 */
945 m->files = g_slist_delete_link(m->files, m->files);
946 }
947 } else {
948 ERR("menu: illegal in this context %s\n", s.str);
949 goto error;
950 }
951 }
952 if (as_item) {
953 mi = gtk_image_menu_item_new_with_label(name);
954 if (fname) {
955 GtkWidget *img;
956 img = _gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
957 gtk_widget_show(img);
958 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
959 }
960 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
961 } else {
962 m->fname = fname ? g_strdup(fname) : g_strdup( DEFAULT_MENU_ICON );
963 m->caption = name ? g_strdup(name) : NULL;
964 mi = make_button(p, fname, name, &color, menu);
965 RET(mi);
966 }
967
968 g_free(fname);
969 g_free(name);
970 RET(mi);
971
972 error:
973 // FIXME: we need to recursivly destroy all child menus and their items
974 gtk_widget_destroy(menu);
975 g_free(fname);
976 g_free(name);
977 RET(NULL);
978 }
979
980 static int
981 menu_constructor(Plugin *p, char **fp)
982 {
983 char *start;
984 menup *m;
985 static char default_config[] =
986 "system {\n"
987 "}\n"
988 "separator {\n"
989 "}\n"
990 "item {\n"
991 "command=run\n"
992 "}\n"
993 "separator {\n"
994 "}\n"
995 "item {\n"
996 "image=gnome-logout\n"
997 "command=logout\n"
998 "}\n"
999 "image=" DEFAULT_MENU_ICON "\n"
1000 "}\n";
1001 char *config_default = default_config;
1002 int iw, ih;
1003
1004 m = g_new0(menup, 1);
1005 g_return_val_if_fail(m != NULL, 0);
1006 m->fname = NULL;
1007 m->caption = NULL;
1008
1009 p->priv = m;
1010
1011 gtk_icon_size_lookup( GTK_ICON_SIZE_MENU, &iw, &ih );
1012 m->iconsize = MAX(iw, ih);
1013
1014 m->box = gtk_hbox_new(FALSE, 0);
1015 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
1016
1017 if( ! fp )
1018 fp = &config_default;
1019
1020 m->config_start = start = *fp;
1021 if (!read_submenu(p, fp, FALSE)) {
1022 ERR("menu: plugin init failed\n");
1023 return 0;
1024 }
1025 m->config_end = *fp - 1;
1026 while( *m->config_end != '}' && m->config_end > m->config_start ) {
1027 --m->config_end;
1028 }
1029 if( *m->config_end == '}' )
1030 --m->config_end;
1031
1032 m->config_data = g_strndup( start, (m->config_end - start) );
1033
1034 p->pwid = m->box;
1035
1036 RET(1);
1037
1038 }
1039
1040 static void save_config( Plugin* p, FILE* fp )
1041 {
1042 menup* menu = (menup*)p->priv;
1043 int level = 0;
1044 lxpanel_put_str( fp, "name", menu->caption );
1045 lxpanel_put_str( fp, "image", menu->fname );
1046 if( menu->config_data ) {
1047 char** lines = g_strsplit( menu->config_data, "\n", 0 );
1048 char** line;
1049 for( line = lines; *line; ++line ) {
1050 g_strstrip( *line );
1051 if( **line )
1052 {
1053 if( level == 0 )
1054 {
1055 /* skip image and caption since we already save these two items */
1056 if( g_str_has_prefix(*line, "image") || g_str_has_prefix(*line, "caption") )
1057 continue;
1058 }
1059 g_strchomp(*line); /* remove trailing spaces */
1060 if( g_str_has_suffix( *line, "{" ) )
1061 ++level;
1062 else if( g_str_has_suffix( *line, "}" ) )
1063 --level;
1064 lxpanel_put_line( fp, *line );
1065 }
1066 }
1067 g_strfreev( lines );
1068 }
1069 }
1070
1071 static void apply_config(Plugin* p)
1072 {
1073 menup* m = (menup*)p->priv;
1074 if( m->fname )
1075 fb_button_set_from_file( m->img, m->fname, -1, p->panel->icon_size, TRUE );
1076 }
1077
1078 static void menu_config( Plugin *p, GtkWindow* parent )
1079 {
1080 GtkWidget* dlg;
1081 menup* menu = (menup*)p->priv;
1082 dlg = create_generic_config_dlg( _(p->class->name),
1083 GTK_WIDGET(parent),
1084 (GSourceFunc) apply_config, (gpointer) p,
1085 _("Icon"), &menu->fname, CONF_TYPE_FILE_ENTRY,
1086 /* _("Caption"), &menu->caption, CONF_TYPE_STR, */
1087 NULL );
1088 gtk_window_present( GTK_WINDOW(dlg) );
1089 }
1090
1091 /* Callback when panel configuration changes. */
1092 static void menu_panel_configuration_changed(Plugin * p)
1093 {
1094 apply_config(p);
1095 }
1096
1097 PluginClass menu_plugin_class = {
1098
1099 PLUGINCLASS_VERSIONING,
1100
1101 type : "menu",
1102 name : N_("Menu"),
1103 version: "2.0",
1104 description : N_("Application Menu"),
1105
1106 constructor : menu_constructor,
1107 destructor : menu_destructor,
1108 config : menu_config,
1109 save : save_config,
1110 panel_configuration_changed : menu_panel_configuration_changed
1111 };
1112