/*
 * Copyright (C) 2008-2010 Nick Schermer <nick@xfce.org>
 *
 * This program 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 program 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 General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "panel-application.h"
#include "panel-dialogs.h"
#include "panel-item-dialog.h"
#include "panel-itembar.h"
#include "panel-module.h"
#include "panel-plugin-external.h"
#include "panel-preferences-dialog.h"

#include "common/panel-private.h"
#include "common/panel-utils.h"
#include "libxfce4panel/libxfce4panel.h"
#include "libxfce4panel/xfce-panel-plugin-provider.h"

#include <libxfce4ui/libxfce4ui.h>
#include <libxfce4util/libxfce4util.h>

#ifdef HAVE_GTK_LAYER_SHELL
#include <gtk-layer-shell.h>
#else
#define gtk_layer_is_supported() FALSE
#endif

#ifdef HAVE_MATH_H
#include <math.h>
#endif

static void
panel_preferences_dialog_finalize (GObject *object);
static void
panel_preferences_dialog_response (GtkWidget *window,
                                   gint response_id,
                                   PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_bindings_unbind (PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_bindings_add (PanelPreferencesDialog *dialog,
                                       const gchar *property1,
                                       const gchar *property2,
                                       GBindingFlags flags);
static void
panel_preferences_dialog_bindings_update (PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_output_changed (GtkComboBox *combobox,
                                         PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_autohide_changed (GtkComboBox *combobox,
                                           PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_bg_style_changed (PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_bg_image_file_set (GtkFileChooserButton *button,
                                            PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_bg_image_notified (PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_length_max_notified (PanelPreferencesDialog *dialog);
static gboolean
panel_preferences_dialog_length_transform_to (GBinding *binding,
                                              const GValue *from_value,
                                              GValue *to_value,
                                              gpointer user_data);
static gboolean
panel_preferences_dialog_length_transform_from (GBinding *binding,
                                                const GValue *from_value,
                                                GValue *to_value,
                                                gpointer user_data);
static void
panel_preferences_dialog_panel_combobox_changed (GtkComboBox *combobox,
                                                 PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_monitors_changed (GdkDisplay *display,
                                           GdkMonitor *monitor,
                                           PanelPreferencesDialog *dialog);
static gboolean
panel_preferences_dialog_panel_combobox_rebuild (PanelPreferencesDialog *dialog,
                                                 gint panel_id);
static void
panel_preferences_dialog_panel_add (GtkWidget *widget,
                                    PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_panel_remove (GtkWidget *widget,
                                       PanelPreferencesDialog *dialog);
static gboolean
panel_preferences_dialog_icon_size_state_set (GtkSwitch *widget,
                                              gboolean state,
                                              PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_icon_size_changed (GtkSpinButton *button,
                                            PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_compositing_clicked (GtkButton *button,
                                              gpointer user_data);
static void
panel_preferences_dialog_panel_switch (GtkWidget *widget,
                                       PanelPreferencesDialog *dialog);
static XfcePanelPluginProvider *
panel_preferences_dialog_item_get_selected (PanelPreferencesDialog *dialog,
                                            GtkTreeIter *return_iter,
                                            GList **return_list);
static void
panel_preferences_dialog_item_store_rebuild (GtkWidget *itembar,
                                             PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_before_item_move (XfceItemListStore *store,
                                           gint source_index,
                                           gint dest_index,
                                           PanelPreferencesDialog *dialog);
static gboolean
panel_preferences_dialog_items_remove (GtkWidget *item_view,
                                       const gint *items,
                                       gint n_items,
                                       PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_before_item_remove (XfceItemListStore *store,
                                             gint index,
                                             PanelPreferencesDialog *dialog);
static gboolean
panel_preferences_dialog_item_add (XfceItemListView *item_view,
                                   PanelPreferencesDialog *dialog);
static gboolean
panel_preferences_dialog_item_properties (XfceItemListView *item_view,
                                          gint index,
                                          PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_item_about (GAction *about,
                                     GVariant *parameter,
                                     PanelPreferencesDialog *dialog);
static void
panel_preferences_dialog_item_selection_changed (GtkTreeSelection *selection,
                                                 PanelPreferencesDialog *dialog);



enum
{
  ITEM_COLUMN_PROVIDER = XFCE_ITEM_LIST_MODEL_COLUMN_USER,
  ITEM_COLUMN_ABOUT,
  N_ITEM_COLUMNS
};

struct _PanelPreferencesDialog
{
  GtkBuilder __parent__;

  PanelApplication *application;

  /* currently selected window in the selector */
  PanelWindow *active;

  /* GBinding's between dialog <-> window */
  GSList *bindings;

  /* items list widgets and store */
  XfceItemListView *item_view;
  GtkTreeView *tree_view;
  XfceItemListStore *store;

  /* item about action */
  GSimpleAction *about;

  /* changed signal for the active panel's itembar */
  gulong items_changed_handler_id;

  /* background image watch */
  gulong bg_image_notify_handler_id;

  /* changed signal for the output selector */
  gulong output_changed_handler_id;

  /* plug in which the dialog is embedded */
  GtkWidget *socket_plug;
};



G_DEFINE_FINAL_TYPE (PanelPreferencesDialog, panel_preferences_dialog, GTK_TYPE_BUILDER)



static PanelPreferencesDialog *dialog_singleton = NULL;



static void
panel_preferences_dialog_class_init (PanelPreferencesDialogClass *klass)
{
  GObjectClass *gobject_class;

  gobject_class = G_OBJECT_CLASS (klass);
  gobject_class->finalize = panel_preferences_dialog_finalize;
}



