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