Enabling multithreaded compilation.
[debian/lxpanel.git] / src / plugins / launchbar.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 /* Modified by Giuseppe Penone <giuspen@gmail.com> starting from 2012-08 and lxpanel 0.5.10 */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <signal.h>
32 #include <errno.h>
33
34 #include <gdk-pixbuf/gdk-pixbuf.h>
35 #include <glib/gi18n.h>
36
37 #include <menu-cache.h>
38
39 #include "panel.h"
40 #include "misc.h"
41 #include "plugin.h"
42 #include "icon-grid.h"
43 #include "menu-policy.h"
44
45 #include "dbg.h"
46
47 /* Drag and drop target info. */
48 enum {
49 TARGET_URILIST,
50 TARGET_UTF8_STRING,
51 TARGET_STRING,
52 TARGET_TEXT,
53 TARGET_COMPOUND_TEXT
54 };
55
56 static const GtkTargetEntry target_table[] = {
57 { "text/uri-list", 0, TARGET_URILIST},
58 { "UTF8_STRING", 0, TARGET_UTF8_STRING },
59 { "COMPOUND_TEXT", 0, 0 },
60 { "TEXT", 0, 0 },
61 { "STRING", 0, 0 }
62 };
63
64 /* Column definitions for configuration dialogs. */
65 enum {
66 COL_ICON,
67 COL_TITLE,
68 COL_ICON_NAME,
69 COL_BTN,
70 N_COLS
71 };
72
73 static const char DESKTOP_ENTRY[] = "Desktop Entry";
74
75 /* Representative of one launch button.
76 * Note that the launch parameters come from the specified desktop file, or from the configuration file.
77 * This structure is also used during the "add to launchbar" dialog to hold menu items. */
78 typedef struct {
79 Plugin * plugin; /* Back pointer to plugin */
80 GtkWidget * widget; /* Pointer to button */
81 GtkWidget * image_widget; /* Pointer to image */
82 gchar * desktop_id; /* Name of application (desktop file name less the .desktop) */
83 gchar * image; /* Image icon (from Icon entry) */
84 gchar * action; /* Action (from Exec entry) */
85 gchar * tooltip; /* Tooltip (from Name entry) */
86 guchar use_terminal : 1; /* True if Terminal=true or from configuration file */
87 guchar customize_image : 1; /* True if image icon from configuration file */
88 guchar customize_action : 1; /* True if action from configuration file */
89 guchar customize_tooltip : 1; /* True if tooltip from configuration file */
90 } LaunchButton;
91
92 /* Private context for launchbar plugin. */
93 typedef struct {
94 IconGrid * icon_grid; /* Icon grid managing the container */
95 GSList * buttons; /* Launchbar buttons */
96 GtkWidget * config_dlg; /* Configuration dialog */
97 LaunchButton * bootstrap_button; /* Bootstrapping button for empty launchbar */
98 GtkWidget *p_button_add, *p_button_remove, *p_label_menu_app_exec, *p_label_def_app_exec;
99 } LaunchbarPlugin;
100
101 void panel_config_save(Panel * panel); /* defined in configurator.c */
102
103 static void launchbutton_free(LaunchButton * btn);
104 static gboolean launchbutton_press_event(GtkWidget * widget, GdkEventButton * event, LaunchButton * b);
105 static void launchbutton_drag_data_received_event(
106 GtkWidget * widget,
107 GdkDragContext * context,
108 gint x,
109 gint y,
110 GtkSelectionData * sd,
111 guint info,
112 guint time,
113 LaunchButton * b);
114 static void launchbutton_build_bootstrap(Plugin * p);
115 static void launchbutton_build_gui(Plugin * p, LaunchButton * btn);
116 static int launchbutton_constructor(Plugin * p, char ** fp);
117 static int launchbar_constructor(Plugin * p, char ** fp);
118 static void launchbar_destructor(Plugin * p);
119 static void launchbar_configure_add_button(GtkButton * widget, Plugin * p);
120 static void launchbar_configure_remove_button(GtkButton * widget, Plugin * p);
121 static void launchbar_configure_move_up_button(GtkButton * widget, Plugin * p);
122 static void launchbar_configure_move_down_button(GtkButton * widget, Plugin * p);
123 static void launchbar_configure_response(GtkDialog * dlg, int response, Plugin * p);
124 static void launchbar_configure_initialize_list(Plugin * p, GtkWidget * dlg, GtkTreeView * view, gboolean from_menu);
125 static void launchbar_configure(Plugin * p, GtkWindow * parent);
126 static void launchbar_save_configuration(Plugin * p, FILE * fp);
127 static void launchbar_panel_configuration_changed(Plugin * p);
128
129 /* Deallocate a LaunchButton. */
130 static void launchbutton_free(LaunchButton * btn)
131 {
132 g_free(btn->desktop_id);
133 g_free(btn->image);
134 g_free(btn->action);
135 g_free(btn->tooltip);
136 g_free(btn);
137 }
138
139 static gboolean on_defined_view_button_press_event(GtkWidget *p_widget, GdkEventButton *p_event, gpointer p_data)
140 {
141 if(p_event->button == 1)
142 {
143 if(p_event->type == GDK_2BUTTON_PRESS)
144 {
145 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
146 gtk_button_clicked(GTK_BUTTON(lb->p_button_remove));
147 }
148 }
149 return FALSE;
150 }
151
152 static void on_defined_view_cursor_changed(GtkTreeView *p_treeview, gpointer p_data)
153 {
154 gboolean label_set = FALSE;
155 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
156 GtkTreeIter tree_iter_sel;
157 GtkTreeModel* p_treemodel = gtk_tree_view_get_model(p_treeview);
158 GtkTreeSelection *p_treeselection = gtk_tree_view_get_selection(p_treeview);
159 if(gtk_tree_selection_get_selected(p_treeselection,
160 (GtkTreeModel **)(&p_treemodel),
161 &tree_iter_sel))
162 {
163 LaunchButton * p_btn;
164 gtk_tree_model_get(p_treemodel, &tree_iter_sel, COL_BTN, &p_btn, -1);
165 if( (p_btn != NULL) && (p_btn->action != NULL) )
166 {
167 GString *p_gstring = g_string_new("");
168 g_string_printf(p_gstring, "<i>Exec =</i> %s", p_btn->action);
169 gtk_label_set_markup(GTK_LABEL(lb->p_label_def_app_exec), p_gstring->str);
170 g_string_free(p_gstring, TRUE/*free also gstring->str*/);
171 gtk_widget_set_visible(lb->p_label_def_app_exec, TRUE);
172 label_set = TRUE;
173 }
174 }
175 if(!label_set)
176 {
177 gtk_widget_set_visible(lb->p_label_def_app_exec, FALSE);
178 }
179 }
180
181 static void on_menu_view_cursor_changed(GtkTreeView *p_treeview, gpointer p_data)
182 {
183 gboolean label_set = FALSE;
184 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
185 GtkTreeIter tree_iter_sel;
186 GtkTreeModel* p_treemodel = gtk_tree_view_get_model(p_treeview);
187 GtkTreeSelection *p_treeselection = gtk_tree_view_get_selection(p_treeview);
188 if(gtk_tree_selection_get_selected(p_treeselection,
189 (GtkTreeModel **)(&p_treemodel),
190 &tree_iter_sel))
191 {
192 LaunchButton * p_btn;
193 gtk_tree_model_get(p_treemodel, &tree_iter_sel, COL_BTN, &p_btn, -1);
194 if( (p_btn != NULL) && (p_btn->action != NULL) )
195 {
196 GString *p_gstring = g_string_new("");
197 g_string_printf(p_gstring, "<i>Exec =</i> %s", p_btn->action);
198 gtk_label_set_markup(GTK_LABEL(lb->p_label_menu_app_exec), p_gstring->str);
199 g_string_free(p_gstring, TRUE/*free also gstring->str*/);
200 gtk_widget_set_visible(lb->p_label_menu_app_exec, TRUE);
201 label_set = TRUE;
202 }
203 }
204 if(!label_set)
205 {
206 gtk_widget_set_visible(lb->p_label_menu_app_exec, FALSE);
207 }
208 }
209
210 static gboolean on_menu_view_button_press_event(GtkWidget *p_widget, GdkEventButton *p_event, gpointer p_data)
211 {
212 if(p_event->button == 1)
213 {
214 if(p_event->type == GDK_2BUTTON_PRESS)
215 {
216 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
217 gtk_button_clicked(GTK_BUTTON(lb->p_button_add));
218 }
219 }
220 else if(p_event->button == 2)
221 {
222 GtkTreePath *p_tree_path;
223 if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(p_widget),
224 p_event->x, p_event->y,
225 &p_tree_path,
226 NULL, NULL, NULL))
227 {
228 if(gtk_tree_view_row_expanded(GTK_TREE_VIEW(p_widget), p_tree_path))
229 gtk_tree_view_collapse_row(GTK_TREE_VIEW(p_widget), p_tree_path);
230 else
231 gtk_tree_view_expand_row(GTK_TREE_VIEW(p_widget), p_tree_path, FALSE);
232 gtk_tree_path_free(p_tree_path);
233 }
234 }
235 return FALSE;
236 }
237
238 /* Handler for "button-press-event" event from launchbar button. */
239 static gboolean launchbutton_press_event(GtkWidget * widget, GdkEventButton * event, LaunchButton * b)
240 {
241 /* Standard right-click handling. */
242 if (plugin_button_press_event(widget, event, b->plugin))
243 return TRUE;
244
245 if (event->button == 1) /* left button */
246 {
247 if (b->desktop_id == NULL) /* The bootstrap button */
248 launchbar_configure(b->plugin, NULL);
249 else if (b->action != NULL)
250 lxpanel_launch_app(b->action, NULL, b->use_terminal);
251 }
252 return TRUE;
253 }
254
255 /* Handler for "drag-data-received" event from launchbar button. */
256 static void launchbutton_drag_data_received_event(
257 GtkWidget * widget,
258 GdkDragContext * context,
259 gint x,
260 gint y,
261 GtkSelectionData * sd,
262 guint info,
263 guint time,
264 LaunchButton * b)
265 {
266 #if GTK_CHECK_VERSION(2,14,0)
267 if (gtk_selection_data_get_length(sd) > 0)
268 #else
269 if (sd->lengh > 0)
270 #endif
271 {
272 if (info == TARGET_URILIST)
273 {
274 #if GTK_CHECK_VERSION(2,14,0)
275 gchar * s = (gchar *) gtk_selection_data_get_data(sd);
276 #else
277 gchar * s = (gchar *) sd->data;
278 #endif
279 #if GTK_CHECK_VERSION(2,14,0)
280 gchar * end = s + gtk_selection_data_get_length(sd);
281 #else
282 gchar * end = s + sd->lenght;
283 #endif
284 gchar * str = g_strdup(b->action);
285 while (s < end)
286 {
287 while (s < end && g_ascii_isspace(*s))
288 s++;
289 gchar * e = s;
290 while (e < end && !g_ascii_isspace(*e))
291 e++;
292 if (s != e)
293 {
294 *e = 0;
295 s = g_filename_from_uri(s, NULL, NULL);
296 if (s)
297 {
298 gchar * tmp = g_strconcat(str, " '", s, "'", NULL);
299 g_free(str);
300 g_free(s);
301 str = tmp;
302 }
303 }
304 s = e+1;
305 }
306
307 g_spawn_command_line_async(str, NULL);
308 g_free(str);
309 }
310 }
311 }
312
313 /* Build the graphic elements for the bootstrap launchbar button. */
314 static void launchbutton_build_bootstrap(Plugin * p)
315 {
316 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
317
318 if (lb->bootstrap_button == NULL)
319 {
320 /* Build a button that has the stock "Add" icon.
321 * The "desktop-id" being NULL is the marker that this is the bootstrap button. */
322 lb->bootstrap_button = g_new0(LaunchButton, 1);
323 lb->bootstrap_button->plugin = p;
324
325 /* Create an event box. */
326 GtkWidget * event_box = gtk_event_box_new();
327 gtk_container_set_border_width(GTK_CONTAINER(event_box), 0);
328 #if GLIB_CHECK_VERSION(2,18,0)
329 gtk_widget_set_can_focus (event_box, FALSE);
330 #else
331 GTK_WIDGET_UNSET_FLAGS(event_box, GTK_CAN_FOCUS);
332 #endif
333 lb->bootstrap_button->widget = event_box;
334 g_signal_connect(event_box, "button-press-event", G_CALLBACK(launchbutton_press_event), lb->bootstrap_button);
335
336 /* Create an image containing the stock "Add" icon as a child of the event box. */
337 lb->bootstrap_button->image_widget = gtk_image_new_from_pixbuf(
338 lxpanel_load_icon(GTK_STOCK_ADD, p->panel->icon_size, p->panel->icon_size, FALSE));
339 gtk_misc_set_padding(GTK_MISC(lb->bootstrap_button->image_widget), 0, 0);
340 gtk_misc_set_alignment(GTK_MISC(lb->bootstrap_button->image_widget), 0, 0);
341 gtk_container_add(GTK_CONTAINER(event_box), lb->bootstrap_button->image_widget);
342
343 /* Add the bootstrap button to the icon grid. By policy it is empty at this point. */
344 icon_grid_add(lb->icon_grid, event_box, TRUE);
345 }
346 else
347 icon_grid_set_visible(lb->icon_grid, lb->bootstrap_button->widget, TRUE);
348 }
349
350 static gboolean load_app_key_file(gchar *filepath, GKeyFile *p_gkeyfile)
351 {
352 gboolean loaded;
353 if (g_path_is_absolute(filepath))
354 {
355 loaded = g_key_file_load_from_file(p_gkeyfile, filepath, G_KEY_FILE_NONE, NULL );
356 }
357 else
358 {
359 /* Load from the freedesktop.org specified data directories. */
360 gchar * full_id = g_strconcat("applications/", filepath, NULL);
361 loaded = g_key_file_load_from_data_dirs(
362 p_gkeyfile, full_id, &filepath, G_KEY_FILE_NONE, NULL);
363 g_free(full_id);
364 }
365 return loaded;
366 }
367
368
369 /* Build the graphic elements for a launchbar button. The desktop_id field is already established. */
370 static void launchbutton_build_gui(Plugin * p, LaunchButton * btn)
371 {
372 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
373
374 if (btn->desktop_id != NULL)
375 {
376 /* There is a valid desktop file name. Try to open it. */
377 GKeyFile * desktop = g_key_file_new();
378 gboolean loaded = load_app_key_file(btn->desktop_id, desktop);
379
380 if (loaded)
381 {
382 /* Desktop file located. Get Icon, Name, Exec, and Terminal parameters. */
383 gchar * icon = g_key_file_get_string(desktop, DESKTOP_ENTRY, "Icon", NULL);
384 gchar * title = g_key_file_get_locale_string(desktop, DESKTOP_ENTRY, "Name", NULL, NULL);
385 if ((btn->image == NULL) && (icon != NULL))
386 btn->image = icon;
387
388 if ( ! btn->customize_action )
389 {
390 gchar * exec = g_key_file_get_string(desktop, DESKTOP_ENTRY, "Exec", NULL);
391 btn->action = translate_exec_to_cmd(exec, icon, title, btn->desktop_id);
392 g_free(exec);
393 }
394
395 btn->use_terminal = g_key_file_get_boolean(desktop, DESKTOP_ENTRY, "Terminal", NULL);
396
397 if ( ! btn->customize_tooltip)
398 btn->tooltip = title;
399 if (btn->image != icon)
400 g_free(icon);
401 if (btn->tooltip != title)
402 g_free(title);
403 }
404
405 g_key_file_free(desktop);
406 }
407
408 /* Create a button with the specified icon. */
409 GtkWidget * button = fb_button_new_from_file(btn->image, p->panel->icon_size, p->panel->icon_size, PANEL_ICON_HIGHLIGHT, TRUE);
410 btn->widget = button;
411 #if GLIB_CHECK_VERSION(2,18,0)
412 gtk_widget_set_can_focus(button, FALSE);
413 #else
414 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
415 #endif
416
417 if (btn->tooltip != NULL)
418 gtk_widget_set_tooltip_text(button, btn->tooltip);
419
420 /* Add the button to the icon grid. */
421 icon_grid_add(lb->icon_grid, button, TRUE);
422
423 /* Drag and drop support. */
424 gtk_drag_dest_set(GTK_WIDGET(button),
425 GTK_DEST_DEFAULT_ALL,
426 target_table, G_N_ELEMENTS(target_table),
427 GDK_ACTION_COPY);
428
429 /* Connect signals. */
430 g_signal_connect(button, "button-press-event", G_CALLBACK(launchbutton_press_event), (gpointer) btn);
431 g_signal_connect(button, "drag_data_received", G_CALLBACK(launchbutton_drag_data_received_event), (gpointer) btn);
432
433 /* If the list goes from null to non-null, remove the bootstrap button. */
434 if ((lb->buttons == NULL) && (lb->bootstrap_button != NULL))
435 icon_grid_set_visible(lb->icon_grid, lb->bootstrap_button->widget, FALSE);
436
437 /* Append at end of list to preserve configured order. */
438 lb->buttons = g_slist_append(lb->buttons, btn);
439
440 /* Show the widget and return. */
441 gtk_widget_show(button);
442 plugin_widget_set_background(button, p->panel);
443 }
444
445 /* Read the configuration file entry for a launchbar button and create it. */
446 static int launchbutton_constructor(Plugin * p, char ** fp)
447 {
448 /* Allocate the LaunchButton structure. */
449 LaunchButton * btn = g_new0(LaunchButton, 1);
450 btn->plugin = p;
451
452 /* Read parameters from the configuration file. */
453 line s;
454 s.len = 256;
455 if (fp != NULL)
456 {
457 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
458 {
459 if (s.type == LINE_NONE)
460 {
461 ERR( "launchbar: illegal token %s\n", s.str);
462 launchbutton_free(btn);
463 return 0;
464 }
465 if (s.type == LINE_VAR)
466 {
467 if (g_ascii_strcasecmp(s.t[0], "id") == 0)
468 btn->desktop_id = g_strdup(s.t[1]);
469 else if (g_ascii_strcasecmp(s.t[0], "image") == 0)
470 {
471 btn->customize_image = TRUE;
472 btn->image = expand_tilda(g_strdup(s.t[1]));
473 }
474 else if (g_ascii_strcasecmp(s.t[0], "tooltip") == 0)
475 {
476 btn->customize_tooltip = TRUE;
477 btn->tooltip = g_strdup(s.t[1]);
478 }
479 else if (g_ascii_strcasecmp(s.t[0], "action") == 0)
480 {
481 btn->customize_action = TRUE;
482 btn->action = g_strdup(s.t[1]);
483 }
484 else if (g_ascii_strcasecmp(s.t[0], "terminal") == 0)
485 {
486 btn->use_terminal = str2num(bool_pair, s.t[1], 0);
487 }
488 else
489 ERR( "launchbar: unknown var %s\n", s.t[0]);
490 }
491 else
492 {
493 ERR( "launchbar: illegal in this context %s\n", s.str);
494 launchbutton_free(btn);
495 return 0;
496 }
497 }
498 }
499
500 /* Build the structures and return. */
501 launchbutton_build_gui(p, btn);
502 return 1;
503 }
504
505 /* Plugin constructor. */
506 static int launchbar_constructor(Plugin * p, char ** fp)
507 {
508 static gchar * launchbar_rc = "style 'launchbar-style'\n"
509 "{\n"
510 "GtkWidget::focus-line-width = 0\n"
511 "GtkWidget::focus-padding = 0\n"
512 "GtkButton::default-border = { 0, 0, 0, 0 }\n"
513 "GtkButton::default-outside-border = { 0, 0, 0, 0 }\n"
514 "}\n"
515 "widget '*launchbar*' style 'launchbar-style'";
516
517 gtk_rc_parse_string(launchbar_rc);
518
519 /* Allocate plugin context and set into Plugin private data pointer. */
520 LaunchbarPlugin * lb = g_new0(LaunchbarPlugin, 1);
521 p->priv = lb;
522
523 /* Allocate top level widget and set into Plugin widget pointer. */
524 p->pwid = gtk_event_box_new();
525 #if GLIB_CHECK_VERSION(2,18,0)
526 gtk_widget_set_has_window(p->pwid, FALSE);
527 #else
528 GTK_WIDGET_SET_FLAGS(p->pwid, GTK_NO_WINDOW);
529 #endif
530 gtk_widget_set_name(p->pwid, "launchbar");
531
532 /* Allocate an icon grid manager to manage the container. */
533 GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
534 lb->icon_grid = icon_grid_new(p->panel, p->pwid, bo, p->panel->icon_size, p->panel->icon_size, 3, 0, p->panel->height);
535
536 /* Read parameters from the configuration file. */
537 if (fp != NULL)
538 {
539 line s;
540 s.len = 256;
541 while (lxpanel_get_line(fp, &s) != LINE_BLOCK_END)
542 {
543 if (s.type == LINE_NONE)
544 {
545 ERR( "launchbar: illegal token %s\n", s.str);
546 return FALSE;
547 }
548 if (s.type == LINE_BLOCK_START)
549 {
550 if (g_ascii_strcasecmp(s.t[0], "button") == 0)
551 {
552 if ( ! launchbutton_constructor(p, fp))
553 {
554 ERR( "launchbar: can't init button\n");
555 return FALSE;
556 }
557 }
558 else
559 {
560 ERR( "launchbar: unknown var %s\n", s.t[0]);
561 return FALSE;
562 }
563 }
564 else
565 {
566 ERR( "launchbar: illegal in this context %s\n", s.str);
567 return FALSE;
568 }
569 }
570 }
571
572 if (lb->buttons == NULL)
573 launchbutton_build_bootstrap(p);
574 return TRUE;
575 }
576
577 /* Plugin destructor. */
578 static void launchbar_destructor(Plugin * p)
579 {
580 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
581
582 /* Free the launchbar. */
583 g_slist_foreach(lb->buttons, (GFunc) launchbutton_free, NULL);
584 icon_grid_free(lb->icon_grid);
585
586 /* Free the bootstrap button if it exists. */
587 if (lb->bootstrap_button != NULL)
588 g_free(lb->bootstrap_button);
589
590 /* Ensure that the configuration dialog is dismissed. */
591 if (lb->config_dlg != NULL)
592 gtk_widget_destroy(lb->config_dlg);
593
594 /* Deallocate all memory. */
595 g_free(lb);
596 }
597
598 /* Handler for "clicked" action on launchbar configuration dialog "Add" button. */
599 static void launchbar_configure_add_button(GtkButton * widget, Plugin * p)
600 {
601 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
602 GtkTreeView * menu_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "menu_view"));
603 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
604 GtkTreeModel * list;
605 GtkTreeIter it;
606 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(menu_view), &list, &it))
607 {
608 LaunchButton * btn;
609 gtk_tree_model_get(list, &it, COL_BTN, &btn, -1);
610 if( btn == NULL )
611 return;
612
613 /* We have located a selected button.
614 * Add a launch button to the launchbar and refresh the view in the configuration dialog. */
615 LaunchButton * defined_button = g_new0(LaunchButton, 1);
616 defined_button->plugin = p;
617 defined_button->desktop_id = g_strdup(btn->desktop_id);
618 launchbutton_build_gui(p, defined_button);
619 GtkListStore * list = GTK_LIST_STORE(gtk_tree_view_get_model(defined_view));
620 GtkTreeIter it;
621 GdkPixbuf* pix;
622 gtk_list_store_append(list, &it);
623 pix = lxpanel_load_icon(btn->image, PANEL_ICON_SIZE, PANEL_ICON_SIZE, TRUE);
624 gtk_list_store_set(list, &it,
625 COL_ICON, pix,
626 COL_TITLE, ((btn->tooltip != NULL) ? btn->tooltip : btn->action),
627 COL_BTN, defined_button,
628 -1);
629 g_object_unref(pix);
630 }
631 }
632
633 /* Handler for "clicked" action on launchbar configuration dialog "Remove" button. */
634 static void launchbar_configure_remove_button(GtkButton * widget, Plugin * p)
635 {
636 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
637 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
638 GtkTreeModel * list;
639 GtkTreeIter it;
640 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
641 {
642 LaunchButton * btn;
643 gtk_tree_model_get(list, &it, COL_BTN, &btn, -1);
644
645 /* We have found a selected button.
646 * Remove it from the icon grid, the data structure, and the view. */
647 gtk_list_store_remove(GTK_LIST_STORE(list), &it);
648 icon_grid_remove(lb->icon_grid, btn->widget);
649 lb->buttons = g_slist_remove(lb->buttons, btn);
650 launchbutton_free(btn);
651
652 gtk_widget_set_visible(lb->p_label_def_app_exec, FALSE);
653
654 /* Put the bootstrap button back if the list becomes empty. */
655 if (lb->buttons == NULL)
656 launchbutton_build_bootstrap(p);
657 }
658 }
659
660 /* Handler for "clicked" action on launchbar configuration dialog "Move Up" button. */
661 static void launchbar_configure_move_up_button(GtkButton * widget, Plugin * p)
662 {
663 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
664
665 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
666 GtkTreeModel * list;
667 GtkTreeIter it;
668 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
669 {
670 LaunchButton *btn;
671 gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
672 GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
673 if ((gtk_tree_path_get_indices(path)[0] > 0)
674 && (gtk_tree_path_prev(path)))
675 {
676 GtkTreeIter it2;
677 if (gtk_tree_model_get_iter(list, &it2, path))
678 {
679 /* We have found a selected button that can be moved.
680 * Reorder it in the icon grid, the data structure, and the view. */
681 int i = gtk_tree_path_get_indices(path)[0];
682 lb->buttons = g_slist_remove(lb->buttons, btn);
683 lb->buttons = g_slist_insert(lb->buttons, btn, i);
684 gtk_list_store_move_before(GTK_LIST_STORE(list), &it, &it2);
685 icon_grid_reorder_child(lb->icon_grid, btn->widget, i);
686 }
687 }
688 gtk_tree_path_free(path);
689 }
690 }
691
692 /* Handler for "clicked" action on launchbar configuration dialog "Move Down" button. */
693 static void launchbar_configure_move_down_button(GtkButton * widget, Plugin * p)
694 {
695 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
696
697 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
698 GtkTreeModel * list;
699 GtkTreeIter it;
700 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
701 {
702 LaunchButton *btn;
703 gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
704 GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
705 int n = gtk_tree_model_iter_n_children(list, NULL);
706 if (gtk_tree_path_get_indices(path)[0] < (n - 1))
707 {
708 gtk_tree_path_next(path);
709 GtkTreeIter it2;
710 if (gtk_tree_model_get_iter( list, &it2, path))
711 {
712 /* We have found a selected button that can be moved.
713 * Reorder it in the icon grid, the data structure, and the view. */
714 int i = gtk_tree_path_get_indices(path)[0];
715 lb->buttons = g_slist_remove(lb->buttons, btn);
716 lb->buttons = g_slist_insert(lb->buttons, btn, i + 1);
717 gtk_list_store_move_after(GTK_LIST_STORE(list), &it, &it2);
718 icon_grid_reorder_child( lb->icon_grid, btn->widget, i);
719 }
720 }
721 gtk_tree_path_free(path);
722 }
723 }
724
725 static void launchbar_configure_free_btns_in_model(GtkTreeModel* model, GtkTreeIter *parent_it)
726 {
727 GtkTreeIter it;
728 if (gtk_tree_model_iter_children(model, &it, parent_it))
729 {
730 do
731 {
732 LaunchButton * btn;
733 gtk_tree_model_get(model, &it, COL_BTN, &btn, -1);
734 if(G_LIKELY(btn))
735 launchbutton_free(btn);
736 if( gtk_tree_model_iter_has_child(model, &it) )
737 launchbar_configure_free_btns_in_model(model, &it);
738 }
739 while (gtk_tree_model_iter_next(model, &it));
740 }
741 }
742
743 /* Handler for "response" signal from launchbar configuration dialog. */
744 static void launchbar_configure_response(GtkDialog * dlg, int response, Plugin * p)
745 {
746 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
747
748 /* Deallocate LaunchButtons that were loaded from the menu. */
749 GtkTreeView * menu_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "menu_view"));
750 GtkTreeModel * model = gtk_tree_view_get_model(menu_view);
751 launchbar_configure_free_btns_in_model(model, NULL);
752
753 /* Deallocate the configuration dialog. */
754 lb->config_dlg = NULL;
755 gtk_widget_destroy(GTK_WIDGET(dlg));
756 }
757
758 static void launchbar_configure_update_icons(GtkTreeStore* tree, GtkTreeIter* parent_it)
759 {
760 GtkTreeIter it;
761 if(gtk_tree_model_iter_children(GTK_TREE_MODEL(tree), &it, parent_it))
762 {
763 do
764 {
765 char* name;
766 GdkPixbuf* pix;
767 gtk_tree_model_get(GTK_TREE_MODEL(tree), &it, COL_ICON, &pix, -1);
768 if(!pix)
769 {
770 gtk_tree_model_get(GTK_TREE_MODEL(tree), &it, COL_ICON_NAME, &name, -1);
771 pix = lxpanel_load_icon(name, PANEL_ICON_SIZE, PANEL_ICON_SIZE, TRUE);
772 gtk_tree_store_set(tree, &it, COL_ICON, pix, -1);
773 g_free(name);
774 }
775 if(pix)
776 g_object_unref(pix);
777 }while(gtk_tree_model_iter_next(GTK_TREE_MODEL(tree), &it));
778 }
779 }
780
781 static void on_app_tree_row_expanded(GtkTreeView* view, GtkTreeIter* it, GtkTreePath* tp, gpointer user_data)
782 {
783 launchbar_configure_update_icons((GtkTreeStore*)user_data, it);
784 }
785
786 static void launchbar_configure_add_menu_recursive(GtkTreeStore * tree, GtkTreeIter* parent_it, MenuCacheDir * menu_dir)
787 {
788 /* Iterate over all menu items in this directory. */
789 GSList * l;
790 for (l = menu_cache_dir_get_children(menu_dir); l != NULL; l = l->next)
791 {
792 /* Get the next menu item. */
793 MenuCacheItem * item = MENU_CACHE_ITEM(l->data);
794 switch (menu_cache_item_get_type(item))
795 {
796 case MENU_CACHE_TYPE_NONE:
797 case MENU_CACHE_TYPE_SEP:
798 break;
799
800 case MENU_CACHE_TYPE_APP:
801 {
802 /* If an application, build a LaunchButton data structure so we can identify
803 * the button in the handler. In this application, the desktop_id is the
804 * fully qualified desktop file path. The image and tooltip are what is displayed in the view. */
805 LaunchButton * btn = g_new0(LaunchButton, 1);
806 btn->desktop_id = menu_cache_item_get_file_path(item);
807 btn->image = g_strdup(menu_cache_item_get_icon(item));
808 btn->tooltip = g_strdup(menu_cache_item_get_name(item));
809
810 GKeyFile * desktop = g_key_file_new();
811 gboolean loaded = load_app_key_file(btn->desktop_id, desktop);
812 btn->action = loaded ? g_key_file_get_string(desktop, DESKTOP_ENTRY, "Exec", NULL) : NULL;
813
814 /* Add the row to the view. */
815 GtkTreeIter it;
816 gtk_tree_store_append(tree, &it, parent_it);
817 gtk_tree_store_set(tree, &it,
818 COL_ICON_NAME, menu_cache_item_get_icon(item),
819 COL_TITLE, menu_cache_item_get_name(item),
820 COL_BTN, btn,
821 -1);
822 }
823 break;
824
825 case MENU_CACHE_TYPE_DIR:
826 {
827 GtkTreeIter it;
828 gtk_tree_store_append(tree, &it, parent_it);
829 gtk_tree_store_set(tree, &it,
830 COL_ICON_NAME, menu_cache_item_get_icon(item),
831 COL_TITLE, menu_cache_item_get_name(item),
832 -1);
833 /* If a directory, recursively add its menu items. */
834 launchbar_configure_add_menu_recursive(tree, &it, MENU_CACHE_DIR(item));
835 }
836 break;
837 }
838 }
839 if(!parent_it)
840 launchbar_configure_update_icons(tree, parent_it);
841 }
842
843 static void destroy_menu_cache(gpointer* param, GObject* tree)
844 {
845 MenuCache* mc = (MenuCache*)param[0];
846 gpointer id = param[1];
847 menu_cache_remove_reload_notify(mc, id);
848 menu_cache_unref(mc);
849 g_slice_free1(sizeof(gpointer) * 2, param);
850 }
851
852 static void on_menu_cache_reload(MenuCache* menu_cache, GtkTreeStore* tree)
853 {
854 MenuCacheDir * dir = menu_cache_get_root_dir(menu_cache);
855 gtk_tree_store_clear(tree);
856 if(dir)
857 launchbar_configure_add_menu_recursive(tree, NULL, dir);
858 }
859
860 /* Initialize the list of existing launchbar buttons when the configuration dialog is shown. */
861 static void launchbar_configure_initialize_list(Plugin * p, GtkWidget * dlg, GtkTreeView * view, gboolean from_menu)
862 {
863 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
864
865 /* Set the selection mode. */
866 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_BROWSE);
867
868 /* Define a column. */
869 GtkTreeViewColumn* col = gtk_tree_view_get_column(view, 0);
870
871 /* Establish the pixbuf column cell renderer. */
872 GtkCellRenderer * render = gtk_cell_renderer_pixbuf_new();
873 gtk_tree_view_column_pack_start(col, render, FALSE);
874 gtk_tree_view_column_set_attributes(col, render, "pixbuf", COL_ICON, NULL);
875
876 /* Establish the text column cell renderer. */
877 render = gtk_cell_renderer_text_new();
878 gtk_tree_view_column_pack_start(col, render, TRUE);
879 gtk_tree_view_column_add_attribute(col, render, "text", COL_TITLE);
880
881 if (from_menu)
882 {
883 GtkTreeStore* tree = GTK_TREE_STORE(gtk_tree_view_get_model(view));
884 /* Initialize from all menu items. */
885 guint32 flags;
886 MenuCache *menu_cache = panel_menu_cache_new(&flags);
887
888 g_signal_connect(view, "row-expanded", G_CALLBACK(on_app_tree_row_expanded), tree);
889
890 if (menu_cache != NULL)
891 {
892 MenuCacheDir * dir = menu_cache_get_root_dir(menu_cache);
893 gpointer id = menu_cache_add_reload_notify(menu_cache, (GFunc)on_menu_cache_reload, tree);
894 gpointer *param = g_slice_alloc(sizeof(gpointer) * 2);
895 if(dir)
896 launchbar_configure_add_menu_recursive(tree, NULL, dir);
897 param[0] = menu_cache;
898 param[1] = id;
899 g_object_weak_ref(G_OBJECT(tree), (GWeakNotify)destroy_menu_cache, param);
900 }
901 g_object_set_data(G_OBJECT(dlg), "menu_view", view);
902 }
903 else
904 {
905 /* Establish the column data types. */
906 GtkListStore* list = GTK_LIST_STORE(gtk_tree_view_get_model(view));
907
908 /* Initialize from defined launchbar buttons. */
909 GSList* l;
910 for (l = lb->buttons; l != NULL; l = l->next)
911 {
912 LaunchButton * btn = (LaunchButton *) l->data;
913 GtkTreeIter it;
914 gtk_list_store_append(list, &it);
915 gtk_list_store_set(list, &it,
916 COL_ICON, lxpanel_load_icon(btn->image, PANEL_ICON_SIZE, PANEL_ICON_SIZE, TRUE),
917 COL_TITLE, ((btn->tooltip != NULL) ? btn->tooltip : btn->action),
918 COL_BTN, btn,
919 -1);
920 }
921 g_object_set_data(G_OBJECT(dlg), "defined_view", view);
922 }
923 }
924
925 /* Callback when the configuration dialog is to be shown. */
926 static void launchbar_configure(Plugin * p, GtkWindow * parent)
927 {
928 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
929
930 if (lb->config_dlg == NULL)
931 {
932 GtkWidget *dlg, *btn, *defined_view, *menu_view;
933 GtkBuilder *builder = gtk_builder_new();
934
935 gtk_builder_add_from_file(builder, PACKAGE_UI_DIR "/launchbar.ui", NULL);
936 dlg = (GtkWidget*)gtk_builder_get_object(builder, "dlg");
937 panel_apply_icon(GTK_WINDOW(dlg));
938
939 defined_view = (GtkWidget*)gtk_builder_get_object(builder, "defined_view");
940 menu_view = (GtkWidget*)gtk_builder_get_object(builder, "menu_view");
941 lb->p_label_def_app_exec = (GtkWidget*)gtk_builder_get_object(builder, "label_def_app_exec");
942 lb->p_label_menu_app_exec = (GtkWidget*)gtk_builder_get_object(builder, "label_menu_app_exec");
943
944 /* Connect signals. */
945 g_signal_connect(dlg, "response", G_CALLBACK(launchbar_configure_response), p);
946
947 lb->p_button_add = (GtkWidget*)gtk_builder_get_object(builder, "add");
948 g_signal_connect(lb->p_button_add, "clicked", G_CALLBACK(launchbar_configure_add_button), p);
949
950 lb->p_button_remove = (GtkWidget*)gtk_builder_get_object(builder, "remove");
951 g_signal_connect(lb->p_button_remove, "clicked", G_CALLBACK(launchbar_configure_remove_button), p);
952
953 btn = (GtkWidget*)gtk_builder_get_object(builder, "up");
954 g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_up_button), p);
955
956 btn = (GtkWidget*)gtk_builder_get_object(builder, "down");
957 g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_down_button), p);
958
959 g_signal_connect(defined_view, "button-press-event", G_CALLBACK(on_defined_view_button_press_event), lb);
960 g_signal_connect(defined_view, "cursor-changed", G_CALLBACK(on_defined_view_cursor_changed), lb);
961 g_signal_connect(menu_view, "button-press-event", G_CALLBACK(on_menu_view_button_press_event), lb);
962 g_signal_connect(menu_view, "cursor-changed", G_CALLBACK(on_menu_view_cursor_changed), lb);
963
964 gtk_window_present(GTK_WINDOW(dlg));
965 lb->config_dlg = dlg;
966
967 /* Establish a callback when the dialog completes. */
968 g_object_weak_ref(G_OBJECT(dlg), (GWeakNotify) panel_config_save, p->panel);
969
970 /* Initialize the tree view contents. */
971 launchbar_configure_initialize_list(p, dlg, GTK_TREE_VIEW(defined_view), FALSE);
972 launchbar_configure_initialize_list(p, dlg, GTK_TREE_VIEW(menu_view), TRUE);
973
974 gtk_widget_set_visible(lb->p_label_menu_app_exec, FALSE);
975 gtk_widget_set_visible(lb->p_label_def_app_exec, FALSE);
976
977 g_object_unref(builder);
978 return;
979 }
980 }
981
982 /* Callback when the configuration is to be saved. */
983 static void launchbar_save_configuration(Plugin * p, FILE * fp)
984 {
985 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
986 GSList * l;
987 for (l = lb->buttons; l != NULL; l = l->next)
988 {
989 LaunchButton * btn = (LaunchButton *) l->data;
990 lxpanel_put_line(fp, "Button {");
991 if (btn->desktop_id != NULL)
992 lxpanel_put_str(fp, "id", btn->desktop_id);
993 if (btn->customize_image)
994 lxpanel_put_str(fp, "image", btn->image);
995 if(btn->customize_tooltip)
996 lxpanel_put_str(fp, "tooltip", btn->tooltip);
997 if (btn->customize_action)
998 lxpanel_put_str(fp, "action", btn->action);
999 if (btn->use_terminal)
1000 lxpanel_put_bool(fp, "terminal", TRUE);
1001 lxpanel_put_line(fp, "}");
1002 }
1003 }
1004
1005 /* Callback when panel configuration changes. */
1006 static void launchbar_panel_configuration_changed(Plugin * p)
1007 {
1008 /* Set orientation into the icon grid. */
1009 LaunchbarPlugin * lb = (LaunchbarPlugin *) p->priv;
1010 GtkOrientation bo = (p->panel->orientation == ORIENT_HORIZ) ? GTK_ORIENTATION_HORIZONTAL : GTK_ORIENTATION_VERTICAL;
1011 icon_grid_set_geometry(lb->icon_grid, bo, p->panel->icon_size, p->panel->icon_size, 3, 0, p->panel->height);
1012
1013 /* Reset all the images to resize them. */
1014 GSList * l;
1015 for (l = lb->buttons; l != NULL; l = l->next)
1016 {
1017 LaunchButton * btn = (LaunchButton *) l->data;
1018 fb_button_set_from_file(btn->widget, btn->image, p->panel->icon_size, p->panel->icon_size, TRUE);
1019 }
1020
1021 /* Reset the bootstrap button. */
1022 if (lb->bootstrap_button != NULL)
1023 gtk_image_set_from_pixbuf(GTK_IMAGE(lb->bootstrap_button->image_widget),
1024 lxpanel_load_icon(GTK_STOCK_ADD, p->panel->icon_size, p->panel->icon_size, FALSE));
1025 }
1026
1027 /* Plugin descriptor. */
1028 PluginClass launchbar_plugin_class = {
1029
1030 PLUGINCLASS_VERSIONING,
1031
1032 type : "launchbar",
1033 name : N_("Application Launch Bar"),
1034 version: "2.0",
1035 description : N_("Bar with buttons to launch application"),
1036
1037 constructor : launchbar_constructor,
1038 destructor : launchbar_destructor,
1039 config : launchbar_configure,
1040 save : launchbar_save_configuration,
1041 panel_configuration_changed : launchbar_panel_configuration_changed
1042 };