static void
panel_preferences_dialog_init (PanelPreferencesDialog *dialog)
{
  GdkDisplay *display = gdk_display_get_default ();
  GObject *window;
  GObject *object;
  GObject *info;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  GtkTreeSelection *selection;
  gchar *path_old;
  gchar *path_new;
  gchar *xfwm4_tweaks;

  dialog->bindings = NULL;
  dialog->application = panel_application_get ();

  /* block autohide */
  panel_application_windows_blocked (dialog->application, TRUE);

  /* load the builder data into the object */
  gtk_builder_set_translation_domain (GTK_BUILDER (dialog), GETTEXT_PACKAGE);
  gtk_builder_add_from_resource (GTK_BUILDER (dialog), "/org/xfce/panel/panel-preferences-dialog.glade", NULL);

  /* get the dialog */
  window = gtk_builder_get_object (GTK_BUILDER (dialog), "dialog");
  panel_return_if_fail (GTK_IS_WIDGET (window));
  g_signal_connect (G_OBJECT (window), "response",
                    G_CALLBACK (panel_preferences_dialog_response), dialog);

#define connect_signal(name, detail_signal, c_handler) \
  object = gtk_builder_get_object (GTK_BUILDER (dialog), name); \
  panel_return_if_fail (G_IS_OBJECT (object)); \
  g_signal_connect (G_OBJECT (object), detail_signal, G_CALLBACK (c_handler), dialog);

  /* panel selector buttons and combobox */
  connect_signal ("panel-add", "clicked", panel_preferences_dialog_panel_add);
  connect_signal ("panel-remove", "clicked", panel_preferences_dialog_panel_remove);
  connect_signal ("panel-combobox", "changed", panel_preferences_dialog_panel_combobox_changed);
  g_signal_connect_object (display, "monitor-added",
                           G_CALLBACK (panel_preferences_dialog_monitors_changed), dialog, 0);
  g_signal_connect_object (display, "monitor-removed",
                           G_CALLBACK (panel_preferences_dialog_monitors_changed), dialog, 0);

  /* check if xfce4-panel-profiles or panel-switch are installed and if either is show the button */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "panel-profiles");
  path_old = g_find_program_in_path ("xfpanel-switch");
  path_new = g_find_program_in_path ("xfce4-panel-profiles");
  if (path_new == NULL && path_old == NULL)
    gtk_widget_hide (GTK_WIDGET (object));
  g_free (path_old);
  g_free (path_new);
  connect_signal ("panel-switch", "clicked", panel_preferences_dialog_panel_switch);

  /* general tab */
  if (WINDOWING_IS_WAYLAND ())
    {
      XfconfChannel *channel = xfconf_channel_get (XFCE_PANEL_CHANNEL_NAME);
      object = gtk_builder_get_object (GTK_BUILDER (dialog), "keep-below");
      panel_return_if_fail (G_IS_OBJECT (object));
      gtk_widget_set_visible (
        GTK_WIDGET (object),
        gtk_layer_is_supported () && !xfconf_channel_get_bool (channel, "/force-all-external", FALSE));
    }

  /* appearance tab */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-style");
  panel_return_if_fail (G_IS_OBJECT (object));
  g_signal_connect_swapped (G_OBJECT (object), "changed",
                            G_CALLBACK (panel_preferences_dialog_bg_style_changed), dialog);

  /* icon size switch, handling enabling/disabling of custom icon sizes */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-spinbutton");
  panel_return_if_fail (G_IS_OBJECT (object));
  g_signal_connect (G_OBJECT (object), "value-changed",
                    G_CALLBACK (panel_preferences_dialog_icon_size_changed), dialog);
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-switch");
  panel_return_if_fail (G_IS_OBJECT (object));
  g_signal_connect (G_OBJECT (object), "state-set",
                    G_CALLBACK (panel_preferences_dialog_icon_size_state_set), dialog);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "composited");
  panel_return_if_fail (G_IS_OBJECT (object));
  g_signal_connect_swapped (G_OBJECT (object), "notify::visible",
                            G_CALLBACK (panel_preferences_dialog_bg_style_changed), dialog);

  info = gtk_builder_get_object (GTK_BUILDER (dialog), "composited-info");
  panel_return_if_fail (G_IS_OBJECT (info));
  g_object_bind_property (G_OBJECT (object), "sensitive",
                          G_OBJECT (info), "visible",
                          G_BINDING_SYNC_CREATE | G_BINDING_INVERT_BOOLEAN);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "xfwm4-settings");
  panel_return_if_fail (G_IS_OBJECT (object));
  xfwm4_tweaks = g_find_program_in_path ("xfwm4-tweaks-settings");
  gtk_widget_set_visible (GTK_WIDGET (object), xfwm4_tweaks != NULL);
  g_free (xfwm4_tweaks);
  g_signal_connect (G_OBJECT (object), "clicked",
                    G_CALLBACK (panel_preferences_dialog_compositing_clicked), NULL);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-image");
  panel_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (object));
  g_signal_connect (G_OBJECT (object), "file-set",
                    G_CALLBACK (panel_preferences_dialog_bg_image_file_set), dialog);

  /* create store for panel items */
  dialog->store = xfce_item_list_store_new (N_ITEM_COLUMNS,
                                            G_TYPE_OBJECT, /* ITEM_COLUMN_PROVIDER */
                                            G_TYPE_BOOLEAN); /* ITEM_COLUMN_ABOUT */
  g_signal_connect (dialog->store, "before-move-item", G_CALLBACK (panel_preferences_dialog_before_item_move), dialog);
  g_signal_connect (dialog->store, "before-remove-item", G_CALLBACK (panel_preferences_dialog_before_item_remove), dialog);

  /* build tree for panel items */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "item-list-view");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  dialog->item_view = XFCE_ITEM_LIST_VIEW (object);
  g_signal_connect (dialog->item_view, "remove-items", G_CALLBACK (panel_preferences_dialog_items_remove), dialog);
  g_signal_connect (dialog->item_view, "add-item", G_CALLBACK (panel_preferences_dialog_item_add), dialog);
  g_signal_connect (dialog->item_view, "edit-item", G_CALLBACK (panel_preferences_dialog_item_properties), dialog);
  dialog->tree_view = GTK_TREE_VIEW (xfce_item_list_view_get_tree_view (dialog->item_view));
  xfce_item_list_view_set_model (dialog->item_view, XFCE_ITEM_LIST_MODEL (dialog->store));

  /* setup renderers */
  column = gtk_tree_view_get_column (dialog->tree_view, XFCE_ITEM_LIST_VIEW_COLUMN_ICON);
  renderer = g_object_get_data (G_OBJECT (column), "renderer");
  g_object_set (renderer, "stock-size", GTK_ICON_SIZE_LARGE_TOOLBAR, NULL);

  /* create "About" action */
  GMenu *menu = xfce_item_list_view_get_menu (dialog->item_view);
  GMenuItem *about = g_menu_item_new (C_ ("plugin context menu", "_About"), "xfce-item-list-view.about");
  GIcon *icon = g_themed_icon_new ("help-about-symbolic");
  g_menu_item_set_icon (about, icon);
  g_menu_item_set_attribute_value (about, XFCE_MENU_ATTRIBUTE_TOOLTIP,
                                   g_variant_new_string (_("Show about information of the currently selected item")));
  g_object_unref (icon);
  g_menu_append_item (menu, about);
  g_object_unref (about);

  dialog->about = g_simple_action_new ("about", NULL);
  g_simple_action_set_enabled (dialog->about, FALSE);
  g_signal_connect (dialog->about, "activate", G_CALLBACK (panel_preferences_dialog_item_about), dialog);
  GActionGroup *action_group = gtk_widget_get_action_group (GTK_WIDGET (dialog->item_view), "xfce-item-list-view");
  g_action_map_add_action (G_ACTION_MAP (action_group), G_ACTION (dialog->about));
  g_object_unref (dialog->about);

  /* setup tree selection */
  selection = gtk_tree_view_get_selection (dialog->tree_view);
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
  g_signal_connect (G_OBJECT (selection), "changed",
                    G_CALLBACK (panel_preferences_dialog_item_selection_changed), dialog);

  /* connect the output changed signal */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "output-name");
  panel_return_if_fail (GTK_IS_COMBO_BOX (object));
  dialog->output_changed_handler_id =
    g_signal_connect (G_OBJECT (object), "changed",
                      G_CALLBACK (panel_preferences_dialog_output_changed), dialog);

  /* connect the autohide behavior changed signal */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "autohide-behavior");
  panel_return_if_fail (GTK_IS_COMBO_BOX (object));
  g_signal_connect (G_OBJECT (object), "changed",
                    G_CALLBACK (panel_preferences_dialog_autohide_changed), dialog);
}



static void
panel_preferences_dialog_finalize (GObject *object)
{
  PanelPreferencesDialog *dialog = PANEL_PREFERENCES_DIALOG (object);
  GtkWidget *itembar;

  /* unblock autohide */
  panel_application_windows_blocked (dialog->application, FALSE);

  /* free bindings list */
  g_slist_free (dialog->bindings);

  /* destroy possible pluggable dialog */
  if (dialog->socket_plug != NULL)
    gtk_widget_destroy (dialog->socket_plug);

  if (dialog->active != NULL)
    {
      if (dialog->items_changed_handler_id != 0)
        {
          /* disconnect changed signal */
          itembar = gtk_bin_get_child (GTK_BIN (dialog->active));
          g_signal_handler_disconnect (G_OBJECT (itembar),
                                       dialog->items_changed_handler_id);
        }

      if (dialog->bg_image_notify_handler_id != 0)
        {
          g_signal_handler_disconnect (G_OBJECT (dialog->active),
                                       dialog->bg_image_notify_handler_id);
        }
    }

  /* deselect all windows */
  if (!panel_item_dialog_visible ())
    panel_application_window_select (dialog->application, NULL);

  g_object_unref (G_OBJECT (dialog->application));
  g_object_unref (G_OBJECT (dialog->store));

  (*G_OBJECT_CLASS (panel_preferences_dialog_parent_class)->finalize) (object);
}



