launcher.c 94.9 KB
Newer Older
1
/*
2
 * Copyright (C) 2008-2010 Nick Schermer <nick@xfce.org>
3
 *
4 5 6 7
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
Nick Schermer's avatar
Nick Schermer committed
8
 *
9 10 11 12
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
Nick Schermer's avatar
Nick Schermer committed
13
 *
Nick Schermer's avatar
Nick Schermer committed
14 15 16
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Nick Schermer's avatar
Nick Schermer committed
17 18 19 20 21 22
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

23 24 25 26
#ifdef HAVE_STRING_H
#include <string.h>
#endif

27
#include <gio/gio.h>
Nick Schermer's avatar
Nick Schermer committed
28
#include <libxfce4util/libxfce4util.h>
29
#include <libxfce4ui/libxfce4ui.h>
30
#include <garcon/garcon.h>
31
#include <garcon-gtk/garcon-gtk.h>
32 33 34
#include <xfconf/xfconf.h>

#include <libxfce4panel/libxfce4panel.h>
35
#include <common/panel-private.h>
36
#include <common/panel-xfconf.h>
37
#include <common/panel-utils.h>
Nick Schermer's avatar
Nick Schermer committed
38

39
#include "launcher.h"
40
#include "launcher-dialog.h"
Nick Schermer's avatar
Nick Schermer committed
41

42 43 44 45 46
#define ARROW_BUTTON_SIZE              (12)
#define MENU_POPUP_DELAY               (225)
#define NO_ARROW_INSIDE_BUTTON(plugin) ((plugin)->arrow_position != LAUNCHER_ARROW_INTERNAL \
                                        || LIST_HAS_ONE_OR_NO_ENTRIES ((plugin)->items))
#define ARROW_INSIDE_BUTTON(plugin)    (!NO_ARROW_INSIDE_BUTTON (plugin))
47
#define RELATIVE_CONFIG_PATH           PANEL_PLUGIN_RELATIVE_PATH G_DIR_SEPARATOR_S "%s-%d"
48

Nick Schermer's avatar
Nick Schermer committed
49

50

51 52 53 54 55 56 57 58 59 60 61 62 63 64
static void               launcher_plugin_get_property                  (GObject              *object,
                                                                         guint                 prop_id,
                                                                         GValue               *value,
                                                                         GParamSpec           *pspec);
static void               launcher_plugin_set_property                  (GObject              *object,
                                                                         guint                 prop_id,
                                                                         const GValue         *value,
                                                                         GParamSpec           *pspec);
static void               launcher_plugin_construct                     (XfcePanelPlugin      *panel_plugin);
static void               launcher_plugin_free_data                     (XfcePanelPlugin      *panel_plugin);
static void               launcher_plugin_removed                       (XfcePanelPlugin      *panel_plugin);
static gboolean           launcher_plugin_remote_event                  (XfcePanelPlugin      *panel_plugin,
                                                                         const gchar          *name,
                                                                         const GValue         *value);
65
static gboolean           launcher_plugin_save_delayed_timeout          (gpointer              user_data);
66
static void               launcher_plugin_save_delayed                  (LauncherPlugin       *plugin);
67 68
static void               launcher_plugin_mode_changed                  (XfcePanelPlugin      *panel_plugin,
                                                                         XfcePanelPluginMode   mode);
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
static gboolean           launcher_plugin_size_changed                  (XfcePanelPlugin      *panel_plugin,
                                                                         gint                  size);
static void               launcher_plugin_configure_plugin              (XfcePanelPlugin      *panel_plugin);
static void               launcher_plugin_screen_position_changed       (XfcePanelPlugin      *panel_plugin,
                                                                         XfceScreenPosition    position);
static void               launcher_plugin_icon_theme_changed            (GtkIconTheme         *icon_theme,
                                                                         LauncherPlugin       *plugin);
static LauncherArrowType  launcher_plugin_default_arrow_type            (LauncherPlugin       *plugin);
static void               launcher_plugin_pack_widgets                  (LauncherPlugin       *plugin);
static GdkPixbuf         *launcher_plugin_tooltip_pixbuf                (GdkScreen            *screen,
                                                                         const gchar          *icon_name);
static void               launcher_plugin_menu_deactivate               (GtkWidget            *menu,
                                                                         LauncherPlugin       *plugin);
static void               launcher_plugin_menu_item_activate            (GtkMenuItem          *widget,
                                                                         GarconMenuItem       *item);
static void               launcher_plugin_menu_item_drag_data_received  (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         GtkSelectionData     *data,
                                                                         guint                 info,
                                                                         guint                 drag_time,
                                                                         GarconMenuItem       *item);
static void               launcher_plugin_menu_construct                (LauncherPlugin       *plugin);
static void               launcher_plugin_menu_popup_destroyed          (gpointer              user_data);
static gboolean           launcher_plugin_menu_popup                    (gpointer              user_data);
static void               launcher_plugin_menu_destroy                  (LauncherPlugin       *plugin);
static void               launcher_plugin_button_update                 (LauncherPlugin       *plugin);
97 98 99
#if GARCON_CHECK_VERSION(0,7,0)
static void               launcher_plugin_button_update_action_menu     (LauncherPlugin       *plugin);
#endif
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
static void               launcher_plugin_button_state_changed          (GtkWidget            *button_a,
                                                                         GtkStateType         state,
                                                                         GtkWidget            *button_b);
static gboolean           launcher_plugin_button_press_event            (GtkWidget            *button,
                                                                         GdkEventButton       *event,
                                                                         LauncherPlugin       *plugin);
static gboolean           launcher_plugin_button_release_event          (GtkWidget            *button,
                                                                         GdkEventButton       *event,
                                                                         LauncherPlugin       *plugin);
static gboolean           launcher_plugin_button_query_tooltip          (GtkWidget            *widget,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         gboolean              keyboard_mode,
                                                                         GtkTooltip           *tooltip,
                                                                         LauncherPlugin       *plugin);
static void               launcher_plugin_button_drag_data_received     (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         GtkSelectionData     *selection_data,
                                                                         guint                 info,
                                                                         guint                 drag_time,
                                                                         LauncherPlugin       *plugin);
static gboolean           launcher_plugin_button_drag_motion            (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         guint                 drag_time,
                                                                         LauncherPlugin       *plugin);
static gboolean           launcher_plugin_button_drag_drop              (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         guint                 drag_time,
                                                                         LauncherPlugin       *plugin);
static void               launcher_plugin_button_drag_leave             (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         guint                 drag_time,
                                                                         LauncherPlugin       *plugin);
139 140 141
static gboolean           launcher_plugin_button_draw                   (GtkWidget            *widget,
                                                                         cairo_t              *cr,
                                                                         LauncherPlugin       *plugin);
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
static void               launcher_plugin_arrow_visibility              (LauncherPlugin       *plugin);
static gboolean           launcher_plugin_arrow_press_event             (GtkWidget            *button,
                                                                         GdkEventButton       *event,
                                                                         LauncherPlugin       *plugin);
static gboolean           launcher_plugin_arrow_drag_motion             (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         guint                 drag_time,
                                                                         LauncherPlugin       *plugin);
static void               launcher_plugin_arrow_drag_leave              (GtkWidget            *widget,
                                                                         GdkDragContext       *context,
                                                                         guint                 drag_time,
                                                                         LauncherPlugin       *plugin);
static gboolean           launcher_plugin_item_query_tooltip            (GtkWidget            *widget,
                                                                         gint                  x,
                                                                         gint                  y,
                                                                         gboolean              keyboard_mode,
                                                                         GtkTooltip           *tooltip,
                                                                         GarconMenuItem       *item);
static gboolean           launcher_plugin_item_exec_on_screen           (GarconMenuItem       *item,
                                                                         guint32               event_time,
                                                                         GdkScreen            *screen,
                                                                         GSList               *uri_list);
static void               launcher_plugin_item_exec                     (GarconMenuItem       *item,
                                                                         guint32               event_time,
                                                                         GdkScreen            *screen,
                                                                         GSList               *uri_list);
static void               launcher_plugin_item_exec_from_clipboard      (GarconMenuItem       *item,
                                                                         guint32               event_time,
                                                                         GdkScreen            *screen);
static GSList            *launcher_plugin_uri_list_extract              (GtkSelectionData     *data);
static void               launcher_plugin_uri_list_free                 (GSList               *uri_list);
Nick Schermer's avatar
Nick Schermer committed
175

Nick Schermer's avatar
Nick Schermer committed
176 177


178
struct _LauncherPluginClass
Nick Schermer's avatar
Nick Schermer committed
179 180 181 182
{
  XfcePanelPluginClass __parent__;
};

183
struct _LauncherPlugin
Nick Schermer's avatar
Nick Schermer committed
184 185 186
{
  XfcePanelPlugin __parent__;

187 188 189
  GtkWidget         *box;
  GtkWidget         *button;
  GtkWidget         *arrow;
190
  GtkWidget         *child;
191
  GtkWidget         *menu;
192 193 194
#if GARCON_CHECK_VERSION(0,7,0)
  GtkWidget         *action_menu;
#endif
Nick Schermer's avatar
Nick Schermer committed
195

196
  GSList            *items;
Nick Schermer's avatar
Nick Schermer committed
197

198 199
  GdkPixbuf         *pixbuf;
  gchar             *icon_name;
200 201
  GdkPixbuf         *tooltip_cache;

202 203
  gulong             theme_change_id;

204 205 206 207
  guint              menu_timeout_id;

  guint              disable_tooltips : 1;
  guint              move_first : 1;
208
  guint              show_label : 1;
209
  LauncherArrowType  arrow_position;
210 211 212

  GFile             *config_directory;
  GFileMonitor      *config_monitor;
213 214

  guint              save_timeout_id;
Nick Schermer's avatar
Nick Schermer committed
215 216
};

217 218 219 220 221 222
enum
{
  PROP_0,
  PROP_ITEMS,
  PROP_DISABLE_TOOLTIPS,
  PROP_MOVE_FIRST,
223
  PROP_SHOW_LABEL,
224 225 226
  PROP_ARROW_POSITION
};

227 228 229 230 231
enum
{
  ITEMS_CHANGED,
  LAST_SIGNAL
};
Nick Schermer's avatar
Nick Schermer committed
232

233

234
/* define the plugin */
235
XFCE_PANEL_DEFINE_PLUGIN_RESIDENT (LauncherPlugin, launcher_plugin)
Nick Schermer's avatar
Nick Schermer committed
236 237 238



