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