static void
panel_preferences_dialog_response (GtkWidget *window,
                                   gint response_id,
                                   PanelPreferencesDialog *dialog)
{
  panel_return_if_fail (GTK_IS_DIALOG (window));
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  if (G_UNLIKELY (response_id == 1))
    {
      panel_utils_show_help (GTK_WINDOW (window), "preferences", NULL);
    }
  else
    {
      gtk_widget_destroy (window);
      g_object_unref (G_OBJECT (dialog));
    }
}



static void
panel_preferences_dialog_bindings_unbind (PanelPreferencesDialog *dialog)
{
  g_clear_slist (&dialog->bindings, g_object_unref);

  /* disconnect image watch */
  if (dialog->bg_image_notify_handler_id != 0)
    {
      if (dialog->active != NULL)
        {
          g_signal_handler_disconnect (G_OBJECT (dialog->active),
                                       dialog->bg_image_notify_handler_id);
        }

      dialog->bg_image_notify_handler_id = 0;
    }

  if (dialog->active != NULL)
    g_signal_handlers_disconnect_by_func (dialog->active,
                                          panel_preferences_dialog_length_max_notified, dialog);
}



static void
panel_preferences_dialog_bindings_add (PanelPreferencesDialog *dialog,
                                       const gchar *property1,
                                       const gchar *property2,
                                       GBindingFlags flags)
{
  GBinding *binding;
  GObject *object;

  /* get the object from the builder */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), property1);
  panel_return_if_fail (G_IS_OBJECT (object));

  /* create the binding and prepend to the list */
  binding = g_object_bind_property (G_OBJECT (dialog->active), property1, object, property2,
                                    flags ? flags : G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  dialog->bindings = g_slist_prepend (dialog->bindings, binding);
}



static void
panel_preferences_dialog_bindings_update (PanelPreferencesDialog *dialog)
{
  GdkDisplay *display;
  gint n_monitors = 1;
  GObject *object;
  GObject *store;
  GBinding *binding;
  gchar *output_name = NULL;
  gboolean selector_visible = TRUE;
  GtkTreeIter iter;
  gboolean output_selected = FALSE;
  gint n = 0;
  gboolean span_monitors_sensitive = FALSE;
  gboolean icon_size_set;

  /* leave when there is no active panel */
  panel_return_if_fail (G_IS_OBJECT (dialog->active));
  if (dialog->active == NULL)
    return;

  /* hook up the bindings */
  panel_preferences_dialog_bindings_add (dialog, "mode", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "span-monitors", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "position-locked", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "autohide-behavior", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "enable-struts", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "keep-below", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "size", "value", 0);
  panel_preferences_dialog_bindings_add (dialog, "nrows", "value", 0);
  panel_preferences_dialog_bindings_add (dialog, "length-adjust", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "enter-opacity", "value", 0);
  panel_preferences_dialog_bindings_add (dialog, "leave-opacity", "value", 0);
  panel_preferences_dialog_bindings_add (dialog, "composited", "sensitive", G_BINDING_SYNC_CREATE);
  panel_preferences_dialog_bindings_add (dialog, "background-style", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "background-rgba", "rgba", 0);
  panel_preferences_dialog_bindings_add (dialog, "icon-size", "value", 0);
  panel_preferences_dialog_bindings_add (dialog, "dark-mode", "active", 0);
  panel_preferences_dialog_bindings_add (dialog, "border-width", "value", 0);

  /* watch image changes from the panel */
  dialog->bg_image_notify_handler_id =
    g_signal_connect_swapped (G_OBJECT (dialog->active), "notify::background-image",
                              G_CALLBACK (panel_preferences_dialog_bg_image_notified), dialog);
  panel_preferences_dialog_bg_image_notified (dialog);

  /* manage panel length */
  panel_preferences_dialog_length_max_notified (dialog);
  g_signal_connect_object (dialog->active, "notify::length-max",
                           G_CALLBACK (panel_preferences_dialog_length_max_notified),
                           dialog, G_CONNECT_SWAPPED);
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "length");
  binding = g_object_bind_property_full (dialog->active, "length", object, "value",
                                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL,
                                         panel_preferences_dialog_length_transform_to,
                                         panel_preferences_dialog_length_transform_from,
                                         dialog, NULL);
  dialog->bindings = g_slist_prepend (dialog->bindings, binding);

  /* get run mode of the driver (multiple monitors or randr) */
  display = gtk_widget_get_display (GTK_WIDGET (dialog->active));
  n_monitors = gdk_display_get_n_monitors (display);

  /* update the output selector */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "output-name");
  panel_return_if_fail (GTK_IS_COMBO_BOX (object));

  g_signal_handler_block (G_OBJECT (object), dialog->output_changed_handler_id);

  store = gtk_builder_get_object (GTK_BUILDER (dialog), "output-store");
  panel_return_if_fail (GTK_IS_LIST_STORE (store));
  gtk_list_store_clear (GTK_LIST_STORE (store));

  g_object_get (G_OBJECT (dialog->active), "output-name", &output_name, NULL);

  if (n_monitors > 1 || !xfce_str_is_empty (output_name))
    {
      gtk_list_store_insert_with_values (GTK_LIST_STORE (store), &iter, n++,
                                         OUTPUT_NAME, "Automatic",
                                         OUTPUT_TITLE, _("Automatic"), -1);
      if (xfce_str_is_empty (output_name) || g_strcmp0 (output_name, "Automatic") == 0)
        {
          gtk_combo_box_set_active_iter (GTK_COMBO_BOX (object), &iter);
          output_selected = TRUE;
          span_monitors_sensitive = TRUE;
        }
      gtk_list_store_insert_with_values (GTK_LIST_STORE (store), &iter, n++,
                                         OUTPUT_NAME, "Primary",
                                         OUTPUT_TITLE, _("Primary"), -1);
      if (g_strcmp0 (output_name, "Primary") == 0)
        {
          gtk_combo_box_set_active_iter (GTK_COMBO_BOX (object), &iter);
          output_selected = TRUE;
          span_monitors_sensitive = FALSE;
        }

      if (n_monitors >= 1)
        {
          panel_utils_populate_output_list (GTK_LIST_STORE (store),
                                            GTK_COMBO_BOX (object),
                                            output_name,
                                            display,
                                            n_monitors,
                                            &output_selected,
                                            &iter,
                                            &n);
        }

      /* add the output from the config if still nothing has been selected */
      if (!output_selected && !xfce_str_is_empty (output_name))
        {
          gtk_list_store_insert_with_values (GTK_LIST_STORE (store), &iter, n++,
                                             OUTPUT_NAME, output_name,
                                             OUTPUT_TITLE, output_name, -1);
          gtk_combo_box_set_active_iter (GTK_COMBO_BOX (object), &iter);
        }
    }
  else
    {
      /* hide the selector */
      selector_visible = FALSE;
      span_monitors_sensitive = TRUE;
    }

  g_signal_handler_unblock (G_OBJECT (object), dialog->output_changed_handler_id);

  /* update visibility of the output selector */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "output-label");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_set (G_OBJECT (object), "visible", selector_visible, NULL);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "output-name");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_set (G_OBJECT (object), "visible", selector_visible, NULL);

  /* monitor spanning is only active when no output is selected */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "span-monitors");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  gtk_widget_set_sensitive (GTK_WIDGET (object), span_monitors_sensitive);
  g_object_set (G_OBJECT (object), "visible", n_monitors > 1 && WINDOWING_IS_X11 (), NULL);

  g_free (output_name);

  /* update sensitivity of "don't reserve space on borders" option */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "autohide-behavior");
  panel_return_if_fail (GTK_IS_COMBO_BOX (object));
  panel_preferences_dialog_autohide_changed (GTK_COMBO_BOX (object), dialog);

  /* update visibility of the "icon-size" widgets */
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-spinbutton");
  panel_return_if_fail (G_IS_OBJECT (object));

  if (gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (object)) == 0)
    icon_size_set = FALSE;
  else
    icon_size_set = TRUE;

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-revealer");
  panel_return_if_fail (G_IS_OBJECT (object));
  gtk_revealer_set_reveal_child (GTK_REVEALER (object), icon_size_set);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-switch");
  panel_return_if_fail (G_IS_OBJECT (object));
  gtk_switch_set_state (GTK_SWITCH (object), !icon_size_set);
}



