/*
 * Copyright (C) 2008-2010 Nick Schermer <nick@xfce.org>
 *
 * 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.
 *
 * 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.
 *
 * 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
 */

#include "launcher-dialog.h"

#include "common/panel-private.h"
#include "common/panel-utils.h"

#include <garcon/garcon.h>
#include <gdk/gdkkeysyms.h>
#include <gio/gio.h>
#include <libxfce4ui/libxfce4ui.h>
#include <libxfce4util/libxfce4util.h>
#include <xfconf/xfconf.h>



typedef struct
{
  LauncherPlugin *plugin;
  GtkBuilder *builder;
  guint idle_populate_id;
  GSList *items;
  XfceItemListStore *store;
} LauncherPluginDialog;

typedef struct
{
  LauncherPluginDialog *dialog;
  GarconMenuItem *item;
} LauncherChangedHandler;

enum
{
  COL_ICON,
  COL_NAME,
  COL_ITEM,
  COL_TOOLTIP,
  COL_SEARCH,
};

enum
{
  ITEM_COL_ITEM = XFCE_ITEM_LIST_MODEL_COLUMN_USER,
  ITEM_N_COLS,
};



static void
launcher_dialog_items_set_item (GtkTreeModel *model,
                                GtkTreeIter *iter,
                                GarconMenuItem *item,
                                LauncherPluginDialog *dialog);
static void
launcher_dialog_tree_save (LauncherPluginDialog *dialog);
static gboolean
launcher_dialog_press_event (LauncherPluginDialog *dialog,
                             const gchar *object_name);
static void
launcher_dialog_items_unload (LauncherPluginDialog *dialog);
static void
launcher_dialog_items_load (LauncherPluginDialog *dialog);
static void
launcher_dialog_item_desktop_item_edit (GtkWidget *widget,
                                        const gchar *type,
                                        const gchar *uri,
                                        LauncherPluginDialog *dialog);
static void
launcher_dialog_item_list_changed (LauncherPluginDialog *dialog);



/* dnd for items in and from the treeviews */
enum
{
  DROP_TARGET_URI,
  DROP_TARGET_ROW
};

static const GtkTargetEntry add_drag_targets[] = {
  { "text/uri-list", 0, 0 }
};

static const GtkTargetEntry list_drop_targets[] = {
  { "text/uri-list", 0, DROP_TARGET_URI },
  { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, DROP_TARGET_ROW }
};

