Fix crash on removing placeholder from launchbar.
[lxde/lxpanel.git] / src / plugins / launchbar.c
1 /**
2 * Copyright (c) 2006-2014 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 <libfm/fm-gtk.h>
38
39 #include "misc.h"
40 #include "plugin.h"
41 #include "icon-grid.h"
42
43 #define PANEL_ICON_SIZE 24 /* see the private.h */
44
45 /* Column definitions for configuration dialogs. */
46 enum {
47 COL_ICON,
48 COL_TITLE,
49 COL_ICON_NAME,
50 COL_BTN,
51 N_COLS
52 };
53
54 static const char DESKTOP_ENTRY[] = "Desktop Entry";
55
56 typedef struct LaunchbarPlugin LaunchbarPlugin;
57
58 /* Representative of one launch button.
59 * Note that the launch parameters come from the specified desktop file, or from the configuration file.
60 * This structure is also used during the "add to launchbar" dialog to hold menu items. */
61 typedef struct {
62 LaunchbarPlugin * p; /* Back pointer to plugin */
63 GtkWidget * widget; /* Pointer to button */
64 GtkWidget * image_widget; /* Pointer to image */
65 FmFileInfo * fi; /* Launcher application descriptor */
66 config_setting_t * settings; /* Pointer to settings */
67 FmDndDest * dd; /* Drag and drop support */
68 } LaunchButton;
69
70 /* Private context for launchbar plugin. */
71 struct LaunchbarPlugin {
72 GtkWidget * plugin; /* Back pointer to plugin */
73 config_setting_t * settings;
74 Panel * panel; /* Back pointer to panel */
75 IconGrid * icon_grid; /* Icon grid managing the container */
76 GSList * buttons; /* Launchbar buttons */
77 GtkWidget * config_dlg; /* Configuration dialog */
78 LaunchButton * bootstrap_button; /* Bootstrapping button for empty launchbar */
79 FmIcon * add_icon; /* Icon for bootstrap_button */
80 GtkWidget *p_button_add, *p_button_remove, *p_label_menu_app_exec, *p_label_def_app_exec;
81 };
82
83 static void launchbar_configure(Panel *panel, GtkWidget *p, GtkWindow *parent);
84 static void launchbar_destructor(gpointer user_data);
85
86 /* Deallocate a LaunchButton. */
87 static void launchbutton_free(LaunchButton * btn)
88 {
89 if (btn->fi)
90 fm_file_info_unref(btn->fi);
91 if (btn->dd)
92 g_object_unref(btn->dd);
93 g_free(btn);
94 }
95
96 static gboolean on_defined_view_button_press_event(GtkWidget *p_widget, GdkEventButton *p_event, gpointer p_data)
97 {
98 if(p_event->button == 1)
99 {
100 if(p_event->type == GDK_2BUTTON_PRESS)
101 {
102 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
103 gtk_button_clicked(GTK_BUTTON(lb->p_button_remove));
104 }
105 }
106 return FALSE;
107 }
108
109 static void on_defined_view_cursor_changed(GtkTreeView *p_treeview, gpointer p_data)
110 {
111 gboolean label_set = FALSE;
112 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
113 GtkTreeIter tree_iter_sel;
114 GtkTreeModel* p_treemodel = gtk_tree_view_get_model(p_treeview);
115 GtkTreeSelection *p_treeselection = gtk_tree_view_get_selection(p_treeview);
116 if(gtk_tree_selection_get_selected(p_treeselection,
117 (GtkTreeModel **)(&p_treemodel),
118 &tree_iter_sel))
119 {
120 LaunchButton * p_btn;
121 gtk_tree_model_get(p_treemodel, &tree_iter_sel, COL_BTN, &p_btn, -1);
122 if( (p_btn != NULL) && (p_btn->fi != NULL) )
123 {
124 GString *p_gstring = g_string_new("");
125 g_string_printf(p_gstring, "<i>%s</i>", fm_file_info_get_disp_name(p_btn->fi));
126 gtk_label_set_markup(GTK_LABEL(lb->p_label_def_app_exec), p_gstring->str);
127 g_string_free(p_gstring, TRUE/*free also gstring->str*/);
128 label_set = TRUE;
129 }
130 }
131 gtk_widget_set_visible(lb->p_label_def_app_exec, label_set);
132 gtk_widget_set_sensitive(lb->p_button_remove, label_set);
133 }
134
135 static void on_menu_view_cursor_changed(GtkTreeView *p_treeview, gpointer p_data)
136 {
137 gboolean label_set = FALSE;
138 LaunchbarPlugin *lb = (LaunchbarPlugin *)p_data;
139 GAppInfo *app = fm_app_menu_view_dup_selected_app(p_treeview);
140
141 if (app)
142 {
143 GString *p_gstring = g_string_new("");
144 if (g_app_info_get_description(app))
145 g_string_printf(p_gstring, "<i>%s</i>", g_app_info_get_description(app));
146 else
147 g_string_printf(p_gstring, "<i>%s</i>", g_app_info_get_name(app));
148 gtk_label_set_markup(GTK_LABEL(lb->p_label_menu_app_exec), p_gstring->str);
149 g_string_free(p_gstring, TRUE/*free also gstring->str*/);
150 label_set = TRUE;
151 }
152 gtk_widget_set_visible(lb->p_label_menu_app_exec, label_set);
153 gtk_widget_set_sensitive(lb->p_button_add, label_set);
154 }
155
156 /* Handler for "button-press-event" event from launchbar button. */
157 static gboolean launchbutton_press_event(GtkWidget * widget, GdkEventButton * event, LaunchButton * b)
158 {
159 /* Standard right-click handling. */
160 if (lxpanel_plugin_button_press_event(b->p->plugin, event, b->p->panel))
161 return TRUE;
162
163 if (event->button == 1) /* left button */
164 {
165 if (b->fi == NULL) /* The bootstrap button */
166 launchbar_configure(b->p->panel, b->p->plugin, NULL);
167 else
168 lxpanel_launch_path(b->p->panel, fm_file_info_get_path(b->fi));
169 }
170 return TRUE;
171 }
172
173 /* Handler for "drag-motion" event from launchbar button. */
174 static gboolean launchbutton_drag_motion_event(
175 GtkWidget * widget,
176 GdkDragContext * context,
177 gint x,
178 gint y,
179 guint time,
180 LaunchButton * b)
181 {
182 GdkAtom target;
183 GdkDragAction action = 0;
184
185 fm_dnd_dest_set_dest_file(b->dd, b->fi);
186 target = fm_dnd_dest_find_target(b->dd, context);
187 if (target != GDK_NONE && fm_dnd_dest_is_target_supported(b->dd, target))
188 action = fm_dnd_dest_get_default_action(b->dd, context, target);
189 gdk_drag_status(context, action, time);
190 /* g_debug("launchbutton_drag_motion_event: act=%u",action); */
191 return (action != 0);
192 }
193
194 /* Build the graphic elements for the bootstrap launchbar button. */
195 static void launchbutton_build_bootstrap(LaunchbarPlugin * lb)
196 {
197 if (lb->bootstrap_button == NULL)
198 {
199 GdkPixbuf * icon;
200 /* Build a button that has the stock "Add" icon.
201 * The "desktop-id" being NULL is the marker that this is the bootstrap button. */
202 lb->bootstrap_button = g_new0(LaunchButton, 1);
203 lb->bootstrap_button->p = lb;
204
205 /* Create an event box. */
206 GtkWidget * event_box = gtk_event_box_new();
207 gtk_container_set_border_width(GTK_CONTAINER(event_box), 0);
208 #if GLIB_CHECK_VERSION(2,18,0)
209 gtk_widget_set_can_focus (event_box, FALSE);
210 #else
211 GTK_WIDGET_UNSET_FLAGS(event_box, GTK_CAN_FOCUS);
212 #endif
213 lb->bootstrap_button->widget = event_box;
214 g_signal_connect(event_box, "button-press-event", G_CALLBACK(launchbutton_press_event), lb->bootstrap_button);
215
216 /* Create an image containing the stock "Add" icon as a child of the event box. */
217 lb->add_icon = fm_icon_from_name(GTK_STOCK_ADD);
218 icon = fm_pixbuf_from_icon(lb->add_icon, panel_get_icon_size(lb->panel));
219 lb->bootstrap_button->image_widget = gtk_image_new_from_pixbuf(icon);
220 g_object_unref(icon);
221 gtk_misc_set_padding(GTK_MISC(lb->bootstrap_button->image_widget), 0, 0);
222 gtk_misc_set_alignment(GTK_MISC(lb->bootstrap_button->image_widget), 0, 0);
223 gtk_container_add(GTK_CONTAINER(event_box), lb->bootstrap_button->image_widget);
224
225 /* Add the bootstrap button to the icon grid. By policy it is empty at this point. */
226 icon_grid_add(lb->icon_grid, event_box, TRUE);
227 }
228 else
229 icon_grid_set_visible(lb->icon_grid, lb->bootstrap_button->widget, TRUE);
230 }
231
232 /* Build the graphic elements for a launchbar button. The desktop_id field is already established. */
233 static LaunchButton *launchbutton_build_gui(LaunchbarPlugin * lb, FmPath * id)
234 {
235 /* Try to get the file data */
236 FmFileInfoJob *job = fm_file_info_job_new(NULL, FM_FILE_INFO_JOB_NONE);
237 FmFileInfo *fi;
238 LaunchButton *btn;
239 GtkWidget *button;
240
241 fm_file_info_job_add(job, id);
242 if (!fm_job_run_sync(FM_JOB(job)))
243 {
244 g_warning("launchbar: problem running file info job\n");
245 g_object_unref(job);
246 return NULL;
247 }
248 fi = fm_file_info_list_pop_head(job->file_infos);
249 g_object_unref(job);
250 if (fi == NULL)
251 {
252 g_warning("launchbar: desktop entry does not exist\n");
253 return NULL;
254 }
255
256 /* Allocate the LaunchButton structure. */
257 btn = g_new0(LaunchButton, 1);
258 btn->p = lb;
259 btn->fi = fi;
260
261 /* Create a button with the specified icon. */
262 button = lxpanel_button_new_for_fm_icon(lb->panel, fm_file_info_get_icon(fi),
263 NULL, NULL);
264 btn->widget = button;
265 #if GLIB_CHECK_VERSION(2,18,0)
266 gtk_widget_set_can_focus(button, FALSE);
267 #else
268 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS);
269 #endif
270
271 gtk_widget_set_tooltip_text(button, fm_file_info_get_disp_name(fi));
272
273 /* Add the button to the icon grid. */
274 icon_grid_add(lb->icon_grid, button, TRUE);
275
276 /* Drag and drop support. */
277 btn->dd = fm_dnd_dest_new_with_handlers(button);
278
279 /* Connect signals. */
280 g_signal_connect(button, "button-press-event", G_CALLBACK(launchbutton_press_event), (gpointer) btn);
281 g_signal_connect(button, "drag-motion", G_CALLBACK(launchbutton_drag_motion_event), btn);
282
283 /* If the list goes from null to non-null, remove the bootstrap button. */
284 if ((lb->buttons == NULL) && (lb->bootstrap_button != NULL))
285 icon_grid_set_visible(lb->icon_grid, lb->bootstrap_button->widget, FALSE);
286
287 /* Append at end of list to preserve configured order. */
288 lb->buttons = g_slist_append(lb->buttons, btn);
289
290 /* Show the widget and return. */
291 gtk_widget_show(button);
292 plugin_widget_set_background(button, lb->panel);
293 return btn;
294 }
295
296 /* Read the configuration file entry for a launchbar button and create it. */
297 static gboolean launchbutton_constructor(LaunchbarPlugin * lb, config_setting_t * s)
298 {
299 LaunchButton *btn;
300 const char *str;
301 FmPath *path;
302
303 /* Read parameters from the configuration file and validate. */
304 if (!config_setting_lookup_string(s, "id", &str) || str[0] == '\0')
305 return FALSE;
306
307 /* Build the structures and return. */
308 path = fm_path_new_for_str(str);
309 btn = launchbutton_build_gui(lb, path);
310 fm_path_unref(path);
311 if (btn)
312 btn->settings = s;
313 return (btn != NULL);
314 }
315
316 /* prototype of this is app_info_create_from_commandline() in libfm */
317 static gboolean _launchbutton_create_id(LaunchbarPlugin * lb, config_setting_t * s)
318 {
319 const char *icon = NULL, *name, *exec, *path = NULL;
320 char *dirname, *filename;
321 int fd, terminal = 0;
322 gboolean ret = FALSE;
323
324 if (!config_setting_lookup_string(s, "action", &exec) || exec[0] == '\0')
325 return FALSE;
326 if (!config_setting_lookup_string(s, "tooltip", &name) || name[0] == '\0')
327 name = "Launcher"; /* placeholder, XDG requires a non-empty name */
328 config_setting_lookup_string(s, "image", &icon);
329 config_setting_lookup_string(s, "path", &path);
330 config_setting_lookup_int(s, "terminal", &terminal);
331
332 dirname = g_build_filename(g_get_user_data_dir(), "applications", NULL);
333 if (g_mkdir_with_parents(dirname, 0700) == 0)
334 {
335 filename = g_strdup_printf("%s/lxpanel-launcher-XXXXXX.desktop", dirname);
336 fd = g_mkstemp (filename);
337 if (fd != -1)
338 {
339 GString* content = g_string_sized_new(256);
340
341 g_string_printf(content,
342 "[" G_KEY_FILE_DESKTOP_GROUP "]\n"
343 G_KEY_FILE_DESKTOP_KEY_TYPE "=" G_KEY_FILE_DESKTOP_TYPE_APPLICATION "\n"
344 G_KEY_FILE_DESKTOP_KEY_NAME "=%s\n"
345 G_KEY_FILE_DESKTOP_KEY_EXEC "=%s\n"
346 G_KEY_FILE_DESKTOP_KEY_CATEGORIES "=X-LXPanel;\n",
347 name, exec);
348 if (icon)
349 g_string_append_printf(content, "Icon=%s\n", icon);
350 if (terminal)
351 g_string_append(content, G_KEY_FILE_DESKTOP_KEY_TERMINAL "=true\n");
352 if (path && path[0] == '/')
353 g_string_append_printf(content, "Path=%s\n", path);
354 close(fd);
355 ret = g_file_set_contents(filename, content->str, content->len, NULL);
356 if (ret) {
357 config_group_set_string(s, "id", filename);
358 /* FIXME: is it reasonable to remove obsolete keys too? */
359 panel_config_save(lb->panel);
360 } else
361 g_unlink(filename);
362 g_string_free(content, TRUE);
363 }
364 g_free(filename);
365 }
366 g_free(dirname);
367 if (ret) /* we created it, let use it */
368 return launchbutton_constructor(lb, s);
369 return FALSE;
370 }
371
372 /* Plugin constructor. */
373 static GtkWidget *launchbar_constructor(Panel *panel, config_setting_t *settings)
374 {
375 static gchar * launchbar_rc = "style 'launchbar-style'\n"
376 "{\n"
377 "GtkWidget::focus-line-width = 0\n"
378 "GtkWidget::focus-padding = 0\n"
379 "GtkButton::default-border = { 0, 0, 0, 0 }\n"
380 "GtkButton::default-outside-border = { 0, 0, 0, 0 }\n"
381 "}\n"
382 "widget '*launchbar*' style 'launchbar-style'";
383 GtkWidget *p;
384 LaunchbarPlugin * lb;
385 config_setting_t *s;
386
387 gtk_rc_parse_string(launchbar_rc);
388
389 /* Allocate plugin context and set into Plugin private data pointer. */
390 lb = g_new0(LaunchbarPlugin, 1);
391
392 /* Save construction pointers */
393 lb->panel = panel;
394 lb->settings = settings;
395
396 /* Allocate top level widget and set into Plugin widget pointer. */
397 lb->plugin = p = gtk_event_box_new();
398 lxpanel_plugin_set_data(p, lb, launchbar_destructor);
399 #if GLIB_CHECK_VERSION(2,18,0)
400 gtk_widget_set_has_window(p, FALSE);
401 #else
402 GTK_WIDGET_SET_FLAGS(p, GTK_NO_WINDOW);
403 #endif
404 gtk_widget_set_name(p, "launchbar");
405
406 /* Allocate an icon grid manager to manage the container. */
407 lb->icon_grid = icon_grid_new(panel, p, panel_get_orientation(panel),
408 panel_get_icon_size(panel),
409 panel_get_icon_size(panel), 3, 0, panel_get_height(panel));
410
411 /* Read parameters from the configuration file. */
412 settings = config_setting_get_member(settings, "");
413 if (settings && config_setting_is_list(settings))
414 {
415 guint i;
416
417 for (i = 0; (s = config_setting_get_elem(settings, i)) != NULL; )
418 {
419 if (strcmp(config_setting_get_name(s), "Button") != 0)
420 {
421 g_warning("launchbar: illegal token %s\n", config_setting_get_name(s));
422 config_setting_destroy(s);
423 }
424 else if (!launchbutton_constructor(lb, s) &&
425 /* try to create desktop id from old-style manual setup */
426 !_launchbutton_create_id(lb, s))
427 {
428 g_warning( "launchbar: can't init button\n");
429 /* FIXME: show failed id to the user instead */
430 config_setting_destroy(s);
431 }
432 else /* success, accept the setting */
433 i++;
434 }
435 }
436
437 if (lb->buttons == NULL)
438 launchbutton_build_bootstrap(lb);
439 return p;
440 }
441
442 /* Plugin destructor. */
443 static void launchbar_destructor(gpointer user_data)
444 {
445 LaunchbarPlugin * lb = (LaunchbarPlugin *)user_data;
446
447 /* Free the launchbar. */
448 g_slist_foreach(lb->buttons, (GFunc) launchbutton_free, NULL);
449 icon_grid_free(lb->icon_grid);
450
451 /* Free the bootstrap button if it exists. */
452 if (lb->bootstrap_button != NULL)
453 launchbutton_free(lb->bootstrap_button);
454
455 /* Ensure that the configuration dialog is dismissed. */
456 if (lb->config_dlg != NULL)
457 gtk_widget_destroy(lb->config_dlg);
458
459 if (lb->add_icon != NULL)
460 g_object_unref(lb->add_icon);
461
462 /* Deallocate all memory. */
463 g_free(lb);
464 }
465
466 /* Handler for "clicked" action on launchbar configuration dialog "Add" button. */
467 static void launchbar_configure_add_button(GtkButton * widget, LaunchbarPlugin * lb)
468 {
469 GtkTreeView * menu_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "menu_view"));
470 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
471 FmPath * sel_path = fm_app_menu_view_dup_selected_app_desktop_path(menu_view);
472 LaunchButton * btn;
473
474 if (sel_path != NULL && (btn = launchbutton_build_gui(lb, sel_path)) != NULL)
475 {
476 GtkListStore * list = GTK_LIST_STORE(gtk_tree_view_get_model(defined_view));
477 GtkTreeIter it;
478 GdkPixbuf* pix;
479 char *path;
480 gtk_list_store_append(list, &it);
481 pix = fm_pixbuf_from_icon(fm_file_info_get_icon(btn->fi), PANEL_ICON_SIZE);
482 gtk_list_store_set(list, &it,
483 COL_ICON, pix,
484 COL_TITLE, fm_file_info_get_disp_name(btn->fi),
485 COL_BTN, btn,
486 -1);
487 g_object_unref(pix);
488 path = fm_path_to_str(sel_path);
489 /* g_debug("*** path '%s'",path); */
490 btn->settings = config_group_add_subgroup(lb->settings, "Button");
491 config_group_set_string(btn->settings, "id", path);
492 g_free(path);
493 fm_path_unref(sel_path);
494 }
495 }
496
497 /* Handler for "clicked" action on launchbar configuration dialog "Remove" button. */
498 static void launchbar_configure_remove_button(GtkButton * widget, LaunchbarPlugin * lb)
499 {
500 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
501 GtkTreeModel * list;
502 GtkTreeIter it;
503 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
504 {
505 LaunchButton * btn;
506 gtk_tree_model_get(list, &it, COL_BTN, &btn, -1);
507
508 /* We have found a selected button.
509 * Remove it from the icon grid, the data structure, and the view. */
510 gtk_list_store_remove(GTK_LIST_STORE(list), &it);
511 icon_grid_remove(lb->icon_grid, btn->widget);
512 lb->buttons = g_slist_remove(lb->buttons, btn);
513 config_setting_destroy(btn->settings);
514 launchbutton_free(btn);
515 gtk_widget_set_visible(lb->p_label_def_app_exec, FALSE);
516
517 /* Put the bootstrap button back if the list becomes empty. */
518 if (lb->buttons == NULL)
519 launchbutton_build_bootstrap(lb);
520 }
521 }
522
523 /* Handler for "clicked" action on launchbar configuration dialog "Move Up" button. */
524 static void launchbar_configure_move_up_button(GtkButton * widget, LaunchbarPlugin * lb)
525 {
526 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
527 GtkTreeModel * list;
528 GtkTreeIter it;
529 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
530 {
531 LaunchButton *btn;
532 gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
533 GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
534 if ((gtk_tree_path_get_indices(path)[0] > 0)
535 && (gtk_tree_path_prev(path)))
536 {
537 GtkTreeIter it2;
538 if (gtk_tree_model_get_iter(list, &it2, path))
539 {
540 /* We have found a selected button that can be moved.
541 * Reorder it in the icon grid, the data structure, and the view. */
542 int i = gtk_tree_path_get_indices(path)[0];
543 lb->buttons = g_slist_remove(lb->buttons, btn);
544 lb->buttons = g_slist_insert(lb->buttons, btn, i);
545 gtk_list_store_move_before(GTK_LIST_STORE(list), &it, &it2);
546 icon_grid_reorder_child(lb->icon_grid, btn->widget, i);
547 config_setting_move_elem(btn->settings,
548 config_setting_get_member(lb->settings, ""),
549 i);
550 }
551 }
552 gtk_tree_path_free(path);
553 }
554 }
555
556 /* Handler for "clicked" action on launchbar configuration dialog "Move Down" button. */
557 static void launchbar_configure_move_down_button(GtkButton * widget, LaunchbarPlugin * lb)
558 {
559 GtkTreeView * defined_view = GTK_TREE_VIEW(g_object_get_data(G_OBJECT(lb->config_dlg), "defined_view"));
560 GtkTreeModel * list;
561 GtkTreeIter it;
562 if (gtk_tree_selection_get_selected(gtk_tree_view_get_selection(defined_view), &list, &it))
563 {
564 LaunchButton *btn;
565 gtk_tree_model_get(GTK_TREE_MODEL(list), &it, COL_BTN, &btn, -1);
566 GtkTreePath * path = gtk_tree_model_get_path(GTK_TREE_MODEL(list), &it);
567 int n = gtk_tree_model_iter_n_children(list, NULL);
568 if (gtk_tree_path_get_indices(path)[0] < (n - 1))
569 {
570 gtk_tree_path_next(path);
571 GtkTreeIter it2;
572 if (gtk_tree_model_get_iter( list, &it2, path))
573 {
574 /* We have found a selected button that can be moved.
575 * Reorder it in the icon grid, the data structure, and the view. */
576 int i = gtk_tree_path_get_indices(path)[0];
577 lb->buttons = g_slist_remove(lb->buttons, btn);
578 lb->buttons = g_slist_insert(lb->buttons, btn, i + 1);
579 gtk_list_store_move_after(GTK_LIST_STORE(list), &it, &it2);
580 icon_grid_reorder_child( lb->icon_grid, btn->widget, i);
581 config_setting_move_elem(btn->settings,
582 config_setting_get_member(lb->settings, ""),
583 i);
584 }
585 }
586 gtk_tree_path_free(path);
587 }
588 }
589
590 /* Handler for "response" signal from launchbar configuration dialog. */
591 static void launchbar_configure_response(GtkDialog * dlg, int response, LaunchbarPlugin * lb)
592 {
593 /* Deallocate the configuration dialog. */
594 lb->config_dlg = NULL;
595 gtk_widget_destroy(GTK_WIDGET(dlg));
596 }
597
598 /* Initialize the list of existing launchbar buttons when the configuration dialog is shown. */
599 static void launchbar_configure_initialize_list(LaunchbarPlugin * lb, GtkWidget * dlg, GtkTreeView * view)
600 {
601 /* Set the selection mode. */
602 gtk_tree_selection_set_mode(gtk_tree_view_get_selection(view), GTK_SELECTION_BROWSE);
603
604 /* Define a column. */
605 GtkTreeViewColumn* col = gtk_tree_view_get_column(view, 0);
606
607 /* Establish the pixbuf column cell renderer. */
608 GtkCellRenderer * render = gtk_cell_renderer_pixbuf_new();
609 gtk_tree_view_column_pack_start(col, render, FALSE);
610 gtk_tree_view_column_set_attributes(col, render, "pixbuf", COL_ICON, NULL);
611
612 /* Establish the text column cell renderer. */
613 render = gtk_cell_renderer_text_new();
614 gtk_tree_view_column_pack_start(col, render, TRUE);
615 gtk_tree_view_column_add_attribute(col, render, "text", COL_TITLE);
616
617 /* Establish the column data types. */
618 GtkListStore* list = GTK_LIST_STORE(gtk_tree_view_get_model(view));
619
620 /* Initialize from defined launchbar buttons. */
621 GSList* l;
622 for (l = lb->buttons; l != NULL; l = l->next)
623 {
624 LaunchButton * btn = (LaunchButton *) l->data;
625 GdkPixbuf * pix;
626 GtkTreeIter it;
627 gtk_list_store_append(list, &it);
628 pix = fm_pixbuf_from_icon(fm_file_info_get_icon(btn->fi), PANEL_ICON_SIZE);
629 gtk_list_store_set(list, &it,
630 COL_ICON, pix,
631 COL_TITLE, fm_file_info_get_disp_name(btn->fi),
632 COL_BTN, btn,
633 -1);
634 g_object_unref(pix);
635 }
636 g_object_set_data(G_OBJECT(dlg), "defined_view", view);
637 }
638
639 /* Callback when the configuration dialog is to be shown. */
640 static void launchbar_configure(Panel *panel, GtkWidget *p, GtkWindow *parent)
641 {
642 LaunchbarPlugin * lb = lxpanel_plugin_get_data(p);
643
644 if (lb->config_dlg == NULL)
645 {
646 GtkWidget *dlg, *btn, *defined_view, *menu_view, *menu_view_window;
647 GtkBuilder *builder = gtk_builder_new();
648
649 gtk_builder_add_from_file(builder, PACKAGE_UI_DIR "/launchbar.ui", NULL);
650 dlg = (GtkWidget*)gtk_builder_get_object(builder, "dlg");
651 panel_apply_icon(GTK_WINDOW(dlg));
652
653 defined_view = (GtkWidget*)gtk_builder_get_object(builder, "defined_view");
654 menu_view_window = (GtkWidget*)gtk_builder_get_object(builder, "menu_view_window");
655 if (menu_view_window == NULL) /* fallback for old glade file */
656 {
657 menu_view_window = (GtkWidget*)gtk_builder_get_object(builder, "scroll2");
658 gtk_widget_destroy(gtk_bin_get_child(GTK_BIN(menu_view_window)));
659 }
660 menu_view = GTK_WIDGET(fm_app_menu_view_new());
661 gtk_container_add(GTK_CONTAINER(menu_view_window), menu_view);
662 gtk_widget_show(menu_view);
663 lb->p_label_def_app_exec = (GtkWidget*)gtk_builder_get_object(builder, "label_def_app_exec");
664 lb->p_label_menu_app_exec = (GtkWidget*)gtk_builder_get_object(builder, "label_menu_app_exec");
665
666 /* Connect signals. */
667 g_signal_connect(dlg, "response", G_CALLBACK(launchbar_configure_response), lb);
668
669 lb->p_button_add = (GtkWidget*)gtk_builder_get_object(builder, "add");
670 g_signal_connect(lb->p_button_add, "clicked", G_CALLBACK(launchbar_configure_add_button), lb);
671
672 lb->p_button_remove = (GtkWidget*)gtk_builder_get_object(builder, "remove");
673 g_signal_connect(lb->p_button_remove, "clicked", G_CALLBACK(launchbar_configure_remove_button), lb);
674
675 btn = (GtkWidget*)gtk_builder_get_object(builder, "up");
676 g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_up_button), lb);
677
678 btn = (GtkWidget*)gtk_builder_get_object(builder, "down");
679 g_signal_connect(btn, "clicked", G_CALLBACK(launchbar_configure_move_down_button), lb);
680
681 g_signal_connect(defined_view, "button-press-event", G_CALLBACK(on_defined_view_button_press_event), lb);
682 g_signal_connect(defined_view, "cursor-changed", G_CALLBACK(on_defined_view_cursor_changed), lb);
683 g_signal_connect(menu_view, "cursor-changed", G_CALLBACK(on_menu_view_cursor_changed), lb);
684
685 gtk_window_present(GTK_WINDOW(dlg));
686 lb->config_dlg = dlg;
687
688 /* Establish a callback when the dialog completes. */
689 g_object_weak_ref(G_OBJECT(dlg), (GWeakNotify) panel_config_save, panel);
690
691 /* Initialize the tree view contents. */
692 launchbar_configure_initialize_list(lb, dlg, GTK_TREE_VIEW(defined_view));
693 g_object_set_data(G_OBJECT(dlg), "menu_view", menu_view);
694
695 gtk_widget_set_visible(lb->p_label_menu_app_exec, FALSE);
696 gtk_widget_set_visible(lb->p_label_def_app_exec, FALSE);
697 gtk_widget_set_sensitive(lb->p_button_add, FALSE);
698 gtk_widget_set_sensitive(lb->p_button_remove, FALSE);
699
700 g_object_unref(builder);
701 return;
702 }
703 }
704
705 /* Callback when panel configuration changes. */
706 static void launchbar_panel_configuration_changed(Panel *panel, GtkWidget *p)
707 {
708 /* Set orientation into the icon grid. */
709 LaunchbarPlugin * lb = lxpanel_plugin_get_data(p);
710 icon_grid_set_geometry(lb->icon_grid, panel_get_orientation(panel),
711 panel_get_icon_size(panel), panel_get_icon_size(panel),
712 3, 0, panel_get_height(panel));
713
714 /* Reset all the images to resize them. */
715 GSList * l;
716 for (l = lb->buttons; l != NULL; l = l->next)
717 {
718 LaunchButton * btn = (LaunchButton *) l->data;
719 lxpanel_button_update_icon(btn->widget, fm_file_info_get_icon(btn->fi),
720 panel_get_icon_size(panel));
721 }
722
723 /* Reset the bootstrap button. */
724 if (lb->bootstrap_button != NULL)
725 {
726 GdkPixbuf * icon = fm_pixbuf_from_icon(lb->add_icon, panel_get_icon_size(panel));
727 gtk_image_set_from_pixbuf(GTK_IMAGE(lb->bootstrap_button->image_widget), icon);
728 g_object_unref(icon);
729 }
730 }
731
732 /* Plugin descriptor. */
733 LXPanelPluginInit lxpanel_static_plugin_launchbar = {
734 .name = N_("Application Launch Bar"),
735 .description = N_("Bar with buttons to launch application"),
736
737 .new_instance = launchbar_constructor,
738 .config = launchbar_configure,
739 .reconfigure = launchbar_panel_configuration_changed
740 };