static void
panel_preferences_dialog_output_changed (GtkComboBox *combobox,
                                         PanelPreferencesDialog *dialog)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  gchar *output_name = NULL;
  GObject *object;

  panel_return_if_fail (GTK_IS_COMBO_BOX (combobox));
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (PANEL_WINDOW (dialog->active));

  if (gtk_combo_box_get_active_iter (combobox, &iter))
    {
      model = gtk_combo_box_get_model (combobox);
      gtk_tree_model_get (model, &iter, OUTPUT_NAME, &output_name, -1);
      g_object_set (G_OBJECT (dialog->active), "output-name", output_name, NULL);

      /* monitor spanning does not work when an output is selected */
      object = gtk_builder_get_object (GTK_BUILDER (dialog), "span-monitors");
      panel_return_if_fail (GTK_IS_WIDGET (object));
      if (output_name == NULL || g_strcmp0 ("Automatic", output_name) == 0)
        gtk_widget_set_sensitive (GTK_WIDGET (object), TRUE);
      else
        gtk_widget_set_sensitive (GTK_WIDGET (object), FALSE);

      g_free (output_name);
    }
}



static void
panel_preferences_dialog_autohide_changed (GtkComboBox *combobox,
                                           PanelPreferencesDialog *dialog)
{
  GObject *struts_object, *below_object;

  panel_return_if_fail (GTK_IS_COMBO_BOX (combobox));
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (PANEL_WINDOW (dialog->active));

  struts_object = gtk_builder_get_object (GTK_BUILDER (dialog), "enable-struts");
  panel_return_if_fail (GTK_IS_WIDGET (struts_object));

  below_object = gtk_builder_get_object (GTK_BUILDER (dialog), "keep-below");
  panel_return_if_fail (GTK_IS_WIDGET (struts_object));

  /* make "don't reserve space on borders" sensitive only when autohide is disabled */
  if (gtk_combo_box_get_active (combobox) == 0)
    {
      gtk_widget_set_sensitive (GTK_WIDGET (struts_object), TRUE);
      gtk_widget_set_sensitive (GTK_WIDGET (below_object), TRUE);
    }
  else
    {
      gtk_widget_set_sensitive (GTK_WIDGET (struts_object), FALSE);
      gtk_widget_set_sensitive (GTK_WIDGET (below_object), FALSE);
    }
}



static void
panel_preferences_dialog_bg_style_changed (PanelPreferencesDialog *dialog)
{
  gint active;
  GObject *object;
  gboolean composited;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (PANEL_WINDOW (dialog->active));

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-style");
  panel_return_if_fail (GTK_IS_COMBO_BOX (object));
  active = gtk_combo_box_get_active (GTK_COMBO_BOX (object));

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-rgba");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_get (G_OBJECT (dialog->active), "composited", &composited, NULL);
  gtk_color_chooser_set_use_alpha (GTK_COLOR_CHOOSER (object), composited);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-rgba-label");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_set (G_OBJECT (object), "visible", active == 1, NULL);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-rgba");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_set (G_OBJECT (object), "visible", active == 1, NULL);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-image-label");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_set (G_OBJECT (object), "visible", active == 2, NULL);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "background-image");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  g_object_set (G_OBJECT (object), "visible", active == 2, NULL);
}



static void
panel_preferences_dialog_bg_image_file_set (GtkFileChooserButton *button,
                                            PanelPreferencesDialog *dialog)
{
  gchar *uri;

  panel_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (PANEL_IS_WINDOW (dialog->active));

  g_signal_handler_block (G_OBJECT (dialog->active), dialog->bg_image_notify_handler_id);

  uri = gtk_file_chooser_get_uri (GTK_FILE_CHOOSER (button));
  g_object_set (G_OBJECT (dialog->active), "background-image", uri, NULL);
  g_free (uri);

  g_signal_handler_unblock (G_OBJECT (dialog->active), dialog->bg_image_notify_handler_id);
}



static void
panel_preferences_dialog_bg_image_notified (PanelPreferencesDialog *dialog)
{
  gchar *uri;
  GObject *button;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (PANEL_IS_WINDOW (dialog->active));

  button = gtk_builder_get_object (GTK_BUILDER (dialog), "background-image");
  panel_return_if_fail (GTK_IS_FILE_CHOOSER_BUTTON (button));

  g_signal_handlers_block_by_func (
    G_OBJECT (button), G_CALLBACK (panel_preferences_dialog_bg_image_file_set), dialog);

  g_object_get (G_OBJECT (dialog->active), "background-image", &uri, NULL);
  gtk_file_chooser_set_uri (GTK_FILE_CHOOSER (button), uri != NULL ? uri : "");
  g_free (uri);

  g_signal_handlers_unblock_by_func (
    G_OBJECT (button), G_CALLBACK (panel_preferences_dialog_bg_image_file_set), dialog);
}



static void
panel_preferences_dialog_length_max_notified (PanelPreferencesDialog *dialog)
{
  GObject *adjust;
  gdouble length;
  guint length_max;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (PANEL_IS_WINDOW (dialog->active));

  adjust = gtk_builder_get_object (GTK_BUILDER (dialog), "length");
  panel_return_if_fail (GTK_IS_ADJUSTMENT (adjust));

  g_object_get (dialog->active, "length-max", &length_max, NULL);
  gtk_adjustment_set_upper (GTK_ADJUSTMENT (adjust), length_max);
  g_object_get (dialog->active, "length", &length, NULL);
  gtk_adjustment_set_value (GTK_ADJUSTMENT (adjust), length_max * length / 100);
}



static gboolean
panel_preferences_dialog_length_transform_to (GBinding *binding,
                                              const GValue *from_value,
                                              GValue *to_value,
                                              gpointer user_data)
{
  GObject *adjust;
  gdouble length, upper;

  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (user_data), FALSE);

  adjust = gtk_builder_get_object (user_data, "length");
  panel_return_val_if_fail (GTK_IS_ADJUSTMENT (adjust), FALSE);

  length = g_value_get_double (from_value);
  upper = gtk_adjustment_get_upper (GTK_ADJUSTMENT (adjust));
  g_value_set_double (to_value, rint (upper * length / 100));

  return TRUE;
}