static const GtkTargetEntry list_drag_targets[] = {
  { "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }
};



static gboolean
launcher_dialog_add_visible_function (GtkTreeModel *model,
                                      GtkTreeIter *iter,
                                      gpointer user_data)
{
  gchar *string, *escaped;
  gboolean visible = FALSE;
  const gchar *text;
  gchar *normalized;
  gchar *text_casefolded;
  gchar *name_casefolded;

  /* get the search string from the item */
  text = gtk_entry_get_text (GTK_ENTRY (user_data));
  if (G_UNLIKELY (xfce_str_is_empty (text)))
    return TRUE;

  /* casefold the search text */
  normalized = g_utf8_normalize (text, -1, G_NORMALIZE_ALL);
  text_casefolded = g_utf8_casefold (normalized, -1);
  g_free (normalized);

  /* try the pre-build search string first */
  gtk_tree_model_get (model, iter, COL_SEARCH, &string, -1);
  if (!xfce_str_is_empty (string))
    {
      /* search */
      visible = (strstr (string, text_casefolded) != NULL);
    }
  else
    {
      /* get the name */
      gtk_tree_model_get (model, iter, COL_NAME, &string, -1);
      if (!xfce_str_is_empty (string))
        {
          /* escape and casefold the name */
          escaped = g_markup_escape_text (string, -1);
          normalized = g_utf8_normalize (escaped, -1, G_NORMALIZE_ALL);
          name_casefolded = g_utf8_casefold (normalized, -1);
          g_free (normalized);
          g_free (escaped);

          /* search */
          visible = (strstr (name_casefolded, text_casefolded) != NULL);

          /* store the generated search string */
          gtk_list_store_set (GTK_LIST_STORE (model), iter, COL_SEARCH,
                              name_casefolded, -1);

          g_free (name_casefolded);
        }
    }

  g_free (text_casefolded);
  g_free (string);

  return visible;
}



static void
launcher_dialog_add_store_insert (gpointer key,
                                  gpointer value,
                                  gpointer user_data)
{
  GtkTreeIter iter;
  GarconMenuItem *item = GARCON_MENU_ITEM (value);
  GtkTreeModel *model = GTK_TREE_MODEL (user_data);

  panel_return_if_fail (GARCON_IS_MENU_ITEM (item));
  panel_return_if_fail (GTK_IS_LIST_STORE (model));

  gtk_list_store_append (GTK_LIST_STORE (model), &iter);
  launcher_dialog_items_set_item (model, &iter, item, NULL);
}



static gboolean
launcher_dialog_add_populate_model_idle (gpointer user_data)
{
  LauncherPluginDialog *dialog = user_data;
  GObject *store;
  GHashTable *pool;

  panel_return_val_if_fail (GTK_IS_BUILDER (dialog->builder), FALSE);

  /* load the item pool */
  pool = launcher_plugin_garcon_menu_pool ();

  /* insert the items in the store */
  store = gtk_builder_get_object (dialog->builder, "add-store");
  g_hash_table_foreach (pool, launcher_dialog_add_store_insert, store);

  g_hash_table_destroy (pool);

  return FALSE;
}



static void
launcher_dialog_add_populate_model_idle_destroyed (gpointer user_data)
{
  ((LauncherPluginDialog *) user_data)->idle_populate_id = 0;
}



static void
launcher_dialog_add_populate_model (LauncherPluginDialog *dialog)
{
  GObject *store;

  panel_return_if_fail (GTK_IS_BUILDER (dialog->builder));

  /* get the store and make sure it's empty */
  store = gtk_builder_get_object (dialog->builder, "add-store");
  gtk_list_store_clear (GTK_LIST_STORE (store));

  /* fire an idle menu system load */
  if (G_LIKELY (dialog->idle_populate_id == 0))
    dialog->idle_populate_id = gdk_threads_add_idle_full (
      G_PRIORITY_DEFAULT_IDLE,
      launcher_dialog_add_populate_model_idle,
      dialog, launcher_dialog_add_populate_model_idle_destroyed);
}



static void
launcher_dialog_add_drag_data_get (GtkWidget *treeview,
                                   GdkDragContext *drag_context,
                                   GtkSelectionData *data,
                                   guint info,
                                   guint timestamp,
                                   LauncherPluginDialog *dialog)
{
  GtkTreeSelection *selection;
  GList *rows, *li;
  GtkTreeModel *model;
  gchar **uris;
  GarconMenuItem *item;
  guint i;
  GtkTreeIter iter;

  panel_return_if_fail (GTK_IS_BUILDER (dialog->builder));
  panel_return_if_fail (GTK_IS_TREE_VIEW (treeview));

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
  rows = gtk_tree_selection_get_selected_rows (selection, &model);
  if (G_UNLIKELY (rows == NULL))
    return;

  uris = g_new0 (gchar *, g_list_length (rows) + 1);
  for (li = rows, i = 0; li != NULL; li = li->next)
    {
      if (!gtk_tree_model_get_iter (model, &iter, li->data))
        continue;

      gtk_tree_model_get (model, &iter, COL_ITEM, &item, -1);
      if (G_UNLIKELY (item == NULL))
        continue;

      uris[i++] = garcon_menu_item_get_uri (item);
      g_object_unref (G_OBJECT (item));
    }

  gtk_selection_data_set_uris (data, uris);
  g_list_free (rows);
  g_strfreev (uris);
}



static void
launcher_dialog_add_selection_changed (GtkTreeSelection *selection,
                                       LauncherPluginDialog *dialog)
{
  GObject *object;
  gboolean sensitive;

  panel_return_if_fail (GTK_IS_BUILDER (dialog->builder));
  panel_return_if_fail (GTK_IS_TREE_SELECTION (selection));

  object = gtk_builder_get_object (dialog->builder, "button-add");
  sensitive = !!(gtk_tree_selection_count_selected_rows (selection) > 0);
  gtk_widget_set_sensitive (GTK_WIDGET (object), sensitive);
}



static gboolean
launcher_dialog_add_button_press_event (GtkTreeView *treeview,
                                        GdkEventButton *event,
                                        LauncherPluginDialog *dialog)
{
  panel_return_val_if_fail (GTK_IS_BUILDER (dialog->builder), FALSE);
  panel_return_val_if_fail (GTK_IS_TREE_VIEW (treeview), FALSE);

  if (event->button == 1
      && event->type == GDK_2BUTTON_PRESS
      && event->window == gtk_tree_view_get_bin_window (treeview)
      && gtk_tree_view_get_path_at_pos (treeview, event->x, event->y,
                                        NULL, NULL, NULL, NULL))
    return launcher_dialog_press_event (dialog, "button-add");

  return FALSE;
}



static gboolean
launcher_dialog_add_key_press_event (GtkTreeView *treeview,
                                     GdkEventKey *event,
                                     LauncherPluginDialog *dialog)
{
  panel_return_val_if_fail (GTK_IS_BUILDER (dialog->builder), FALSE);
  panel_return_val_if_fail (GTK_IS_TREE_VIEW (treeview), FALSE);

  if (event->keyval == GDK_KEY_Return
      || event->keyval == GDK_KEY_KP_Enter)
    return launcher_dialog_press_event (dialog, "button-add");

  return FALSE;
}



static void
launcher_dialog_add_response (GtkWidget *widget,
                              gint response_id,
                              LauncherPluginDialog *dialog)
{
  GObject *itemview, *treeview, *store;
  GtkTreeSelection *selection;
  GtkTreeModel *add_model;
  GtkTreeIter iter, tmp;
  GarconMenuItem *item;
  GList *list, *li;

  panel_return_if_fail (GTK_IS_DIALOG (widget));
  panel_return_if_fail (LAUNCHER_IS_PLUGIN (dialog->plugin));

  if (response_id == 1)
    {
      /* add all the selected rows in the add dialog */
      treeview = gtk_builder_get_object (dialog->builder, "add-treeview");
      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
      list = gtk_tree_selection_get_selected_rows (selection, &add_model);

      itemview = gtk_builder_get_object (dialog->builder, "item-view");
      treeview = G_OBJECT (xfce_item_list_view_get_tree_view (XFCE_ITEM_LIST_VIEW (itemview)));
      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));

      if (list != NULL)
        gtk_tree_selection_unselect_all (selection);

      g_signal_handlers_block_by_func (G_OBJECT (dialog->plugin), G_CALLBACK (launcher_dialog_tree_save), dialog);
      for (li = list; li != NULL; li = g_list_next (li))
        {
          /* get the selected file in the add dialog */
          gtk_tree_model_get_iter (add_model, &tmp, li->data);
          gtk_tree_model_get (add_model, &tmp, COL_ITEM, &item, -1);

          /* insert the item in the item store */
          if (G_LIKELY (item != NULL))
            {
              gint index = xfce_item_list_store_insert (dialog->store, -1);
              xfce_item_list_model_set_index (XFCE_ITEM_LIST_MODEL (dialog->store), &iter, index);

              launcher_dialog_items_set_item (GTK_TREE_MODEL (dialog->store), &iter, item, dialog);
              g_object_unref (G_OBJECT (item));

              gtk_tree_selection_select_iter (selection, &iter);
            }

          gtk_tree_path_free (li->data);
        }

      g_signal_handlers_unblock_by_func (G_OBJECT (dialog->plugin), G_CALLBACK (launcher_dialog_tree_save), dialog);
      g_list_free (list);

      /* write the model to xfconf */
      launcher_dialog_tree_save (dialog);
    }
  else
    {
      /* empty the store */
      store = gtk_builder_get_object (dialog->builder, "add-store");
      gtk_list_store_clear (GTK_LIST_STORE (store));

      /* hide the dialog, since it's owned by gtkbuilder */
      gtk_widget_hide (widget);
    }
}



