26c5ea835eb60a2fdf36579b07cc8527848eb983
[lxde/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 "panel.h"
27 #include "misc.h"
28 #include "plugin.h"
29 #include "bg.h"
30
31 #include "ptk-app-menu.h"
32
33 //#define DEBUG
34 #include "dbg.h"
35
36 /*
37 * SuxPanel version 0.1
38 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
39 */
40
41 /*
42 * menu style code was taken from suxpanel
43 */
44
45 typedef struct {
46 GtkTooltips *tips;
47 GtkWidget *menu, *box, *bg, *label;
48 gulong handler_id;
49 int iconsize, paneliconsize;
50 GSList *files;
51 gboolean has_system_menu;
52 char* config_data;
53 int sysmenu_pos;
54 } menup;
55
56 static guint idle_loader = 0;
57
58 static void
59 menu_destructor(plugin *p)
60 {
61 menup *m = (menup *)p->priv;
62
63 ENTER;
64
65 if( G_UNLIKELY( idle_loader ) )
66 {
67 g_source_remove( idle_loader );
68 idle_loader = 0;
69 }
70 if( m->has_system_menu )
71 p->panel->system_menus = g_slist_remove( p->panel->system_menus, p );
72
73 g_signal_handler_disconnect(G_OBJECT(m->bg), m->handler_id);
74 gtk_widget_destroy(m->menu);
75 /* The widget is destroyed in plugin_stop().
76 gtk_widget_destroy(m->box);
77 */
78 g_free(m);
79 RET();
80 }
81
82 static void
83 spawn_app(GtkWidget *widget, gpointer data)
84 {
85 GError *error = NULL;
86
87 ENTER;
88 if (data) {
89 if (! g_spawn_command_line_async(data, &error) ) {
90 ERR("can't spawn %s\nError is %s\n", (char *)data, error->message);
91 g_error_free (error);
92 }
93 }
94 RET();
95 }
96
97
98 static void
99 run_command(GtkWidget *widget, void (*cmd)(void))
100 {
101 ENTER;
102 cmd();
103 RET();
104 }
105
106 static void
107 menu_pos(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, GtkWidget *widget)
108 {
109 int ox, oy, w, h;
110 plugin *p;
111
112 ENTER;
113 p = g_object_get_data(G_OBJECT(widget), "plugin");
114 gdk_window_get_origin(widget->window, &ox, &oy);
115 w = GTK_WIDGET(menu)->requisition.width;
116 h = GTK_WIDGET(menu)->requisition.height;
117 if (p->panel->orientation == ORIENT_HORIZ) {
118 *x = ox;
119 if (*x + w > gdk_screen_width())
120 *x = ox + widget->allocation.width - w;
121 *y = oy - h;
122 if (*y < 0)
123 *y = oy + widget->allocation.height;
124 } else {
125 *x = ox + widget->allocation.width;
126 if (*x > gdk_screen_width())
127 *x = ox - w;
128 *y = oy;
129 if (*y + h > gdk_screen_height())
130 *y = oy + widget->allocation.height - h;
131 }
132 DBG("widget: x,y=%d,%d w,h=%d,%d\n", ox, oy,
133 widget->allocation.width, widget->allocation.height );
134 DBG("w-h %d %d\n", w, h);
135 *push_in = TRUE;
136 RET();
137 }
138
139 static void
140 reload_system_menu( GtkMenu* menu )
141 {
142 GList *children, *child;
143 GtkMenuItem* item;
144 GtkWidget* sub_menu;
145 gint idx;
146 children = gtk_container_get_children( GTK_CONTAINER(menu) );
147 for( child = children, idx = 0; child; child = child->next, ++idx ) {
148 item = GTK_MENU_ITEM( child->data );
149 if( ptk_app_menu_item_has_data( item ) ) {
150 do {
151 item = GTK_MENU_ITEM( child->data );
152 child = child->next;
153 gtk_widget_destroy( GTK_WIDGET(item) );
154 }while( child && ptk_app_menu_item_has_data( child->data ) );
155 ptk_app_menu_insert_items( menu, idx );
156 if( ! child )
157 break;
158 }
159 else if( ( sub_menu = gtk_menu_item_get_submenu( item ) ) ) {
160 reload_system_menu( GTK_MENU(sub_menu) );
161 }
162 }
163 g_list_free( children );
164 }
165
166 static void show_menu( GtkWidget* widget, plugin* p, int btn, guint32 time )
167 {
168 menup* m = (menup*)p->priv;
169 /* reload system menu items if needed */
170 if( m->has_system_menu && ptk_app_menu_need_reload() ) {
171 GSList* l;
172 /* FIXME: Reload all system menus here.
173 This is dirty, but I don't know any better way. */
174 for( l = p->panel->system_menus; l; l = l->next ) {
175 plugin* _p = (plugin*)l->data;
176 menup* _m = (menup*)_p->priv;
177 reload_system_menu( GTK_MENU(_m->menu) );
178 }
179 }
180 gtk_menu_popup(GTK_MENU(m->menu),
181 NULL, NULL,
182 (GtkMenuPositionFunc)menu_pos, widget,
183 btn, time);
184 }
185
186 static gboolean
187 my_button_pressed(GtkWidget *widget, GdkEventButton *event, plugin* p)
188 {
189 ENTER;
190 if ((event->type == GDK_BUTTON_PRESS)
191 && (event->x >=0 && event->x < widget->allocation.width)
192 && (event->y >=0 && event->y < widget->allocation.height)) {
193 show_menu( widget, p, event->button, event->time );
194 }
195 RET(TRUE);
196 }
197
198 gboolean show_system_menu( gpointer system_menu )
199 {
200 plugin* p = (plugin*)system_menu;
201 menup* m = (menup*)p->priv;
202 show_menu( m->bg, p, 0, GDK_CURRENT_TIME );
203 return FALSE;
204 }
205
206 static GtkWidget *
207 make_button(plugin *p, gchar *fname, gchar *name, GtkWidget *menu)
208 {
209 int w, h;
210 menup *m;
211
212 ENTER;
213 m = (menup *)p->priv;
214 m->menu = menu;
215 if (p->panel->orientation == ORIENT_HORIZ) {
216 w = 10000;
217 h = p->panel->ah;
218 } else {
219 w = p->panel->aw;
220 h = 10000;
221 }
222 m->bg = fb_button_new_from_file_with_label(fname, w, h, 0xFF0000, TRUE,
223 (p->panel->orientation == ORIENT_HORIZ ? name : NULL));
224 gtk_widget_show(m->bg);
225 gtk_box_pack_start(GTK_BOX(m->box), m->bg, FALSE, FALSE, 0);
226
227 m->handler_id = g_signal_connect (G_OBJECT (m->bg), "button-press-event",
228 G_CALLBACK (my_button_pressed), p);
229 g_object_set_data(G_OBJECT(m->bg), "plugin", p);
230
231 RET(m->bg);
232 }
233
234
235 static GtkWidget *
236 read_item(plugin *p, char** fp)
237 {
238 line s;
239 gchar *name, *fname, *action;
240 GtkWidget *item;
241 menup *m = (menup *)p->priv;
242 command *cmd_entry = NULL;
243
244 ENTER;
245 s.len = 256;
246 name = fname = action = NULL;
247
248 if( fp )
249 {
250 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
251 if (s.type == LINE_VAR) {
252 if (!g_ascii_strcasecmp(s.t[0], "image"))
253 fname = expand_tilda(s.t[1]);
254 else if (!g_ascii_strcasecmp(s.t[0], "name"))
255 name = g_strdup(s.t[1]);
256 else if (!g_ascii_strcasecmp(s.t[0], "action"))
257 action = g_strdup(s.t[1]);
258 else if (!g_ascii_strcasecmp(s.t[0], "command")) {
259 command *tmp;
260
261 for (tmp = commands; tmp->name; tmp++) {
262 if (!g_ascii_strcasecmp(s.t[1], tmp->name)) {
263 cmd_entry = tmp;
264 break;
265 }
266 }
267 } else {
268 ERR( "menu/item: unknown var %s\n", s.t[0]);
269 goto error;
270 }
271 }
272 }
273 }
274 /* menu button */
275 if( cmd_entry ) /* built-in commands */
276 {
277 item = gtk_image_menu_item_new_with_label( _(cmd_entry->disp_name) );
278 g_signal_connect(G_OBJECT(item), "activate", (GCallback)run_command, cmd_entry->cmd);
279 }
280 else
281 {
282 item = gtk_image_menu_item_new_with_label(name ? name : "");
283 if (action) {
284 g_signal_connect(G_OBJECT(item), "activate", (GCallback)spawn_app, action);
285 }
286 }
287 gtk_container_set_border_width(GTK_CONTAINER(item), 0);
288 g_free(name);
289 if (fname) {
290 GtkWidget *img;
291
292 img = gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
293 gtk_widget_show(img);
294 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item), img);
295 g_free(fname);
296 }
297 RET(item);
298
299 error:
300 g_free(fname);
301 g_free(name);
302 g_free(action);
303 RET(NULL);
304 }
305
306 static GtkWidget *
307 read_separator(plugin *p, char **fp)
308 {
309 line s;
310
311 ENTER;
312 s.len = 256;
313 if( fp )
314 {
315 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
316 ERR("menu: error - separator can not have paramteres\n");
317 RET(NULL);
318 }
319 }
320 RET(gtk_separator_menu_item_new());
321 }
322
323 static gboolean on_idle( panel* p )
324 {
325 GSList* l;
326 /* Reload all system menus here.
327 This is dirty, but I don't know any better way. */
328 for( l = p->system_menus; l; l = l->next ) {
329 plugin* _p = (plugin*)l->data;
330 menup* _m = (menup*)_p->priv;
331 reload_system_menu( GTK_MENU(_m->menu) );
332 }
333 idle_loader = 0;
334 return FALSE; /* remove the handler */
335 }
336
337 static void
338 read_system_menu(GtkMenu* menu, plugin *p, char** fp)
339 {
340 line s;
341 menup *m = (menup *)p->priv;
342 GtkWidget* fake;
343
344 ENTER;
345 s.len = 256;
346 if( fp )
347 {
348 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
349 ERR("menu: error - system can not have paramteres\n");
350 RET();
351 }
352 }
353
354 /* ptk_app_menu_insert_items( menu, -1 ); */
355 /* Don't load the real system menu here to speed up startup.
356 * Let's add a fake item to cheat PtkAppMenu as a place holder,
357 * and we utilize reload_system_menu() to load the real menu later. */
358 fake = gtk_separator_menu_item_new();
359 PTK_APP_MENU_ITEM_ID = g_quark_from_static_string( "PtkAppMenuItem" );
360 g_object_set_qdata( fake, PTK_APP_MENU_ITEM_ID, GUINT_TO_POINTER(TRUE) );
361 gtk_menu_shell_append( menu, fake);
362
363 m->has_system_menu = TRUE;
364
365 p->panel->system_menus = g_slist_append( p->panel->system_menus, p );
366
367 if( idle_loader == 0 ) /* delay the loading, and do it in idle handler */
368 idle_loader = g_idle_add( (GSourceFunc)on_idle, p->panel );
369
370 RET();
371 }
372
373 static void
374 read_include(plugin *p, char **fp)
375 {
376 ENTER;
377 #if 0
378 gchar *name;
379 line s;
380 menup *m = (menup *)p->priv;
381 /* FIXME: this is disabled */
382 ENTER;
383 s.len = 256;
384 name = NULL;
385 if( fp )
386 {
387 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
388 if (s.type == LINE_VAR) {
389 if (!g_ascii_strcasecmp(s.t[0], "name"))
390 name = expand_tilda(s.t[1]);
391 else {
392 ERR( "menu/include: unknown var %s\n", s.t[0]);
393 RET();
394 }
395 }
396 }
397 }
398 if ((fp = fopen(name, "r"))) {
399 LOG(LOG_INFO, "Including %s\n", name);
400 m->files = g_slist_prepend(m->files, fp);
401 p->fp = fp;
402 } else {
403 ERR("Can't include %s\n", name);
404 }
405 if (name) g_free(name);
406 #endif
407 RET();
408 }
409
410 static GtkWidget *
411 read_submenu(plugin *p, char** fp, gboolean as_item)
412 {
413 line s;
414 GtkWidget *mi, *menu;
415 gchar name[256], *fname;
416 menup *m = (menup *)p->priv;
417
418 ENTER;
419 s.len = 256;
420 menu = gtk_menu_new ();
421 gtk_container_set_border_width(GTK_CONTAINER(menu), 0);
422
423 fname = 0;
424 name[0] = 0;
425 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END) {
426 if (s.type == LINE_BLOCK_START) {
427 mi = NULL;
428 if (!g_ascii_strcasecmp(s.t[0], "item")) {
429 mi = read_item(p, fp);
430 } else if (!g_ascii_strcasecmp(s.t[0], "separator")) {
431 mi = read_separator(p, fp);
432 } else if (!g_ascii_strcasecmp(s.t[0], "system")) {
433 read_system_menu(GTK_MENU(menu), p, fp); /* add system menu items */
434 continue;
435 } else if (!g_ascii_strcasecmp(s.t[0], "menu")) {
436 mi = read_submenu(p, fp, TRUE);
437 } else if (!g_ascii_strcasecmp(s.t[0], "include")) {
438 read_include(p, fp);
439 continue;
440 } else {
441 ERR("menu: unknown block %s\n", s.t[0]);
442 goto error;
443 }
444 if (!mi) {
445 ERR("menu: can't create menu item\n");
446 goto error;
447 }
448 gtk_widget_show(mi);
449 gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
450 } else if (s.type == LINE_VAR) {
451 if (!g_ascii_strcasecmp(s.t[0], "image"))
452 fname = expand_tilda(s.t[1]);
453 else if (!g_ascii_strcasecmp(s.t[0], "name"))
454 strcpy(name, s.t[1]);
455 else {
456 ERR("menu: unknown var %s\n", s.t[0]);
457 goto error;
458 }
459 } else if (s.type == LINE_NONE) {
460 if (m->files) {
461 /*
462 fclose(p->fp);
463 p->fp = m->files->data;
464 */
465 m->files = g_slist_delete_link(m->files, m->files);
466 }
467 } else {
468 ERR("menu: illegal in this context %s\n", s.str);
469 goto error;
470 }
471 }
472 if (as_item) {
473 mi = gtk_image_menu_item_new_with_label(name ? name : "");
474 if (fname) {
475 GtkWidget *img;
476 img = gtk_image_new_from_file_scaled(fname, m->iconsize, m->iconsize, TRUE);
477 gtk_widget_show(img);
478 gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), img);
479 g_free(fname);
480 }
481 gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), menu);
482 RET(mi);
483 } else {
484 mi = make_button(p, fname, name, menu);
485 if (fname)
486 g_free(fname);
487 RET(mi);
488 }
489
490 error:
491 // FIXME: we need to recursivly destroy all child menus and their items
492 gtk_widget_destroy(menu);
493 g_free(fname);
494 g_free(name);
495 RET(NULL);
496 }
497
498 static int
499 menu_constructor(plugin *p, char **fp)
500 {
501 menup *m;
502 static char default_config[] =
503 "image=" PACKAGE_DATA_DIR "/lxpanel/images/my-computer.svg\n"
504 "system {\n"
505 "}\n"
506 "separator {\n"
507 "}\n"
508 "item {\n"
509 "command=run\n"
510 "}\n"
511 "item {\n"
512 "image=" PACKAGE_DATA_DIR "/lxpanel/images/gnome-setting.svg\n"
513 "command = configure\n"
514 "}\n"
515 "separator {\n"
516 "}\n"
517 "item {\n"
518 "image=gnome-logout\n"
519 "command=logout\n"
520 "}\n"
521 "}\n";
522 char *config_start, *config_end, *config_default = default_config;
523
524 ENTER;
525 m = g_new0(menup, 1);
526 g_return_val_if_fail(m != NULL, 0);
527 p->priv = m;
528
529 //gtk_rc_parse_string(menu_rc);
530 if (p->panel->orientation == ORIENT_HORIZ)
531 m->paneliconsize = p->panel->ah
532 - 2* GTK_WIDGET(p->panel->box)->style->ythickness;
533 else
534 m->paneliconsize = p->panel->aw
535 - 2* GTK_WIDGET(p->panel->box)->style->xthickness;
536 m->iconsize = 22;
537
538 m->box = gtk_hbox_new(FALSE, 0);
539 gtk_container_set_border_width(GTK_CONTAINER(m->box), 0);
540
541 if( ! fp )
542 fp = &config_default;
543
544 config_start = *fp;
545 if (!read_submenu(p, fp, FALSE)) {
546 ERR("menu: plugin init failed\n");
547 goto error;
548 }
549 config_end = *fp - 1;
550 while( *config_end != '}' && config_end > config_start ) {
551 --config_end;
552 }
553 if( *config_end == '}' )
554 --config_end;
555
556 m->config_data = g_strndup( config_start,
557 (config_end-config_start) );
558
559 p->pwid = m->box;
560
561 RET(1);
562
563 error:
564 menu_destructor(p);
565 RET(0);
566 }
567
568 static void save_config( plugin* p, FILE* fp )
569 {
570 menup* menu = (menup*)p->priv;
571 if( menu->config_data ) {
572 char** lines = g_strsplit( menu->config_data, "\n", 0 );
573 char** line;
574 for( line = lines; *line; ++line ) {
575 g_strstrip( *line );
576 if( **line )
577 lxpanel_put_line( fp, *line );
578 }
579 g_strfreev( lines );
580 }
581 }
582
583 plugin_class menu_plugin_class = {
584 fname: NULL,
585 count: 0,
586
587 type : "menu",
588 name : N_("Menu"),
589 version: "1.0",
590 description : N_("Provide Menu"),
591
592 constructor : menu_constructor,
593 destructor : menu_destructor,
594 config : NULL,
595 save : save_config
596 };
597