static gboolean
panel_preferences_dialog_length_transform_from (GBinding *binding,
                                                const GValue *from_value,
                                                GValue *to_value,
                                                gpointer user_data)
{
  GObject *adjust;
  gdouble length, upper;

  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (user_data), FALSE);

  adjust = gtk_builder_get_object (user_data, "length");
  panel_return_val_if_fail (GTK_IS_ADJUSTMENT (adjust), FALSE);

  length = g_value_get_double (from_value);
  upper = gtk_adjustment_get_upper (GTK_ADJUSTMENT (adjust));
  g_value_set_double (to_value, 100 * length / upper);

  return TRUE;
}



static void
panel_preferences_dialog_panel_sensitive (PanelPreferencesDialog *dialog)
{
  GObject *object;
  gboolean locked = TRUE;
  GSList *windows;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  if (G_LIKELY (dialog->active != NULL))
    locked = panel_window_get_locked (dialog->active);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "panel-remove");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  windows = panel_application_get_windows (dialog->application);
  gtk_widget_set_sensitive (GTK_WIDGET (object), !locked && g_slist_length (windows) > 1);

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "panel-add");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  gtk_widget_set_sensitive (GTK_WIDGET (object), !panel_application_get_locked (dialog->application));

  object = gtk_builder_get_object (GTK_BUILDER (dialog), "notebook");
  panel_return_if_fail (GTK_IS_WIDGET (object));
  gtk_widget_set_sensitive (GTK_WIDGET (object), !locked);

  if (locked)
    {
      g_object_set (dialog->store, "list-flags", XFCE_ITEM_LIST_MODEL_NONE, NULL);
    }
  else
    {
      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);
    }
}



static void
panel_preferences_dialog_panel_combobox_changed (GtkComboBox *combobox,
                                                 PanelPreferencesDialog *dialog)
{
  gint panel_id;
  GtkWidget *itembar;
  GtkTreeModel *model;
  GtkTreeIter iter;

  panel_return_if_fail (GTK_IS_COMBO_BOX (combobox));
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  /* disconnect signal we used to monitor changes in the itembar */
  if (dialog->active != NULL && dialog->items_changed_handler_id != 0)
    {
      itembar = gtk_bin_get_child (GTK_BIN (dialog->active));
      g_signal_handler_disconnect (G_OBJECT (itembar), dialog->items_changed_handler_id);
    }

  /* remove all the active bindings */
  panel_preferences_dialog_bindings_unbind (dialog);

  /* set the selected window */
  if (gtk_combo_box_get_active_iter (combobox, &iter))
    {
      model = gtk_combo_box_get_model (combobox);
      gtk_tree_model_get (model, &iter, 0, &panel_id, -1);

      dialog->active = panel_application_get_window (dialog->application, panel_id);
    }
  else
    {
      dialog->active = NULL;
    }

  panel_application_window_select (dialog->application, dialog->active);

  if (G_LIKELY (dialog->active != NULL))
    {
      itembar = gtk_bin_get_child (GTK_BIN (dialog->active));
      dialog->items_changed_handler_id =
        g_signal_connect (G_OBJECT (itembar), "changed",
                          G_CALLBACK (panel_preferences_dialog_item_store_rebuild), dialog);

      /* rebind the dialog bindings */
      panel_preferences_dialog_bindings_update (dialog);

      /* update the items treeview */
      panel_preferences_dialog_item_store_rebuild (itembar, dialog);
    }

  panel_preferences_dialog_panel_sensitive (dialog);
}



static void
panel_preferences_dialog_monitors_changed (GdkDisplay *display,
                                           GdkMonitor *monitor,
                                           PanelPreferencesDialog *dialog)
{
  GObject *combobox = gtk_builder_get_object (GTK_BUILDER (dialog), "panel-combobox");
  panel_preferences_dialog_panel_combobox_changed (GTK_COMBO_BOX (combobox), dialog);
}



static gboolean
panel_preferences_dialog_panel_combobox_rebuild (PanelPreferencesDialog *dialog,
                                                 gint panel_id)
{
  GObject *store, *combo;
  gint i;
  GSList *windows, *li;
  gchar *name;
  gint id;
  GtkTreeIter iter;
  gboolean selected = FALSE;

  /* get the combo box and model */
  store = gtk_builder_get_object (GTK_BUILDER (dialog), "panel-store");
  panel_return_val_if_fail (GTK_IS_LIST_STORE (store), FALSE);
  combo = gtk_builder_get_object (GTK_BUILDER (dialog), "panel-combobox");
  panel_return_val_if_fail (GTK_IS_COMBO_BOX (combo), FALSE);

  /* block signal */
  g_signal_handlers_block_by_func (combo, panel_preferences_dialog_panel_combobox_changed, dialog);

  /* empty the combo box */
  gtk_list_store_clear (GTK_LIST_STORE (store));

  /* add new names */
  windows = panel_application_get_windows (dialog->application);
  for (li = windows, i = 0; li != NULL; li = li->next, i++)
    {
      /* I18N: panel combo box in the preferences dialog */
      id = panel_window_get_id (li->data);
      name = g_strdup_printf (_("Panel %d"), id);
      gtk_list_store_insert_with_values (GTK_LIST_STORE (store), &iter, i,
                                         0, id, 1, name, -1);
      g_free (name);

      if (id == panel_id)
        {
          /* select panel id */
          gtk_combo_box_set_active_iter (GTK_COMBO_BOX (combo), &iter);
          selected = TRUE;
        }
    }

  /* unblock signal */
  g_signal_handlers_unblock_by_func (combo, panel_preferences_dialog_panel_combobox_changed, dialog);

  if (selected)
    panel_preferences_dialog_panel_combobox_changed (GTK_COMBO_BOX (combo), dialog);

  panel_preferences_dialog_panel_sensitive (dialog);

  return selected;
}



static void
panel_preferences_dialog_panel_add (GtkWidget *widget,
                                    PanelPreferencesDialog *dialog)
{
  PanelWindow *window;
  gint panel_id;

  /* create new window */
  window = panel_application_new_window (dialog->application,
                                         gtk_widget_get_screen (widget), -1, TRUE);
  gtk_widget_show (GTK_WIDGET (window));

  /* rebuild the selector */
  panel_id = panel_window_get_id (window);
  panel_preferences_dialog_panel_combobox_rebuild (dialog, panel_id);
}



static void
panel_preferences_dialog_panel_remove (GtkWidget *widget,
                                       PanelPreferencesDialog *dialog)
{
  gint id;
  GtkWidget *toplevel;
  GSList *windows, *window;
  const gchar *text = _("The panel and plugin configurations will be permanently removed");
  const gchar *label = _("_Remove");

  /* leave if the window is locked */
  if (panel_window_get_locked (dialog->active))
    return;

  toplevel = gtk_widget_get_toplevel (widget);
  if (xfce_dialog_confirm (GTK_WINDOW (toplevel), "list-remove", label, text,
                           _("Are you sure you want to remove panel %d?"),
                           panel_window_get_id (dialog->active)))
    {
      /* release the bindings */
      panel_preferences_dialog_bindings_unbind (dialog);

      /* get new panel id to be selected */
      windows = panel_application_get_windows (dialog->application);
      window = g_slist_find (windows, dialog->active);
      if (window->next != NULL)
        id = panel_window_get_id (window->next->data);
      else
        id = panel_window_get_id (windows->data);

      /* remove the panel, plugins and configuration */
      panel_application_remove_window (dialog->application,
                                       dialog->active);
      dialog->active = NULL;

      /* rebuild the selector */
      panel_preferences_dialog_panel_combobox_rebuild (dialog, id);
    }
}