static gboolean
launcher_dialog_tree_save_foreach (GtkTreeModel *model,
                                   GtkTreePath *path,
                                   GtkTreeIter *iter,
                                   gpointer user_data)
{
  GPtrArray *array = user_data;
  GValue *value;
  GarconMenuItem *item;

  /* get the desktop id of the item from the store */
  gtk_tree_model_get (model, iter, ITEM_COL_ITEM, &item, -1);
  if (G_LIKELY (item != NULL))
    {
      /* create a value with the filename */
      value = g_new0 (GValue, 1);
      g_value_init (value, G_TYPE_STRING);
      g_value_take_string (value, garcon_menu_item_get_uri (item));

      /* put it in the array and release */
      g_ptr_array_add (array, value);
      g_object_unref (G_OBJECT (item));
    }

  return FALSE;
}



static void
launcher_dialog_tree_save (LauncherPluginDialog *dialog)
{
  GPtrArray *array;

  /* save items */
  array = g_ptr_array_new ();
  gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->store),
                          launcher_dialog_tree_save_foreach, array);

  g_signal_handlers_block_by_func (G_OBJECT (dialog->plugin),
                                   G_CALLBACK (launcher_dialog_items_load), dialog);
  g_object_set (dialog->plugin, "items", array, NULL);
  g_signal_handlers_unblock_by_func (G_OBJECT (dialog->plugin),
                                     G_CALLBACK (launcher_dialog_items_load), dialog);

  /* update internal list */
  GtkTreeIter iter;
  GarconMenuItem *item;
  GSList *new_items = NULL;

  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dialog->store), &iter))
    {
      do
        {
          gtk_tree_model_get (GTK_TREE_MODEL (dialog->store), &iter, ITEM_COL_ITEM, &item, -1);
          if (G_LIKELY (item != NULL))
            new_items = g_slist_append (new_items, item);
        }
      while (gtk_tree_model_iter_next (GTK_TREE_MODEL (dialog->store), &iter));
    }
  g_slist_free_full (dialog->items, g_object_unref);
  dialog->items = new_items;

  xfconf_array_free (array);
}