239
/* quark to attach the plugin to menu items */
240 241
static GQuark      launcher_plugin_quark = 0;
static guint       launcher_signals[LAST_SIGNAL];
242 243 244



245 246 247
/* target types for dropping in the launcher plugin */
static const GtkTargetEntry drop_targets[] =
{
248 249 250 251
  { "text/uri-list", 0, 0, },
  { "STRING", 0, 0 },
  { "UTF8_STRING", 0, 0 },
  { "text/plain", 0, 0 },
252 253 254 255
};



256
static void
257
launcher_plugin_class_init (LauncherPluginClass *klass)
258
{
259
  GObjectClass         *gobject_class;
260 261
  XfcePanelPluginClass *plugin_class;

262 263 264 265
  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->get_property = launcher_plugin_get_property;
  gobject_class->set_property = launcher_plugin_set_property;

266
  plugin_class = XFCE_PANEL_PLUGIN_CLASS (klass);
267 268
  plugin_class->construct = launcher_plugin_construct;
  plugin_class->free_data = launcher_plugin_free_data;
269
  plugin_class->mode_changed = launcher_plugin_mode_changed;
270 271 272
  plugin_class->size_changed = launcher_plugin_size_changed;
  plugin_class->configure_plugin = launcher_plugin_configure_plugin;
  plugin_class->screen_position_changed = launcher_plugin_screen_position_changed;
273
  plugin_class->removed = launcher_plugin_removed;
274
  plugin_class->remote_event = launcher_plugin_remote_event;
275

276 277 278 279
  g_object_class_install_property (gobject_class,
                                   PROP_ITEMS,
                                   g_param_spec_boxed ("items",
                                                       NULL, NULL,
280
                                                       G_TYPE_PTR_ARRAY,
281
                                                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
282 283 284 285 286 287

  g_object_class_install_property (gobject_class,
                                   PROP_DISABLE_TOOLTIPS,
                                   g_param_spec_boolean ("disable-tooltips",
                                                         NULL, NULL,
                                                         FALSE,
288
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
289 290 291 292 293 294

  g_object_class_install_property (gobject_class,
                                   PROP_MOVE_FIRST,
                                   g_param_spec_boolean ("move-first",
                                                         NULL, NULL,
                                                         FALSE,
295
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
296

297 298 299 300 301
  g_object_class_install_property (gobject_class,
                                   PROP_SHOW_LABEL,
                                   g_param_spec_boolean ("show-label",
                                                         NULL, NULL,
                                                         FALSE,
302
                                                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
303

304 305 306 307 308 309 310
  g_object_class_install_property (gobject_class,
                                   PROP_ARROW_POSITION,
                                   g_param_spec_uint ("arrow-position",
                                                      NULL, NULL,
                                                      LAUNCHER_ARROW_DEFAULT,
                                                      LAUNCHER_ARROW_INTERNAL,
                                                      LAUNCHER_ARROW_DEFAULT,
311
                                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
312

313 314 315 316 317 318 319 320
  launcher_signals[ITEMS_CHANGED] =
    g_signal_new (g_intern_static_string ("items-changed"),
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  0, NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

321 322
  /* initialize the quark */
  launcher_plugin_quark = g_quark_from_static_string ("xfce-launcher-plugin");
323
}
Nick Schermer's avatar
Nick Schermer committed
324 325 326



327
static void
328
launcher_plugin_init (LauncherPlugin *plugin)
329
{
330 331 332 333
  GtkIconTheme    *icon_theme;
  GtkCssProvider  *css_provider;
  GtkStyleContext *context;
  gchar           *css_string;
334 335 336

  plugin->disable_tooltips = FALSE;
  plugin->move_first = FALSE;
337
  plugin->show_label = FALSE;
338 339
  plugin->arrow_position = LAUNCHER_ARROW_DEFAULT;
  plugin->menu = NULL;
340 341 342
#if GARCON_CHECK_VERSION(0,7,0)
  plugin->action_menu = NULL;
#endif
343
  plugin->items = NULL;
344
  plugin->child = NULL;
345
  plugin->tooltip_cache = NULL;
346 347
  plugin->pixbuf = NULL;
  plugin->icon_name = NULL;
348
  plugin->menu_timeout_id = 0;
349
  plugin->save_timeout_id = 0;
350 351 352

  /* monitor the default icon theme for changes */
  icon_theme = gtk_icon_theme_get_default ();
353 354
  plugin->theme_change_id = g_signal_connect (G_OBJECT (icon_theme), "changed",
      G_CALLBACK (launcher_plugin_icon_theme_changed), plugin);
355 356

  /* create the panel widgets */
357
  plugin->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
358 359 360 361 362 363
  gtk_container_add (GTK_CONTAINER (plugin), plugin->box);

  plugin->button = xfce_panel_create_button ();
  gtk_box_pack_start (GTK_BOX (plugin->box), plugin->button, TRUE, TRUE, 0);
  xfce_panel_plugin_add_action_widget (XFCE_PANEL_PLUGIN (plugin), plugin->button);
  gtk_widget_set_has_tooltip (plugin->button, TRUE);
364
  gtk_widget_set_name (plugin->button, "launcher-button");
365
  g_signal_connect (G_OBJECT (plugin->button), "button-press-event",
Nick Schermer's avatar
Nick Schermer committed
366
      G_CALLBACK (launcher_plugin_button_press_event), plugin);
367
  g_signal_connect (G_OBJECT (plugin->button), "button-release-event",
Nick Schermer's avatar
Nick Schermer committed
368
      G_CALLBACK (launcher_plugin_button_release_event), plugin);
369
  g_signal_connect (G_OBJECT (plugin->button), "query-tooltip",
Nick Schermer's avatar
Nick Schermer committed
370
      G_CALLBACK (launcher_plugin_button_query_tooltip), plugin);
371
  g_signal_connect (G_OBJECT (plugin->button), "drag-data-received",
Nick Schermer's avatar
Nick Schermer committed
372
      G_CALLBACK (launcher_plugin_button_drag_data_received), plugin);
373
  g_signal_connect (G_OBJECT (plugin->button), "drag-motion",
Nick Schermer's avatar
Nick Schermer committed
374
      G_CALLBACK (launcher_plugin_button_drag_motion), plugin);
375 376
  g_signal_connect (G_OBJECT (plugin->button), "drag-drop",
      G_CALLBACK (launcher_plugin_button_drag_drop), plugin);
377
  g_signal_connect (G_OBJECT (plugin->button), "drag-leave",
Nick Schermer's avatar
Nick Schermer committed
378
      G_CALLBACK (launcher_plugin_button_drag_leave), plugin);
379 380
  g_signal_connect_after (G_OBJECT (plugin->button), "draw",
      G_CALLBACK (launcher_plugin_button_draw), plugin);
381

382 383 384 385 386 387 388 389 390 391
  /* Make sure there aren't any constraints set on buttons by themes (Adwaita sets those minimum sizes) */
  context = gtk_widget_get_style_context (plugin->button);
  css_provider = gtk_css_provider_new ();
  css_string = g_strdup_printf ("#launcher-arrow { min-height: 0; min-width: 0; }");
  gtk_css_provider_load_from_data (css_provider, css_string, -1, NULL);
  gtk_style_context_add_provider (context,
                                  GTK_STYLE_PROVIDER (css_provider),
                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
  g_free (css_string);

392
  plugin->child = gtk_image_new ();
393
  gtk_container_add (GTK_CONTAINER (plugin->button), plugin->child);
394 395 396 397 398

  plugin->arrow = xfce_arrow_button_new (GTK_ARROW_UP);
  gtk_box_pack_start (GTK_BOX (plugin->box), plugin->arrow, FALSE, FALSE, 0);
  xfce_panel_plugin_add_action_widget (XFCE_PANEL_PLUGIN (plugin), plugin->arrow);
  gtk_button_set_relief (GTK_BUTTON (plugin->arrow), GTK_RELIEF_NONE);
399
  gtk_widget_set_name (plugin->button, "launcher-arrow");
400
  g_signal_connect (G_OBJECT (plugin->arrow), "button-press-event",
Nick Schermer's avatar
Nick Schermer committed
401
      G_CALLBACK (launcher_plugin_arrow_press_event), plugin);
402
  g_signal_connect (G_OBJECT (plugin->arrow), "drag-motion",
Nick Schermer's avatar
Nick Schermer committed
403
      G_CALLBACK (launcher_plugin_arrow_drag_motion), plugin);
404 405
  g_signal_connect (G_OBJECT (plugin->button), "drag-drop",
      G_CALLBACK (launcher_plugin_button_drag_drop), plugin);
406
  g_signal_connect (G_OBJECT (plugin->arrow), "drag-leave",
Nick Schermer's avatar
Nick Schermer committed
407
      G_CALLBACK (launcher_plugin_arrow_drag_leave), plugin);
408

409
  panel_utils_set_atk_info (plugin->arrow, _("Open launcher menu"), NULL);
410

411 412 413 414 415
  /* accept all sorts of drag data, but filter in drag-drop, so we can
   * send other sorts of drops to parent widgets */
  gtk_drag_dest_set (plugin->button, 0, NULL, 0, 0);
  gtk_drag_dest_set (plugin->arrow, 0, NULL, 0, 0);

416 417
  /* sync button states */
  g_signal_connect (G_OBJECT (plugin->button), "state-changed",
Nick Schermer's avatar
Nick Schermer committed
418
      G_CALLBACK (launcher_plugin_button_state_changed), plugin->arrow);
419
  g_signal_connect (G_OBJECT (plugin->arrow), "state-changed",
Nick Schermer's avatar
Nick Schermer committed
420
      G_CALLBACK (launcher_plugin_button_state_changed), plugin->button);
Nick Schermer's avatar
Nick Schermer committed
421 422 423
}


424

425 426 427 428 429 430 431 432 433 434 435
static void
launcher_free_array_element (gpointer data)
{
  GValue *value = (GValue *)data;

  g_value_unset (value);
  g_free (value);
}



436 437 438 439 440 441
static void
launcher_plugin_get_property (GObject    *object,
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
442 443 444 445
  LauncherPlugin *plugin = XFCE_LAUNCHER_PLUGIN (object);
  GPtrArray      *array;
  GValue         *tmp;
  GSList         *li;
446
  GFile          *item_file;
447 448 449

  switch (prop_id)
    {
450
    case PROP_ITEMS:
451
      array = g_ptr_array_new_full (1, (GDestroyNotify) launcher_free_array_element);
452 453 454 455 456 457 458 459 460 461 462 463 464 465
      for (li = plugin->items; li != NULL; li = li->next)
        {
          tmp = g_new0 (GValue, 1);
          g_value_init (tmp, G_TYPE_STRING);
          panel_return_if_fail (GARCON_IS_MENU_ITEM (li->data));
          item_file = garcon_menu_item_get_file (li->data);
          if (g_file_has_prefix (item_file, plugin->config_directory))
            g_value_take_string (tmp, g_file_get_basename (item_file));
          else
            g_value_take_string (tmp, g_file_get_uri (item_file));
          g_object_unref (G_OBJECT (item_file));
          g_ptr_array_add (array, tmp);
        }
      g_value_set_boxed (value, array);
466
      g_ptr_array_unref (array);
467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
      break;

    case PROP_DISABLE_TOOLTIPS:
      g_value_set_boolean (value, plugin->disable_tooltips);
      break;

    case PROP_MOVE_FIRST:
      g_value_set_boolean (value, plugin->move_first);
      break;

    case PROP_SHOW_LABEL:
      g_value_set_boolean (value, plugin->show_label);
      break;

    case PROP_ARROW_POSITION:
      g_value_set_uint (value, plugin->arrow_position);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
488 489 490 491 492
    }
}



493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
static void
launcher_plugin_item_changed (GarconMenuItem *item,
                              LauncherPlugin *plugin)
{
  GSList *li;

  panel_return_if_fail (GARCON_IS_MENU_ITEM (item));
  panel_return_if_fail (XFCE_IS_LAUNCHER_PLUGIN (plugin));

  /* find the item */
  li = g_slist_find (plugin->items, item);
  if (G_LIKELY (li != NULL))
    {
      /* update the button or destroy the menu */
      if (plugin->items == li)
508 509 510 511 512 513
        {
          launcher_plugin_button_update (plugin);
#if GARCON_CHECK_VERSION(0,7,0)
          launcher_plugin_button_update_action_menu (plugin);
#endif
        }
514 515 516 517 518 519 520 521 522 523 524
      else
        launcher_plugin_menu_destroy (plugin);
    }
  else
    {
      panel_assert_not_reached ();
    }
}



525
static gboolean
Nick Schermer's avatar
Nick Schermer committed
526 527 528
launcher_plugin_item_duplicate (GFile   *src_file,
                                GFile   *dst_file,
                                GError **error)
529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
{
  GKeyFile *key_file;
  gchar    *contents = NULL;
  gsize     length;
  gboolean  result = FALSE;
  gchar    *uri;

  panel_return_val_if_fail (G_IS_FILE (src_file), FALSE);
  panel_return_val_if_fail (G_IS_FILE (dst_file), FALSE);
  panel_return_val_if_fail (error == NULL || *error == NULL, FALSE);

  if (!g_file_load_contents (src_file, NULL, &contents, &length, NULL, error))
    return FALSE;

  /* note that we don't load the key file with preserving the translations
   * and comments, this way we save a small desktop file in the user's language */
  key_file = g_key_file_new ();
  if (!g_key_file_load_from_data (key_file, contents, length, 0, error))
    goto err1;

549
  /* store the source uri in the desktop file for restore purposes */
550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569
  uri = g_file_get_uri (src_file);
  g_key_file_set_string (key_file, G_KEY_FILE_DESKTOP_GROUP, "X-XFCE-Source", uri);
  g_free (uri);

  contents = g_key_file_to_data (key_file, &length, error);
  if (contents == NULL)
    goto err1;

  result = g_file_replace_contents (dst_file, contents, length, NULL, FALSE,
                                    G_FILE_CREATE_REPLACE_DESTINATION,
                                    NULL, NULL, error);

err1:
  g_free (contents);
  g_key_file_free (key_file);

  return result;
}


570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
static gboolean
_exo_str_looks_like_an_uri (const gchar *str)
{
  const gchar *s = str;

  if (G_UNLIKELY (str == NULL))
    return FALSE;

  /* <scheme> starts with an alpha character */
  if (g_ascii_isalpha (*s))
    {
      /* <scheme> continues with (alpha | digit | "+" | "-" | ".")* */
      for (++s; g_ascii_isalnum (*s) || *s == '+' || *s == '-' || *s == '.'; ++s);

      /* <scheme> must be followed by ":" */
      return (*s == ':' && *(s+1) == '/');
    }

  return FALSE;
}

591 592 593 594

static GarconMenuItem *
launcher_plugin_item_load (LauncherPlugin *plugin,
                           const gchar    *str,
595 596
                           gboolean       *desktop_id_return,
                           gboolean       *location_changed)
597 598 599
{
  GFile          *src_file, *dst_file;
  gchar          *src_path, *dst_path;
600
  GSList         *li, *lnext;
601 602 603 604 605 606 607
  GarconMenuItem *item = NULL;
  GError         *error = NULL;

  panel_return_val_if_fail (XFCE_IS_LAUNCHER_PLUGIN (plugin), NULL);
  panel_return_val_if_fail (str != NULL, NULL);
  panel_return_val_if_fail (G_IS_FILE (plugin->config_directory), NULL);

608
  if (G_UNLIKELY (g_path_is_absolute (str) || _exo_str_looks_like_an_uri (str)))
609 610 611 612 613 614 615 616 617 618 619 620
    {
      src_file = g_file_new_for_commandline_arg (str);
      if (g_file_has_prefix (src_file, plugin->config_directory))
        {
          /* nothing, we use the file below */
        }
      else if (g_file_query_exists (src_file, NULL))
        {
          /* create a unique file in the config directory */
          dst_path = launcher_plugin_unique_filename (plugin);
          dst_file = g_file_new_for_path (dst_path);

Nick Schermer's avatar
Nick Schermer committed
621 622
          /* create a duplicate in the config directory */
          if (launcher_plugin_item_duplicate (src_file, dst_file, &error))
623 624 625 626
            {
              /* use the new file */
              g_object_unref (G_OBJECT (src_file));
              src_file = dst_file;
627 628 629

              if (G_LIKELY (location_changed != NULL))
                *location_changed = TRUE;
630 631 632 633
            }
          else
            {
              src_path = g_file_get_parse_name (src_file);
Nick Schermer's avatar
Nick Schermer committed
634
              g_warning ("Failed to create duplicate of desktop file \"%s\" "
635 636 637 638 639 640 641
                          "to \"%s\": %s", src_path, dst_path, error->message);
              g_error_free (error);
              g_free (src_path);

              /* continue using the source file, the user won't be able to
               * edit the item, but atleast we have something that works in
               * the panel */
Nick Schermer's avatar
Nick Schermer committed
642
              g_object_unref (G_OBJECT (dst_file));
643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672
            }

          g_free (dst_path);
        }
      else
        {
          /* nothing we can do with this file */
          src_path = g_file_get_parse_name (src_file);
          g_warning ("Failed to load desktop file \"%s\". It will be removed "
                     "from the configuration", src_path);
          g_free (src_path);
          g_object_unref (G_OBJECT (src_file));

          return NULL;
        }
    }
  else
    {
      /* assume the file is a child in the config directory */
      src_file = g_file_get_child (plugin->config_directory, str);

      /* str might also be a global desktop id */
      if (G_LIKELY (desktop_id_return != NULL))
        *desktop_id_return = TRUE;
    }

  panel_assert (G_IS_FILE (src_file));

  /* maybe we have this file in the launcher configuration, then we don't
   * have to load it again from the harddisk */
673
  for (li = plugin->items; item == NULL && li != NULL; li = lnext)
674
    {
675
      lnext = li->next;
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
      dst_file = garcon_menu_item_get_file (GARCON_MENU_ITEM (li->data));
      if (g_file_equal (src_file, dst_file))
        {
          item = GARCON_MENU_ITEM (li->data);
          plugin->items = g_slist_delete_link (plugin->items, li);
        }
      g_object_unref (G_OBJECT (dst_file));
    }

  /* load the file from the disk */
  if (item == NULL)
    item = garcon_menu_item_new (src_file);

  g_object_unref (G_OBJECT (src_file));

  return item;
}



static void
697
launcher_plugin_items_delete_configs (LauncherPlugin *plugin)
698
{
699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
  GSList   *li;
  GFile    *file;
  gboolean  succeed = TRUE;
  GError   *error = NULL;

  panel_return_if_fail (G_IS_FILE (plugin->config_directory));

  /* cleanup desktop files in the config dir */
  for (li = plugin->items; succeed && li != NULL; li = li->next)
    {
      file = garcon_menu_item_get_file (li->data);
      if (g_file_has_prefix (file, plugin->config_directory))
        succeed = g_file_delete (file, NULL, &error);
      g_object_unref (G_OBJECT (file));
    }

  if (!succeed)
716
    {
717 718 719 720 721 722 723 724 725 726 727 728 729 730
      g_message ("launcher-%d: Failed to cleanup the configuration: %s",
                 xfce_panel_plugin_get_unique_id (XFCE_PANEL_PLUGIN (plugin)),
                 error->message);
      g_error_free (error);
    }
}



static void
launcher_plugin_items_free (LauncherPlugin *plugin)
{
  if (G_LIKELY (plugin->items != NULL))
    {
731
      g_slist_foreach (plugin->items, (GFunc) (void (*)(void)) g_object_unref, NULL);
732 733
      g_slist_free (plugin->items);
      plugin->items = NULL;
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
    }
}



static void
launcher_plugin_items_load (LauncherPlugin *plugin,
                            GPtrArray      *array)
{
  guint           i;
  const GValue   *value;
  const gchar    *str;
  GarconMenuItem *item;
  GarconMenuItem *pool_item;
  GSList         *items = NULL;
  GHashTable     *pool = NULL;
  gboolean        desktop_id;
  gchar          *uri;
752 753
  gboolean        items_modified = FALSE;
  gboolean        location_changed;
754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769

  panel_return_if_fail (XFCE_IS_LAUNCHER_PLUGIN (plugin));
  panel_return_if_fail (array != NULL);

  for (i = 0; i < array->len; i++)
    {
      value = g_ptr_array_index (array, i);
      panel_assert (G_VALUE_HOLDS_STRING (value));
      str = g_value_get_string (value);

      /* only accept desktop files */
      if (str == NULL || !g_str_has_suffix (str, ".desktop"))
        continue;

      /* try to load the item */
      desktop_id = FALSE;
770 771
      location_changed = FALSE;
      item = launcher_plugin_item_load (plugin, str, &desktop_id, &location_changed);
772 773 774 775 776 777 778
      if (G_LIKELY (item == NULL))
        {
          /* str did not look like a desktop-id, so no need to look
           * for it in the application pool */
          if (!desktop_id)
            continue;

779 780 781 782 783
          /* we are going to load an desktop_id from the item pool,
           * even if this failes, save the new item list, so we don't
           * try this again in the future */
          items_modified = TRUE;

784 785 786 787 788 789 790 791 792 793
          /* load the pool with desktop items */
          if (pool == NULL)
            pool = launcher_plugin_garcon_menu_pool ();

          /* lookup the item in the item pool */
          pool_item = g_hash_table_lookup (pool, str);
          if (pool_item != NULL)
            {
              /* we want an editable file, so try to make a copy */
              uri = garcon_menu_item_get_uri (pool_item);
794
              item = launcher_plugin_item_load (plugin, uri, NULL, NULL);
795 796 797 798 799
              g_free (uri);

              /* if something failed, use the pool item, but this one
               * won't be editable in the dialog */
              if (G_UNLIKELY (item == NULL))
800
                item = GARCON_MENU_ITEM (g_object_ref (G_OBJECT (pool_item)));
801 802 803 804 805 806
            }

          /* skip this item if still not found */
          if (item == NULL)
            continue;
        }
807 808 809 810
      else if (location_changed)
        {
          items_modified = TRUE;
        }
811 812 813 814 815 816 817 818 819 820 821

      /* add the item to the list */
      panel_assert (GARCON_IS_MENU_ITEM (item));
      items = g_slist_append (items, item);
      g_signal_connect (G_OBJECT (item), "changed",
          G_CALLBACK (launcher_plugin_item_changed), plugin);
    }

  if (G_UNLIKELY (pool != NULL))
    g_hash_table_destroy (pool);

822 823 824
  /* remove config files of items not in the new config */
  launcher_plugin_items_delete_configs (plugin);

825
  /* release the old menu items and set new one */
826
  launcher_plugin_items_free (plugin);
827
  plugin->items = items;
828 829 830 831

  /* store the new item list */
  if (items_modified)
    launcher_plugin_save_delayed (plugin);
832 833 834 835
}



836 837 838 839 840 841
static void
launcher_plugin_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
842 843
  LauncherPlugin *plugin = XFCE_LAUNCHER_PLUGIN (object);
  GPtrArray      *array;
844 845

  panel_return_if_fail (G_IS_FILE (plugin->config_directory));
846 847 848 849 850 851

  /* destroy the menu, all the setting changes need this */
  launcher_plugin_menu_destroy (plugin);

  switch (prop_id)
    {
852 853 854 855
    case PROP_ITEMS:
      /* load new items from the array */
      array = g_value_get_boxed (value);
      if (G_LIKELY (array != NULL))
856 857 858
        {
          launcher_plugin_items_load (plugin, array);
        }
859
      else
860 861 862 863
        {
          launcher_plugin_items_delete_configs (plugin);
          launcher_plugin_items_free (plugin);
        }
864

865 866
      /* emit signal */
      g_signal_emit (G_OBJECT (plugin), launcher_signals[ITEMS_CHANGED], 0);
867

868 869
      /* update the button */
      launcher_plugin_button_update (plugin);
870 871 872
#if GARCON_CHECK_VERSION(0,7,0)
      launcher_plugin_button_update_action_menu (plugin);
#endif
873

874 875 876
      /* update the widget packing */
      goto update_arrow;
      break;
877

878 879 880 881
    case PROP_DISABLE_TOOLTIPS:
      plugin->disable_tooltips = g_value_get_boolean (value);
      gtk_widget_set_has_tooltip (plugin->button, !plugin->disable_tooltips);
      break;
882

883 884 885
    case PROP_MOVE_FIRST:
      plugin->move_first = g_value_get_boolean (value);
      break;
886

887 888
    case PROP_SHOW_LABEL:
      plugin->show_label = g_value_get_boolean (value);
889

890 891 892
      /* destroy the old child */
      if (plugin->child != NULL)
        gtk_widget_destroy (plugin->child);
893

894 895 896 897
      /* create child */
      if (G_UNLIKELY (plugin->show_label))
        plugin->child = gtk_label_new (NULL);
      else
898
        plugin->child = gtk_image_new ();
899 900 901 902 903 904 905 906 907 908
      gtk_container_add (GTK_CONTAINER (plugin->button), plugin->child);
      gtk_widget_show (plugin->child);

      /* update size */
      launcher_plugin_size_changed (XFCE_PANEL_PLUGIN (plugin),
          xfce_panel_plugin_get_size (XFCE_PANEL_PLUGIN (plugin)));

      /* update the button */
      launcher_plugin_button_update (plugin);
      break;
909

910 911
    case PROP_ARROW_POSITION:
      plugin->arrow_position = g_value_get_uint (value);
912

913
update_arrow:
914 915
      /* update the arrow button visibility */
      launcher_plugin_arrow_visibility (plugin);
916

917 918
      /* repack the widgets */
      launcher_plugin_pack_widgets (plugin);
919

920 921 922 923
      /* update the plugin size */
      launcher_plugin_size_changed (XFCE_PANEL_PLUGIN (plugin),
          xfce_panel_plugin_get_size (XFCE_PANEL_PLUGIN (plugin)));
      break;
924

925 926 927
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
928 929 930 931 932
    }
}



933 934 935 936 937 938 939
static void
launcher_plugin_file_changed (GFileMonitor      *monitor,
                              GFile             *changed_file,
                              GFile             *other_file,
                              GFileMonitorEvent  event_type,
                              LauncherPlugin    *plugin)
{
940
  GSList         *li, *lnext;
941 942 943 944 945 946 947 948 949 950 951 952 953 954
  GarconMenuItem *item;
  GFile          *item_file;
  gboolean        found;
  GError         *error = NULL;
  gchar          *base_name;
  gboolean        result;
  gboolean        exists;
  gboolean        update_plugin = FALSE;

  panel_return_if_fail (XFCE_IS_LAUNCHER_PLUGIN (plugin));
  panel_return_if_fail (plugin->config_monitor == monitor);

  /* waited until all events are proccessed */
  if (event_type != G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT
955 956
      && event_type != G_FILE_MONITOR_EVENT_DELETED
      && event_type != G_FILE_MONITOR_EVENT_CREATED)
957 958 959 960 961 962 963 964 965 966 967 968
    return;

  /* we only act on desktop files */
  base_name = g_file_get_basename (changed_file);
  result = g_str_has_suffix (base_name, ".desktop");
  g_free (base_name);
  if (!result)
    return;

  exists = g_file_query_exists (changed_file, NULL);

  /* lookup the file in the menu items */
969
  for (li = plugin->items, found = FALSE; !found && li != NULL; li = lnext)
970
    {
971
      lnext = li->next;
972 973 974 975 976 977 978 979
      item = GARCON_MENU_ITEM (li->data);
      item_file = garcon_menu_item_get_file (item);
      found = g_file_equal (changed_file, item_file);
      if (found)
        {
          if (exists)
            {
              /* reload the file */
Nick Schermer's avatar
Nick Schermer committed
980
              if (!garcon_menu_item_reload (item, NULL, &error))
981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013
                {
                  g_critical ("Failed to reload menu item: %s", error->message);
                  g_error_free (error);
                }
            }
          else
            {
              /* remove from the list */
              plugin->items = g_slist_delete_link (plugin->items, li);
              g_object_unref (G_OBJECT (item));
              update_plugin = TRUE;
            }
        }
      g_object_unref (G_OBJECT (item_file));
    }

  if (!found && exists)
    {
      /* add the new file to the config */
      item = garcon_menu_item_new (changed_file);
      if (G_LIKELY (item != NULL))
        {
          plugin->items = g_slist_append (plugin->items, item);
          g_signal_connect (G_OBJECT (item), "changed",
              G_CALLBACK (launcher_plugin_item_changed), plugin);
          update_plugin = TRUE;
        }
    }

  if (update_plugin)
    {
      launcher_plugin_button_update (plugin);
      launcher_plugin_menu_destroy (plugin);
1014 1015 1016
#if GARCON_CHECK_VERSION(0,7,0)
      launcher_plugin_button_update_action_menu (plugin);
#endif
1017 1018

      /* save the new config */
1019
      launcher_plugin_save_delayed (plugin);
1020 1021 1022 1023 1024 1025 1026 1027

      /* update the dialog */
      g_signal_emit (G_OBJECT (plugin), launcher_signals[ITEMS_CHANGED], 0);
    }
}



1028
static void
1029
launcher_plugin_construct (XfcePanelPlugin *panel_plugin)
Nick Schermer's avatar
Nick Schermer committed
1030
{
1031
  LauncherPlugin      *plugin = XFCE_LAUNCHER_PLUGIN (panel_plugin);
1032
  const gchar * const *uris;
1033
  guint                i;
1034 1035
  GPtrArray           *array;
  GValue              *value;
1036 1037
  gchar               *file, *path;
  GError              *error = NULL;
1038 1039
  const PanelProperty  properties[] =
  {
1040
    { "show-label", G_TYPE_BOOLEAN },
1041
    { "items", G_TYPE_PTR_ARRAY },
1042 1043 1044
    { "disable-tooltips", G_TYPE_BOOLEAN },
    { "move-first", G_TYPE_BOOLEAN },
    { "arrow-position", G_TYPE_UINT },
1045
    { NULL }
1046
  };
1047

1048
  /* show the configure menu item */
1049 1050 1051
  xfce_panel_plugin_menu_show_configure (panel_plugin);

  xfce_panel_plugin_set_small (panel_plugin, TRUE);
1052

1053
  /* lookup the config directory where this launcher stores it's desktop files */
1054
  file = g_strdup_printf (RELATIVE_CONFIG_PATH,
1055 1056 1057 1058 1059 1060 1061
                          xfce_panel_plugin_get_name (XFCE_PANEL_PLUGIN (plugin)),
                          xfce_panel_plugin_get_unique_id (XFCE_PANEL_PLUGIN (plugin)));
  path = xfce_resource_save_location (XFCE_RESOURCE_CONFIG, file, FALSE);
  plugin->config_directory = g_file_new_for_path (path);
  g_free (file);
  g_free (path);

1062
  /* bind all properties */
1063
  panel_properties_bind (NULL, G_OBJECT (plugin),
1064
                         xfce_panel_plugin_get_property_base (panel_plugin),
1065
                         properties, FALSE);
1066

1067
  /* handle and empty plugin */
1068 1069 1070
  if (G_UNLIKELY (plugin->items == NULL))
    {
      /* get the plugin arguments list */
1071 1072
      uris = xfce_panel_plugin_get_arguments (panel_plugin);
      if (G_LIKELY (uris != NULL))
1073
        {
1074
          /* create array with all the uris */
1075
          array = g_ptr_array_new ();
1076
          for (i = 0; uris[i] != NULL; i++)
1077
            {
1078 1079
              value = g_new0 (GValue, 1);
              g_value_init (value, G_TYPE_STRING);
1080
              g_value_set_static_string (value, uris[i]);
1081
              g_ptr_array_add (array, value);
1082
            }
1083

1084 1085 1086
          /* set new file list */
          if (G_LIKELY (array->len > 0))
            g_object_set (G_OBJECT (plugin), "items", array, NULL);
1087 1088 1089 1090 1091
          xfconf_array_free (array);
        }
      else
        {
          /* update the icon */
1092
          launcher_plugin_button_update (plugin);
1093 1094