static gboolean
panel_preferences_dialog_icon_size_state_set (GtkSwitch *widget,
                                              gboolean state,
                                              PanelPreferencesDialog *dialog)
{
  GObject *spinbutton, *revealer;

  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog), FALSE);
  spinbutton = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-spinbutton");
  panel_return_val_if_fail (G_IS_OBJECT (spinbutton), FALSE);
  revealer = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-revealer");
  panel_return_val_if_fail (G_IS_OBJECT (revealer), FALSE);

  /* we set the icon-size to 0 for auto-handling */
  if (state)
    {
      gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinbutton), 0.0);
    }
  else
    {
      /* if the setting is initially enabled we start at the low default of 16px */
      if (gtk_spin_button_get_value (GTK_SPIN_BUTTON (spinbutton)) == 0)
        gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinbutton), 16.0);
    }

  gtk_revealer_set_reveal_child (GTK_REVEALER (revealer), !state);
  gtk_switch_set_state (GTK_SWITCH (widget), state);

  return TRUE;
}



static void
panel_preferences_dialog_icon_size_changed (GtkSpinButton *button,
                                            PanelPreferencesDialog *dialog)
{
  GObject *object;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  object = gtk_builder_get_object (GTK_BUILDER (dialog), "icon-size-switch");
  panel_return_if_fail (G_IS_OBJECT (object));

  /* 0 means the setting is disabled (icon-size-switch's state == true) so the lower value
     of the spinbutton has to be 0. however, we want to set 16px as the reasonable lower limit for icons. */
  if (gtk_spin_button_get_value_as_int (button) < 16
      && gtk_switch_get_state (GTK_SWITCH (object)))
    gtk_spin_button_set_value (button, 16.0);
}



static void
panel_preferences_dialog_compositing_clicked (GtkButton *button, gpointer user_data)
{
  gchar *command;
  GAppInfo *app_info = NULL;
  GError *error = NULL;

  command = g_find_program_in_path ("xfwm4-tweaks-settings");
  if (command != NULL)
    app_info = g_app_info_create_from_commandline (command, "Window Manager Tweaks", G_APP_INFO_CREATE_NONE, &error);
  g_free (command);

  if (error != NULL)
    {
      g_warning ("Could not find application %s", error->message);
      g_error_free (error);
      return;
    }

  if (app_info != NULL && !g_app_info_launch (app_info, NULL, NULL, &error))
    {
      g_warning ("Could not launch the application %s", error->message);
      g_error_free (error);
    }
}



static void
panel_preferences_dialog_panel_switch (GtkWidget *widget, PanelPreferencesDialog *dialog)
{
  gchar *path_old = g_find_program_in_path ("xfpanel-switch");
  gchar *path_new = g_find_program_in_path ("xfce4-panel-profiles");
  if (path_old == NULL && path_new == NULL)
    return;

  /* first try the new name of the executable, then the old */
  if (path_new)
    g_spawn_command_line_async (path_new, NULL);
  else if (path_old)
    g_spawn_command_line_async (path_old, NULL);

  g_free (path_old);
  g_free (path_new);
}



static XfcePanelPluginProvider *
panel_preferences_dialog_item_get_selected (PanelPreferencesDialog *dialog,
                                            GtkTreeIter *return_iter,
                                            GList **return_list)
{
  XfcePanelPluginProvider *provider = NULL;
  GtkTreeModel *model;
  GtkTreeIter iter;
  GtkTreeSelection *selection;

  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog), NULL);

  /* get the treeview selection */
  panel_return_val_if_fail (GTK_IS_WIDGET (dialog->tree_view), NULL);
  selection = gtk_tree_view_get_selection (dialog->tree_view);

  /* get the selection items */
  if (gtk_tree_selection_count_selected_rows (selection) > 0)
    {
      /* get the first selected provider */
      GList *paths = gtk_tree_selection_get_selected_rows (selection, &model);
      gtk_tree_model_get_iter (model, &iter, paths->data);
      gtk_tree_model_get (model, &iter, ITEM_COLUMN_PROVIDER, &provider, -1);
      if (return_iter != NULL)
        *return_iter = iter;

      /* get the list of selected providers */
      if (return_list != NULL)
        {
          *return_list = NULL;
          *return_list = g_list_prepend (*return_list, provider);
          for (GList *lp = paths->next; lp != NULL; lp = lp->next)
            {
              gtk_tree_model_get_iter (model, &iter, lp->data);
              gtk_tree_model_get (model, &iter, ITEM_COLUMN_PROVIDER, &provider, -1);
              *return_list = g_list_prepend (*return_list, provider);
            }

          *return_list = g_list_reverse (*return_list);
        }

      g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free);
    }

  return provider;
}



static gboolean
get_launcher_data (XfcePanelPluginProvider *provider,
                   PanelModule *module,
                   gchar **display_name,
                   GIcon **icon)
{
  XfconfChannel *channel = xfconf_channel_get (XFCE_PANEL_CHANNEL_NAME);
  gchar *prop = g_strdup_printf (PLUGINS_PROPERTY_BASE "/items",
                                 xfce_panel_plugin_provider_get_unique_id (provider));
  gboolean success = FALSE;

  if (xfconf_channel_has_property (channel, prop))
    {
      /* xfconf_channel_get_string_list() should not return NULL if the property exists
       * according to its documentation, but it does return NULL instead of an empty
       * array, at least until 4.20.0 */
      gchar **desktop_files = xfconf_channel_get_string_list (channel, prop);
      if (desktop_files != NULL)
        {
          if (desktop_files[0] != NULL)
            {
              gchar *dirname = g_strdup_printf (PANEL_PLUGIN_RELATIVE_PATH G_DIR_SEPARATOR_S "%s-%d",
                                                xfce_panel_plugin_provider_get_name (provider),
                                                xfce_panel_plugin_provider_get_unique_id (provider));
              gchar *filename = g_build_filename (dirname, desktop_files[0], NULL);
              gchar *path = xfce_resource_save_location (XFCE_RESOURCE_CONFIG, filename, FALSE);
              GKeyFile *keyfile = g_key_file_new ();

              if (g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, NULL))
                {
                  *display_name = g_key_file_get_locale_string (keyfile, G_KEY_FILE_DESKTOP_GROUP,
                                                                G_KEY_FILE_DESKTOP_KEY_NAME, NULL, NULL);
                  if (*display_name != NULL)
                    {
                      gchar *icon_name = g_key_file_get_string (keyfile, G_KEY_FILE_DESKTOP_GROUP,
                                                                G_KEY_FILE_DESKTOP_KEY_ICON, NULL);
                      if (icon_name != NULL)
                        {
                          if (g_path_is_absolute (icon_name))
                            {
                              GFile *file = g_file_new_for_path (icon_name);
                              *icon = g_file_icon_new (file);
                              g_object_unref (file);
                            }
                          else
                            *icon = g_themed_icon_new (icon_name);

                          g_free (icon_name);
                        }
                      else
                        *icon = g_themed_icon_new (panel_module_get_icon_name (module));

                      success = TRUE;
                    }
                }

              g_key_file_free (keyfile);
              g_free (path);
              g_free (filename);
              g_free (dirname);
            }

          g_strfreev (desktop_files);
        }
    }

  g_free (prop);

  return success;
}