static void
launcher_dialog_tree_drag_data_received (GtkWidget *treeview,
                                         GdkDragContext *context,
                                         gint x,
                                         gint y,
                                         GtkSelectionData *data,
                                         guint info,
                                         guint timestamp,
                                         LauncherPluginDialog *dialog)
{
  GtkTreePath *path;
  GtkTreeViewDropPosition drop_pos;
  gchar **uris;
  GtkTreeIter iter;
  gint position;
  GarconMenuItem *item;
  guint i;

  panel_return_if_fail (GTK_IS_TREE_VIEW (treeview));
  panel_return_if_fail (GTK_IS_BUILDER (dialog->builder));

  if (info == DROP_TARGET_URI)
    {
      uris = gtk_selection_data_get_uris (data);
      if (G_LIKELY (uris == NULL))
        {
          gtk_drag_finish (context, FALSE, FALSE, timestamp);
          return;
        }

      /* get the insert position */
      if (gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (treeview),
                                             x, y, &path, &drop_pos))
        {
          position = gtk_tree_path_get_indices (path)[0];
          gtk_tree_path_free (path);
          if (drop_pos == GTK_TREE_VIEW_DROP_AFTER
              || drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
            position++;
        }
      else
        {
          /* prepend at the end of the model */
          position = -1;
        }

      /* insert the uris */
      for (i = 0; uris[i] != NULL; i++)
        {
          if (!g_str_has_suffix (uris[i], ".desktop"))
            continue;

          item = garcon_menu_item_new_for_uri (uris[i]);
          if (G_UNLIKELY (item == NULL))
            continue;

          gint index = xfce_item_list_store_insert (dialog->store, position);
          xfce_item_list_model_set_index (XFCE_ITEM_LIST_MODEL (dialog->store), &iter, index);

          launcher_dialog_items_set_item (GTK_TREE_MODEL (dialog->store), &iter, item, dialog);
          g_object_unref (G_OBJECT (item));
        }

      g_strfreev (uris);

      launcher_dialog_tree_save (dialog);

      gtk_drag_finish (context, TRUE, FALSE, timestamp);
    }
  else if (info == DROP_TARGET_ROW)
    {
      /* nothing to do here, just wait for an row-inserted signal */
    }
}



static gboolean
launcher_dialog_press_event (LauncherPluginDialog *dialog,
                             const gchar *object_name)
{
  GObject *object;

  panel_return_val_if_fail (GTK_IS_BUILDER (dialog->builder), FALSE);

  object = gtk_builder_get_object (dialog->builder, object_name);
  panel_return_val_if_fail (GTK_IS_BUTTON (object), FALSE);
  if (gtk_widget_get_sensitive (GTK_WIDGET (object)))
    {
      gtk_button_clicked (GTK_BUTTON (object));
      return TRUE;
    }

  return FALSE;
}



static void
launcher_dialog_item_desktop_item_edit (GtkWidget *widget,
                                        const gchar *type,
                                        const gchar *uri,
                                        LauncherPluginDialog *dialog)
{
  gchar *filename;
  gchar *command;
  GdkScreen *screen;
  GError *error = NULL;
  GtkWidget *toplevel;

  panel_return_if_fail (GTK_IS_WIDGET (widget));
  panel_return_if_fail (GTK_IS_BUILDER (dialog->builder));
  panel_return_if_fail (type != NULL || uri != NULL);

  /* build command */
  if (uri != NULL)
    {
      command = g_strdup_printf ("xfce-desktop-item-edit '%s'", uri);
    }
  else
    {
      filename = launcher_plugin_unique_filename (dialog->plugin);
      command = g_strdup_printf ("xfce-desktop-item-edit -t %s -c '%s'", type, filename);
      g_free (filename);
    }

  /* spawn item editor */
  screen = gtk_widget_get_screen (widget);
  if (!xfce_spawn_command_line (screen, command, FALSE, FALSE, TRUE, &error))
    {
      toplevel = gtk_widget_get_toplevel (widget);
      xfce_dialog_show_error (GTK_WINDOW (toplevel), error,
                              _("Failed to open desktop item editor"));
      g_error_free (error);
    }

  g_free (command);
}



static void
launcher_dialog_response (GtkWidget *widget,
                          gint response_id,
                          LauncherPluginDialog *dialog)
{
  GObject *add_dialog;

  panel_return_if_fail (GTK_IS_DIALOG (widget));
  panel_return_if_fail (LAUNCHER_IS_PLUGIN (dialog->plugin));
  panel_return_if_fail (GTK_IS_BUILDER (dialog->builder));

  if (G_UNLIKELY (response_id != 1))
    {
      /* stop idle if still running */
      if (G_UNLIKELY (dialog->idle_populate_id != 0))
        g_source_remove (dialog->idle_populate_id);

      /* disconnect from items-changed signal */
      g_signal_handlers_disconnect_by_func (G_OBJECT (dialog->plugin),
                                            G_CALLBACK (launcher_dialog_items_load), dialog);

      /* disconnect from the menu items */
      launcher_dialog_items_unload (dialog);

      /* also destroy the add dialog */
      add_dialog = gtk_builder_get_object (dialog->builder, "dialog-add");
      gtk_widget_destroy (GTK_WIDGET (add_dialog));

      /* destroy the dialog */
      gtk_widget_destroy (widget);

      g_slice_free (LauncherPluginDialog, dialog);
    }
}



static gboolean
launcher_dialog_item_changed_foreach (GtkTreeModel *model,
                                      GtkTreePath *path,
                                      GtkTreeIter *iter,
                                      gpointer user_data)
{
  GarconMenuItem *item;
  gboolean found;
  LauncherChangedHandler *handler = user_data;

  panel_return_val_if_fail (GARCON_IS_MENU_ITEM (handler->item), TRUE);

  /* check if this is the item in the model */
  gtk_tree_model_get (model, iter, ITEM_COL_ITEM, &item, -1);
  found = !!(item == handler->item);

  if (G_UNLIKELY (found))
    launcher_dialog_items_set_item (model, iter, item, handler->dialog);

  g_object_unref (G_OBJECT (item));

  return found;
}



static void
launcher_dialog_item_changed (GarconMenuItem *item,
                              LauncherPluginDialog *dialog)
{
  GObject *itemview, *treeview;
  GtkTreeModel *model;
  LauncherChangedHandler *handler;

  panel_return_if_fail (GARCON_IS_MENU_ITEM (item));

  itemview = gtk_builder_get_object (dialog->builder, "item-view");
  treeview = G_OBJECT (xfce_item_list_view_get_tree_view (XFCE_ITEM_LIST_VIEW (itemview)));
  model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview));

  /* find the item in the model and update it */
  handler = g_slice_new0 (LauncherChangedHandler);
  handler->dialog = dialog;
  handler->item = item;
  gtk_tree_model_foreach (model, launcher_dialog_item_changed_foreach, handler);
  g_slice_free (LauncherChangedHandler, handler);
}



static void
launcher_dialog_item_list_changed (LauncherPluginDialog *dialog)
{
  g_return_if_fail (GTK_IS_BUILDER (dialog->builder));
  g_return_if_fail (XFCE_IS_ITEM_LIST_MODEL (dialog->store));

  GObject *object;
  gint n_items = xfce_item_list_model_get_n_items (XFCE_ITEM_LIST_MODEL (dialog->store));

  object = gtk_builder_get_object (dialog->builder, "arrow-position");
  gtk_widget_set_sensitive (GTK_WIDGET (object), n_items > 1);

  object = gtk_builder_get_object (dialog->builder, "move-first");
  gtk_widget_set_sensitive (GTK_WIDGET (object), n_items > 1);

  object = gtk_builder_get_object (dialog->builder, "arrow-position-label");
  gtk_widget_set_sensitive (GTK_WIDGET (object), n_items > 1);
}



static void
launcher_dialog_items_set_item (GtkTreeModel *model,
                                GtkTreeIter *iter,
                                GarconMenuItem *item,
                                LauncherPluginDialog *dialog)
{
  const gchar *name, *comment;
  gchar *markup;
  GIcon *icon;
  gchar *tooltip;
  GFile *gfile;

  panel_return_if_fail (GTK_IS_LIST_STORE (model) || XFCE_IS_ITEM_LIST_STORE (model));
  panel_return_if_fail (GARCON_IS_MENU_ITEM (item));

  name = garcon_menu_item_get_name (item);
  comment = garcon_menu_item_get_comment (item);

  if (!xfce_str_is_empty (comment))
    markup = g_markup_printf_escaped ("<b>%s</b>\n%s", name, comment);
  else
    markup = g_markup_printf_escaped ("<b>%s</b>", name);

  icon = launcher_plugin_tooltip_icon (garcon_menu_item_get_icon_name (item));

  if (dialog != NULL)
    g_signal_handlers_block_by_func (G_OBJECT (model), G_CALLBACK (launcher_dialog_items_load), dialog);

  gfile = garcon_menu_item_get_file (item);
  tooltip = g_file_get_parse_name (gfile);
  g_object_unref (G_OBJECT (gfile));

  if (GTK_IS_LIST_STORE (model))
    {
      gtk_list_store_set (GTK_LIST_STORE (model), iter,
                          COL_ICON, icon,
                          COL_NAME, markup,
                          COL_ITEM, item,
                          COL_TOOLTIP, tooltip,
                          -1);
    }
  else if (XFCE_IS_ITEM_LIST_STORE (model))
    {
      gint index = xfce_item_list_model_get_index (XFCE_ITEM_LIST_MODEL (model), iter);
      xfce_item_list_store_set (XFCE_ITEM_LIST_STORE (model), index,
                                XFCE_ITEM_LIST_MODEL_COLUMN_ICON, icon,
                                XFCE_ITEM_LIST_MODEL_COLUMN_NAME, markup,
                                XFCE_ITEM_LIST_MODEL_COLUMN_TOOLTIP, tooltip,
                                XFCE_ITEM_LIST_MODEL_COLUMN_EDITABLE, launcher_plugin_item_is_editable (dialog->plugin, item, NULL),
                                ITEM_COL_ITEM, item,
                                -1);
    }

  if (dialog != NULL)
    g_signal_handlers_unblock_by_func (G_OBJECT (model), G_CALLBACK (launcher_dialog_items_load), dialog);

  if (G_LIKELY (icon != NULL))
    g_object_unref (G_OBJECT (icon));
  g_free (markup);
  g_free (tooltip);
}