static void
panel_preferences_dialog_item_store_rebuild (GtkWidget *itembar,
                                             PanelPreferencesDialog *dialog)
{
  GList *selected = NULL, *items, *li;
  guint i;
  PanelModule *module;
  gchar *tooltip, *display_name, *_display_name;
  GIcon *icon;
  GtkTreeIter iter;
  GtkTreeSelection *selection;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));
  panel_return_if_fail (XFCE_IS_ITEM_LIST_STORE (dialog->store));
  panel_return_if_fail (PANEL_IS_ITEMBAR (itembar));

  /* memorize selected item */
  panel_preferences_dialog_item_get_selected (dialog, NULL, &selected);
  panel_return_if_fail (GTK_IS_WIDGET (dialog->tree_view));
  selection = gtk_tree_view_get_selection (dialog->tree_view);

  g_signal_handlers_block_by_func (dialog->store, G_CALLBACK (panel_preferences_dialog_before_item_remove), dialog);
  xfce_item_list_store_clear (dialog->store);
  g_signal_handlers_unblock_by_func (dialog->store, G_CALLBACK (panel_preferences_dialog_before_item_remove), dialog);

  /* add items to the store */
  items = gtk_container_get_children (GTK_CONTAINER (itembar));
  for (li = items, i = 0; li != NULL; li = li->next, i++)
    {
      /* get the panel module from the plugin */
      module = panel_module_get_from_plugin_provider (li->data);

      /* treat launchers as a special case */
      if (g_strcmp0 (xfce_panel_plugin_provider_get_name (li->data), "launcher") != 0
          || !get_launcher_data (li->data, module, &_display_name, &icon))
        {
          _display_name = g_strdup (panel_module_get_display_name (module));
          icon = g_themed_icon_new (panel_module_get_icon_name (module));
        }

      if (PANEL_IS_PLUGIN_EXTERNAL (li->data))
        {
          /* I18N: append (external) in the preferences dialog if the plugin
           * runs external */
          display_name = g_strdup_printf (_("%s <span color=\"grey\" size=\"small\">(external)</span>"),
                                          _display_name);

          /* I18N: tooltip in preferences dialog when hovering an item in the list
           * for external plugins */
          tooltip = g_strdup_printf (_("Internal name: %s-%d\n"
                                       "PID: %d"),
                                     xfce_panel_plugin_provider_get_name (li->data),
                                     xfce_panel_plugin_provider_get_unique_id (li->data),
                                     panel_plugin_external_get_pid (PANEL_PLUGIN_EXTERNAL (li->data)));
        }
      else
        {
          display_name = g_strdup (_display_name);

          /* I18N: tooltip in preferences dialog when hovering an item in the list
           * for internal plugins */
          tooltip = g_strdup_printf (_("Internal name: %s-%d"),
                                     xfce_panel_plugin_provider_get_name (li->data),
                                     xfce_panel_plugin_provider_get_unique_id (li->data));
        }

      gint index = xfce_item_list_store_insert_with_values (dialog->store, i,
                                                            XFCE_ITEM_LIST_MODEL_COLUMN_ICON,
                                                            icon,
                                                            XFCE_ITEM_LIST_MODEL_COLUMN_NAME,
                                                            display_name,
                                                            XFCE_ITEM_LIST_MODEL_COLUMN_TOOLTIP,
                                                            tooltip,
                                                            XFCE_ITEM_LIST_MODEL_COLUMN_EDITABLE,
                                                            xfce_panel_plugin_provider_get_show_configure (li->data),
                                                            ITEM_COLUMN_PROVIDER,
                                                            li->data,
                                                            ITEM_COLUMN_ABOUT,
                                                            xfce_panel_plugin_provider_get_show_about (li->data),
                                                            -1);
      xfce_item_list_model_set_index (XFCE_ITEM_LIST_MODEL (dialog->store), &iter, index);

      /* reconstruct selection */
      if (g_list_find (selected, li->data))
        gtk_tree_selection_select_iter (selection, &iter);

      g_free (tooltip);
      g_free (_display_name);
      g_free (display_name);
      g_object_unref (icon);
    }

  g_list_free (items);
  g_list_free (selected);

  /* scroll to selection */
  selected = gtk_tree_selection_get_selected_rows (selection, NULL);
  if (selected != NULL)
    {
      gtk_tree_view_scroll_to_cell (dialog->tree_view, selected->data, NULL, TRUE, 0, 0);
      g_list_free (selected);
    }
}



static void
panel_preferences_dialog_before_item_move (XfceItemListStore *store,
                                           gint source_index,
                                           gint dest_index,
                                           PanelPreferencesDialog *dialog)
{
  GtkTreeIter iter;
  XfcePanelPluginProvider *provider;
  GtkWidget *itembar;
  gint position;
  GtkTreePath *path;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  provider = panel_preferences_dialog_item_get_selected (dialog, &iter, NULL);
  if (G_LIKELY (provider != NULL))
    {
      /* get the provider position on the panel */
      itembar = gtk_bin_get_child (GTK_BIN (dialog->active));
      position = panel_itembar_get_child_index (PANEL_ITEMBAR (itembar),
                                                GTK_WIDGET (provider));
      path = gtk_tree_model_get_path (GTK_TREE_MODEL (dialog->store), &iter);

      if (G_LIKELY (position != -1))
        {
          /* block the changed signal */
          g_signal_handler_block (G_OBJECT (itembar), dialog->items_changed_handler_id);

          /* move the item on the panel */
          panel_itembar_reorder_child (PANEL_ITEMBAR (itembar),
                                       GTK_WIDGET (provider),
                                       dest_index);

          /* save the new ids */
          panel_application_save_window (dialog->application,
                                         dialog->active,
                                         SAVE_PLUGIN_IDS);

          /* unblock the changed signal */
          g_signal_handler_unblock (G_OBJECT (itembar), dialog->items_changed_handler_id);
        }
      gtk_tree_path_free (path);
    }
}



static gboolean
panel_preferences_dialog_items_remove (GtkWidget *item_view,
                                       const gint *items,
                                       gint n_items,
                                       PanelPreferencesDialog *dialog)
{
  GList *selected = NULL;
  GtkTreeIter iter;

  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog), TRUE);

  panel_preferences_dialog_item_get_selected (dialog, &iter, &selected);
  if (G_LIKELY (selected != NULL))
    {
      const gchar *label = _("Remove");
      gchar *primary;
      const gchar *secondary;
      guint n_selected = g_list_length (selected);
      if (n_selected == 1)
        {
          PanelModule *module = panel_module_get_from_plugin_provider (selected->data);
          primary = g_strdup_printf (_("Are you sure that you want to remove \"%s\"?"),
                                     panel_module_get_display_name (module));
          secondary = _("If you remove the item from the panel, it is permanently lost.");
        }
      else
        {
          primary = g_strdup_printf (_("Are you sure that you want to remove these %d items?"),
                                     n_selected);
          secondary = _("If you remove them from the panel, they are permanently lost.");
        }

      /* create question dialog (similar code is also in xfce-panel-plugin.c) */
      gboolean confirmed = xfce_dialog_confirm (GTK_WINDOW (gtk_widget_get_toplevel (item_view)), "list-remove", label, secondary, "%s", primary);
      g_list_free (selected);
      g_free (primary);

      /* continue propagation? */
      return !confirmed;
    }

  /* stop propagation */
  return TRUE;
}


static void
panel_preferences_dialog_before_item_remove (XfceItemListStore *store,
                                             gint index,
                                             PanelPreferencesDialog *dialog)
{
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  GtkWidget *itembar = gtk_bin_get_child (GTK_BIN (dialog->active));
  g_signal_handler_block (G_OBJECT (itembar), dialog->items_changed_handler_id);
  GValue provider_val = G_VALUE_INIT;
  xfce_item_list_model_get_item_value (XFCE_ITEM_LIST_MODEL (store), index, ITEM_COLUMN_PROVIDER, &provider_val);
  xfce_panel_plugin_provider_emit_signal (g_value_get_object (&provider_val), PROVIDER_SIGNAL_REMOVE_PLUGIN);
  g_value_reset (&provider_val);
  g_signal_handler_unblock (G_OBJECT (itembar), dialog->items_changed_handler_id);
}