static void
launcher_dialog_items_unload (LauncherPluginDialog *dialog)
{
  GSList *li;

  for (li = dialog->items; li != NULL; li = li->next)
    {
      panel_return_if_fail (GARCON_IS_MENU_ITEM (li->data));
      g_signal_handlers_disconnect_by_func (G_OBJECT (li->data),
                                            G_CALLBACK (launcher_dialog_item_changed), dialog);
      g_object_unref (G_OBJECT (li->data));
    }

  g_clear_slist (&dialog->items, NULL);
}



static void
launcher_dialog_items_load (LauncherPluginDialog *dialog)
{
  GSList *li;
  GtkTreeIter iter;

  /* if the new state is identical to the old one, then do not reset the storage */
  GSList *new_items = launcher_plugin_get_items (dialog->plugin);
  GSList *prev = dialog->items, *cur = new_items;
  for (; prev != NULL && cur != NULL; prev = prev->next, cur = cur->next)
    {
      if (prev->data != cur->data)
        break;
    }
  if (prev != cur)
    {
      g_signal_handlers_block_by_func (dialog->store, G_CALLBACK (launcher_dialog_tree_save), dialog);

      /* save selection */
      GObject *itemview = gtk_builder_get_object (dialog->builder, "item-view");
      GtkWidget *treeview = xfce_item_list_view_get_tree_view (XFCE_ITEM_LIST_VIEW (itemview));
      GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
      GList *selected_rows = gtk_tree_selection_get_selected_rows (selection, NULL);

      /* clear the old items */
      launcher_dialog_items_unload (dialog);
      xfce_item_list_store_clear (XFCE_ITEM_LIST_STORE (dialog->store));

      /* insert the launcher items */
      dialog->items = g_slist_copy (new_items);
      g_slist_foreach (dialog->items, (GFunc) (void (*) (void)) g_object_ref, NULL);
      for (li = dialog->items; li != NULL; li = li->next)
        {
          gint index = xfce_item_list_store_insert (XFCE_ITEM_LIST_STORE (dialog->store), -1);

          xfce_item_list_model_set_index (XFCE_ITEM_LIST_MODEL (dialog->store), &iter, index);
          launcher_dialog_items_set_item (GTK_TREE_MODEL (dialog->store), &iter, GARCON_MENU_ITEM (li->data), dialog);
          g_signal_connect (G_OBJECT (li->data), "changed", G_CALLBACK (launcher_dialog_item_changed), dialog);
        }

      /* restore selection */
      for (GList *l = selected_rows; l != NULL; l = l->next)
        gtk_tree_selection_select_path (selection, l->data);
      g_list_free_full (selected_rows, (GDestroyNotify) gtk_tree_path_free);

      g_signal_handlers_unblock_by_func (dialog->store, G_CALLBACK (launcher_dialog_tree_save), dialog);
    }
  g_slist_free_full (new_items, g_object_unref);
  launcher_dialog_item_list_changed (dialog);
}



static gboolean
launcher_dialog_item_edit (XfceItemListView *item_view,
                           gint index,
                           LauncherPluginDialog *dialog)
{
  GarconMenuItem *item = NULL;
  GtkTreeIter iter;

  xfce_item_list_model_set_index (XFCE_ITEM_LIST_MODEL (dialog->store), &iter, index);
  gtk_tree_model_get (GTK_TREE_MODEL (dialog->store), &iter, ITEM_COL_ITEM, &item, -1);
  if (G_UNLIKELY (item == NULL))
    return TRUE;

  /* build command */
  gchar *uri = garcon_menu_item_get_uri (item);

  g_object_unref (item);
  launcher_dialog_item_desktop_item_edit (GTK_WIDGET (item_view), NULL, uri, dialog);
  g_free (uri);

  return FALSE;
}



static gboolean
launcher_dialog_item_add (XfceItemListView *item_view,
                          LauncherPluginDialog *dialog)
{
  GObject *object = gtk_builder_get_object (dialog->builder, "dialog-add");
  launcher_dialog_add_populate_model (dialog);
  gtk_widget_show (GTK_WIDGET (object));

  return TRUE;
}



static gboolean
launcher_dialog_item_remove_items (XfceItemListView *item_view,
                                   const gint *items,
                                   gint n_items,
                                   LauncherPluginDialog *dialog)
{
  gchar *primary = NULL;

  /* get name */
  if (n_items == 1)
    {
      const gchar *display_name = NULL;
      GtkTreeIter iter;
      GarconMenuItem *item;

      xfce_item_list_model_set_index (XFCE_ITEM_LIST_MODEL (dialog->store), &iter, items[0]);
      gtk_tree_model_get (GTK_TREE_MODEL (dialog->store), &iter, ITEM_COL_ITEM, &item, -1);
      if (G_LIKELY (item != NULL))
        display_name = garcon_menu_item_get_name (item);

      display_name = xfce_str_is_empty (display_name) ? _("Unnamed item") : display_name;
      primary = g_strdup_printf (_("Are you sure you want to remove \"%s\"?"), display_name);

      if (G_LIKELY (item != NULL))
        g_object_unref (G_OBJECT (item));
    }
  else
    {
      primary = g_strdup_printf (_("Are you sure that you want to remove these %d items?"), n_items);
    }

  /* ask the user */
  GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (item_view));
  const gchar *label = _("_Remove");
  const gchar *text = _("If you delete an item, it will be permanently removed");
  gboolean confirmed = xfce_dialog_confirm (GTK_WINDOW (toplevel), "edit-delete",
                                            label,
                                            text,
                                            "%s",
                                            primary);
  g_free (primary);
  return !confirmed;
}



static void
launcher_dialog_item_new (GSimpleAction *action,
                          GVariant *parameter,
                          LauncherPluginDialog *dialog)
{
  GObject *itemview = gtk_builder_get_object (dialog->builder, "item-view");

  launcher_dialog_item_desktop_item_edit (GTK_WIDGET (itemview), "Application", NULL, dialog);
}



static void
launcher_dialog_item_new_link (GSimpleAction *action,
                               GVariant *parameter,
                               LauncherPluginDialog *dialog)
{
  GObject *itemview = gtk_builder_get_object (dialog->builder, "item-view");

  launcher_dialog_item_desktop_item_edit (GTK_WIDGET (itemview), "Link", NULL, dialog);
}



void
launcher_dialog_show (LauncherPlugin *plugin)
{
  LauncherPluginDialog *dialog;
  GtkBuilder *builder;
  GObject **window, *object, *item;
  guint i;
  GtkTreeSelection *selection;
  const gchar *binding_names[] = { "disable-tooltips", "show-label",
                                   "move-first", "arrow-position" };

  panel_return_if_fail (LAUNCHER_IS_PLUGIN (plugin));

  /* setup the dialog */
  window = launcher_plugin_get_settings_dialog_pointer (plugin);
  builder = panel_utils_builder_new (XFCE_PANEL_PLUGIN (plugin), "/org/xfce/panel/launcher-dialog.glade", window);
  if (G_UNLIKELY (builder == NULL))
    return;

  launcher_plugin_set_settings_dialog (plugin, *window);

  /* create structure */
  dialog = g_slice_new0 (LauncherPluginDialog);
  dialog->builder = builder;
  dialog->plugin = plugin;
  dialog->items = NULL;

  g_signal_connect (G_OBJECT (*window), "response",
                    G_CALLBACK (launcher_dialog_response), dialog);

  /* create item store */
  dialog->store = xfce_item_list_store_new (ITEM_N_COLS, G_TYPE_OBJECT, -1);
  g_signal_connect_swapped (dialog->store, "after-remove-item", G_CALLBACK (launcher_dialog_tree_save), dialog);
  g_signal_connect_swapped (dialog->store, "after-move-item", G_CALLBACK (launcher_dialog_tree_save), dialog);
  g_object_set (dialog->store, "list-flags",
                XFCE_ITEM_LIST_MODEL_REORDERABLE
                  | XFCE_ITEM_LIST_MODEL_ADDABLE
                  | XFCE_ITEM_LIST_MODEL_REMOVABLE
                  | XFCE_ITEM_LIST_MODEL_EDITABLE,
                NULL);
  object = gtk_builder_get_object (builder, "item-view");
  xfce_item_list_view_set_model (XFCE_ITEM_LIST_VIEW (object), XFCE_ITEM_LIST_MODEL (dialog->store));
  g_object_unref (dialog->store);

  /* get itemview & treeview */
  object = gtk_builder_get_object (builder, "item-view");
  g_signal_connect (object, "edit-item", G_CALLBACK (launcher_dialog_item_edit), dialog);
  g_signal_connect (object, "add-item", G_CALLBACK (launcher_dialog_item_add), dialog);
  g_signal_connect (object, "remove-items", G_CALLBACK (launcher_dialog_item_remove_items), dialog);
  GtkWidget *treeview = GTK_WIDGET (xfce_item_list_view_get_tree_view (XFCE_ITEM_LIST_VIEW (object)));

  /* setup actions */
  GActionGroup *action_group = gtk_widget_get_action_group (GTK_WIDGET (object), "xfce-item-list-view");
  GMenu *menu = xfce_item_list_view_get_menu (XFCE_ITEM_LIST_VIEW (object));

  /* create "New item" action */
  GSimpleAction *new_action = g_simple_action_new ("new-item", NULL);
  g_signal_connect (new_action, "activate", G_CALLBACK (launcher_dialog_item_new), dialog);
  g_action_map_add_action (G_ACTION_MAP (action_group), G_ACTION (new_action));
  g_object_unref (new_action);

  GMenuItem *new_mi = g_menu_item_new (_("Add a new empty item"), "xfce-item-list-view.new-item");
  g_menu_item_set_attribute_value (new_mi, XFCE_MENU_ATTRIBUTE_TOOLTIP, g_variant_new_string (_("Add a new empty item")));
  GIcon *new_icon = g_themed_icon_new ("document-new-symbolic");
  g_menu_item_set_icon (new_mi, new_icon);
  g_object_unref (new_icon);
  g_menu_append_item (menu, new_mi);
  g_object_unref (new_mi);

  /* create "New Link" action */
  GSimpleAction *link_action = g_simple_action_new ("new-link", NULL);
  g_signal_connect (link_action, "activate", G_CALLBACK (launcher_dialog_item_new_link), dialog);
  g_action_map_add_action (G_ACTION_MAP (action_group), G_ACTION (link_action));
  g_object_unref (link_action);

  GMenuItem *link_mi = g_menu_item_new (_("Add a new hyperlink"), "xfce-item-list-view.new-link");
  g_menu_item_set_attribute_value (link_mi, XFCE_MENU_ATTRIBUTE_TOOLTIP, g_variant_new_string (_("Add a new hyperlink")));
  GIcon *link_icon = g_themed_icon_new ("applications-internet-symbolic");
  g_menu_item_set_icon (link_mi, link_icon);
  g_object_unref (link_icon);
  g_menu_append_item (menu, link_mi);
  g_object_unref (link_mi);

  /* setup treeview columns */
  GtkTreeViewColumn *icon_column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), XFCE_ITEM_LIST_VIEW_COLUMN_ICON);
  gtk_tree_view_column_set_spacing (icon_column, 2);
  GtkCellRenderer *renderer = g_object_get_data (G_OBJECT (icon_column), "renderer");
  g_object_set (renderer, "stock-size", GTK_ICON_SIZE_DND, NULL);

  /* setup treeview selection */
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
  gtk_tree_view_enable_model_drag_dest (GTK_TREE_VIEW (treeview), list_drop_targets,
                                        G_N_ELEMENTS (list_drop_targets), GDK_ACTION_COPY);
  gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (treeview), GDK_BUTTON1_MASK, list_drag_targets,
                                          G_N_ELEMENTS (list_drag_targets), GDK_ACTION_MOVE);
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
  g_signal_connect (G_OBJECT (treeview), "drag-data-received",
                    G_CALLBACK (launcher_dialog_tree_drag_data_received), dialog);

  /* connect bindings to the advanced properties */
  for (i = 0; i < G_N_ELEMENTS (binding_names); i++)
    {
      object = gtk_builder_get_object (builder, binding_names[i]);
      panel_return_if_fail (GTK_IS_WIDGET (object));
      g_object_bind_property (G_OBJECT (plugin), binding_names[i],
                              G_OBJECT (object), "active",
                              G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
    }

  /* setup responses for the add dialog */
  object = gtk_builder_get_object (builder, "dialog-add");
  gtk_window_set_screen (GTK_WINDOW (object),
                         gtk_window_get_screen (GTK_WINDOW (*window)));
  g_signal_connect (G_OBJECT (object), "response",
                    G_CALLBACK (launcher_dialog_add_response), dialog);
  g_signal_connect (G_OBJECT (object), "delete-event",
                    G_CALLBACK (gtk_widget_hide_on_delete), NULL);

  /* setup sorting in the add dialog */
  object = gtk_builder_get_object (builder, "add-store");
  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (object),
                                        COL_NAME, GTK_SORT_ASCENDING);

  /* allow selecting multiple items in the add dialog */
  object = gtk_builder_get_object (builder, "add-treeview");
  gtk_drag_source_set (GTK_WIDGET (object), GDK_BUTTON1_MASK,
                       add_drag_targets, G_N_ELEMENTS (add_drag_targets), GDK_ACTION_COPY);
  g_signal_connect (G_OBJECT (object), "drag-data-get",
                    G_CALLBACK (launcher_dialog_add_drag_data_get), dialog);
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (object));
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
  g_signal_connect (G_OBJECT (selection), "changed",
                    G_CALLBACK (launcher_dialog_add_selection_changed), dialog);
  g_signal_connect (G_OBJECT (object), "button-press-event",
                    G_CALLBACK (launcher_dialog_add_button_press_event), dialog);
  g_signal_connect (G_OBJECT (object), "key-press-event",
                    G_CALLBACK (launcher_dialog_add_key_press_event), dialog);

  /* setup search filter in the add dialog */
  object = gtk_builder_get_object (builder, "add-store-filter");
  item = gtk_builder_get_object (builder, "add-search");
  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (object),
                                          launcher_dialog_add_visible_function, item, NULL);
  g_signal_connect_swapped (G_OBJECT (item), "changed",
                            G_CALLBACK (gtk_tree_model_filter_refilter), object);

  /* load the plugin items */
  launcher_dialog_items_load (dialog);
  g_signal_connect_swapped (G_OBJECT (plugin), "items-changed",
                            G_CALLBACK (launcher_dialog_items_load), dialog);

  /* show the dialog */
  gtk_widget_show (GTK_WIDGET (*window));
}