static gboolean
panel_preferences_dialog_item_add (XfceItemListView *item_view,
                                   PanelPreferencesDialog *dialog)
{
  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog), TRUE);

  panel_item_dialog_show (dialog->active);

  /* stop propagation */
  return TRUE;
}



static gboolean
panel_preferences_dialog_item_properties (XfceItemListView *item_view,
                                          gint index,
                                          PanelPreferencesDialog *dialog)
{
  XfcePanelPluginProvider *provider;

  panel_return_val_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog), TRUE);

  provider = panel_preferences_dialog_item_get_selected (dialog, NULL, NULL);
  if (G_LIKELY (provider != NULL))
    xfce_panel_plugin_provider_show_configure (provider);

  /* don't stop propagation */
  return FALSE;
}



static void
panel_preferences_dialog_item_about (GAction *about,
                                     GVariant *parameter,
                                     PanelPreferencesDialog *dialog)
{
  XfcePanelPluginProvider *provider;

  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  provider = panel_preferences_dialog_item_get_selected (dialog, NULL, NULL);
  if (G_LIKELY (provider != NULL))
    xfce_panel_plugin_provider_show_about (provider);
}



static void
panel_preferences_dialog_item_selection_changed (GtkTreeSelection *selection,
                                                 PanelPreferencesDialog *dialog)
{
  panel_return_if_fail (PANEL_IS_PREFERENCES_DIALOG (dialog));

  gint *sel_items = NULL;
  gint n_sel_items = xfce_item_list_view_get_selected_items (dialog->item_view, &sel_items);
  XfceItemListModel *model = xfce_item_list_view_get_model (dialog->item_view);

  /* update sensitivity of actions */
  g_simple_action_set_enabled (dialog->about, n_sel_items == 1 && xfce_item_list_model_test (model, sel_items[0], ITEM_COLUMN_ABOUT));
  g_free (sel_items);
}



#ifdef ENABLE_X11
static void
panel_preferences_dialog_plug_deleted (GtkWidget *plug)
{
  g_signal_handlers_disconnect_by_func (G_OBJECT (plug),
                                        G_CALLBACK (panel_preferences_dialog_plug_deleted), NULL);

  g_object_unref (G_OBJECT (dialog_singleton));
}
#endif



static void
panel_preferences_dialog_show_internal (PanelWindow *active,
                                        Window socket_window)
{
  gint panel_id = 0;
  GObject *window, *widget;
  GdkScreen *screen;
  GSList *windows;
#ifdef ENABLE_X11
  GtkWidget *plug;
  GObject *plug_child;
  GtkWidget *content_area;
#endif

  panel_return_if_fail (active == NULL || PANEL_IS_WINDOW (active));

  /* check if not the entire application is locked */
  if (panel_dialogs_kiosk_warning ())
    return;

  if (dialog_singleton == NULL)
    {
      /* create new dialog singleton */
      dialog_singleton = g_object_new (PANEL_TYPE_PREFERENCES_DIALOG, NULL);
      g_object_add_weak_pointer (G_OBJECT (dialog_singleton), (gpointer) &dialog_singleton);
    }

  if (active == NULL)
    {
      /* select first window */
      windows = panel_application_get_windows (dialog_singleton->application);
      if (windows != NULL)
        active = g_slist_nth_data (windows, 0);
    }

  /* select the active window in the dialog */
  widget = gtk_builder_get_object (GTK_BUILDER (dialog_singleton), "panel-combobox");
  panel_return_if_fail (GTK_IS_WIDGET (widget));
  panel_id = panel_window_get_id (active);
  if (!panel_preferences_dialog_panel_combobox_rebuild (dialog_singleton, panel_id))
    gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);

  /* select item in the dialog if needed */
  if (WINDOWING_IS_WAYLAND ())
    {
      const gchar *item = g_object_get_data (G_OBJECT (active), "prefs-dialog-item");
      if (item != NULL)
        {
          GtkTreePath *path = gtk_tree_path_new_from_string (item);
          widget = gtk_builder_get_object (GTK_BUILDER (dialog_singleton), "notebook");
          panel_return_if_fail (GTK_IS_WIDGET (widget));
          gtk_notebook_set_current_page (GTK_NOTEBOOK (widget), 2);
          panel_return_if_fail (GTK_IS_WIDGET (dialog_singleton->tree_view));
          gtk_tree_view_set_cursor (dialog_singleton->tree_view, path, NULL, FALSE);
          gtk_tree_path_free (path);
          g_object_set_data (G_OBJECT (active), "prefs-dialog-item", NULL);
        }
    }

  window = gtk_builder_get_object (GTK_BUILDER (dialog_singleton), "dialog");
  panel_return_if_fail (GTK_IS_WIDGET (window));
#ifdef ENABLE_X11
  plug_child = gtk_builder_get_object (GTK_BUILDER (dialog_singleton), "plug-child");
  panel_return_if_fail (GTK_IS_WIDGET (plug_child));

  /* check if we need to remove the window from the plug */
  if (dialog_singleton->socket_plug != NULL)
    {
      panel_return_if_fail (GTK_IS_PLUG (dialog_singleton->socket_plug));

      /* move the vbox to the dialog */
      content_area = gtk_dialog_get_content_area (GTK_DIALOG (window));
      xfce_widget_reparent (GTK_WIDGET (plug_child), content_area);
      gtk_widget_show (GTK_WIDGET (plug_child));

      /* destroy the plug */
      plug = g_steal_pointer (&dialog_singleton->socket_plug);
      g_signal_handlers_disconnect_by_func (G_OBJECT (plug),
                                            G_CALLBACK (panel_preferences_dialog_plug_deleted), NULL);
      gtk_widget_destroy (plug);
    }
#endif

  if (socket_window == 0)
    {
      /* show the dialog on the same screen as the panel */
      if (G_LIKELY (active != NULL))
        screen = gtk_widget_get_screen (GTK_WIDGET (active));
      else
        screen = gdk_screen_get_default ();
      gtk_window_set_screen (GTK_WINDOW (window), screen);

      gtk_window_present (GTK_WINDOW (window));
      panel_application_take_dialog (dialog_singleton->application, GTK_WINDOW (window));
    }
#ifdef ENABLE_X11
  else
    {
      /* hide window */
      gtk_widget_hide (GTK_WIDGET (window));

      /* create a new plug */
      plug = gtk_plug_new (socket_window);
      g_signal_connect (G_OBJECT (plug), "delete-event",
                        G_CALLBACK (panel_preferences_dialog_plug_deleted), NULL);
      dialog_singleton->socket_plug = plug;
      gtk_widget_show (plug);

      /* move the vbox in the plug */
      xfce_widget_reparent (GTK_WIDGET (plug_child), plug);
      gtk_widget_show (GTK_WIDGET (plug_child));
    }
#endif
}



void
panel_preferences_dialog_show (PanelWindow *active)
{
  panel_return_if_fail (active == NULL || PANEL_IS_WINDOW (active));
  panel_preferences_dialog_show_internal (active, 0);
}



void
panel_preferences_dialog_show_from_id (gint panel_id,
                                       Window socket_window)
{
  PanelApplication *application;
  PanelWindow *window;

  application = panel_application_get ();
  window = panel_application_get_window (application, panel_id);
  panel_preferences_dialog_show_internal (window, socket_window);
  g_object_unref (G_OBJECT (application));
}



gboolean
panel_preferences_dialog_visible (void)
{
  return !!(dialog_singleton != NULL);
}
