/*
 * glade-named-icon-chooser-widget.c - Named icon chooser widget
 *
 * Copyright (C) 2007 Vincent Geddes
 *
 * Author:  Vincent Geddes <vgeddes@gnome.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
 * MERCHANNAMED_ICON_CHOOSERILITY 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 <config.h>

#include "glade-named-icon-chooser-dialog.h"
#include "icon-naming-spec.c"

#include <gtk/gtk.h>
#include <glib/gi18n-lib.h>
#include <string.h>
#include <errno.h>


#define DEFAULT_SETTING_LIST_STANDARD_ONLY   TRUE

enum
{
  CONTEXTS_ID_COLUMN,
  CONTEXTS_NAME_COLUMN,
  CONTEXTS_TITLE_COLUMN,

  CONTEXTS_N_COLUMS
};

enum
{
  ICONS_CONTEXT_COLUMN,
  ICONS_STANDARD_COLUMN,
  ICONS_NAME_COLUMN,

  ICONS_N_COLUMNS
};

enum
{
  GLADE_NAMED_ICON
};

enum
{
  ICON_ACTIVATED,
  SELECTION_CHANGED,

  LAST_SIGNAL
};

struct _GladeNamedIconChooserDialogPrivate
{
  GtkWidget *icons_view;
  GtkTreeModel *filter_model;   /* filtering model  */
  GtkListStore *icons_store;    /* data store       */
  GtkTreeSelection *selection;

  GtkWidget *contexts_view;
  GtkListStore *contexts_store;

  GtkWidget *entry;
  GtkEntryCompletion *entry_completion;

  GtkWidget *button;            /* list-standard-only checkbutton */

  gint context_id;              /* current icon name context for icon filtering */

  gchar *pending_select_name;   /* an icon name for a pending treeview selection.
                                 * can only select name after model is loaded
                                 * and the widget is mapped */

  GtkIconTheme *icon_theme;     /* the current icon theme */
  guint load_id;                /* id of the idle function for loading data into model */

  gboolean settings_list_standard;      /* whether to list standard icon names only */

  GtkWidget *last_focus_widget;

  gboolean icons_loaded;        /* whether the icons have been loaded into the model */
};

static GHashTable *standard_icon_quarks = NULL;

static guint dialog_signals[LAST_SIGNAL] = { 0, };

gchar *
glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *dialog);

void
glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *dialog,
                                               const gchar                 *icon_name);

gboolean
glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *dialog,
                                             const gchar                 *context);

gchar *
glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *dialog);

static gboolean should_respond (GladeNamedIconChooserDialog *dialog);

static void filter_icons_model (GladeNamedIconChooserDialog *dialog);

static gboolean scan_for_name_func (GtkTreeModel *model,
                                    GtkTreePath  *path,
                                    GtkTreeIter  *iter,
                                    gpointer      data);

static gboolean scan_for_context_func (GtkTreeModel *model,
                                       GtkTreePath  *path,
                                       GtkTreeIter  *iter,
                                       gpointer      data);

static void settings_load (GladeNamedIconChooserDialog *dialog);

static void settings_save (GladeNamedIconChooserDialog *dialog);


G_DEFINE_TYPE_WITH_PRIVATE (GladeNamedIconChooserDialog,
                            glade_named_icon_chooser_dialog,
                            GTK_TYPE_DIALOG);


static void
entry_set_name (GladeNamedIconChooserDialog *dialog, const gchar *name)
{
  /* Must disable completion before setting text, in order to avoid
   * spurious warnings (possible GTK+ bug).
   */
  gtk_entry_set_completion (GTK_ENTRY (dialog->priv->entry), NULL);

  gtk_entry_set_text (GTK_ENTRY (dialog->priv->entry), name);

  gtk_entry_set_completion (GTK_ENTRY (dialog->priv->entry),
                            dialog->priv->entry_completion);
}

static GtkIconTheme *
get_icon_theme_for_widget (GtkWidget *widget)
{
  if (gtk_widget_has_screen (widget))
    return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget));

  return gtk_icon_theme_get_default ();
}

/* validates name according to the icon naming spec (en_US.US_ASCII [a-z1-9_-.])  */
static gboolean
is_well_formed (const gchar * name)
{
  gchar *c = (gchar *) name;
  for (; *c; c++)
    {
      if (g_ascii_isalnum (*c))
        {
          if (g_ascii_isalpha (*c) && !g_ascii_islower (*c))
            return FALSE;
        }
      else if (*c != '_' && *c != '-' && *c != '.')
        {
          return FALSE;
        }
    }
  return TRUE;
}

static void
check_entry_text (GladeNamedIconChooserDialog *dialog,
                  gchar                      **name_ret,
                  gboolean                    *is_wellformed_ret,
                  gboolean                    *is_empty_ret)
{
  if (strlen (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry))) == 0)
    {
      *name_ret = NULL;
      *is_wellformed_ret = TRUE;
      *is_empty_ret = TRUE;

      return;
    }

  *is_empty_ret = FALSE;

  *is_wellformed_ret =
      is_well_formed (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry)));

  if (*is_wellformed_ret)
    *name_ret = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry)));
  else
    *name_ret = NULL;
}

static void
changed_text_handler (GtkEditable                 *editable,
                      GladeNamedIconChooserDialog *dialog)
{
  g_signal_emit_by_name (dialog, "selection-changed", NULL);
}

/* ensure that only valid text can be inserted into entry */
static void
insert_text_handler (GtkEditable                 *editable,
                     const gchar                 *text,
                     gint                         length,
                     gint                        *position,
                     GladeNamedIconChooserDialog *dialog)
{
  if (is_well_formed (text))
    {

      g_signal_handlers_block_by_func (editable, (gpointer) insert_text_handler,
                                       dialog);

      gtk_editable_insert_text (editable, text, length, position);

      g_signal_handlers_unblock_by_func (editable,
                                         (gpointer) insert_text_handler,
                                         dialog);

    }
  else
    {
      gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (dialog)));
    }

  g_signal_stop_emission_by_name (editable, "insert-text");
}

typedef struct
{
  gchar *name;                  /* the name of the icon or context */

  guint found:1;                /* whether an item matching `name' was found */
  guint do_select:1;            /* select the matched row */
  guint do_cursor:1;            /* put cursor at the matched row */
  guint do_activate:1;          /* activate the matched row */

  GladeNamedIconChooserDialog *dialog;
} ForEachFuncData;

void
glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *dialog, 
                                               const gchar                 *name)
{
  ForEachFuncData *data;
  gboolean located_in_theme;

  g_return_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog));
  g_return_if_fail (gtk_widget_has_screen (GTK_WIDGET (dialog)));

  if (name == NULL)
    {
      gtk_tree_selection_unselect_all (dialog->priv->selection);
      entry_set_name (dialog, "");
      return;
    }

  located_in_theme =
      gtk_icon_theme_has_icon (get_icon_theme_for_widget (GTK_WIDGET (dialog)),
                               name);

  if (located_in_theme)
    {

      if (dialog->priv->icons_loaded && dialog->priv->filter_model)
        {

          data = g_slice_new0 (ForEachFuncData);
          data->name = g_strdup (name);
          data->found = FALSE;
          data->do_activate = FALSE;
          data->do_select = TRUE;
          data->do_cursor = TRUE;
          data->dialog = dialog;

          gtk_tree_model_foreach (dialog->priv->filter_model,
                                  scan_for_name_func, data);

          g_free (data->name);
          g_slice_free (ForEachFuncData, data);

        }
      else
        {
          dialog->priv->pending_select_name = g_strdup (name);
        }

      /* selecting a treeview row will set the entry text,
       * but we must have this here in case the row has been filtered out
       */
      entry_set_name (dialog, name);

    }
  else if (is_well_formed (name))
    {

      gtk_tree_selection_unselect_all (dialog->priv->selection);

      entry_set_name (dialog, name);
    }
  else
    {
      g_warning ("invalid icon name: '%s' is not well formed", name);
    }
}

gboolean
glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *dialog,
                                             const gchar                 *name)
{
  ForEachFuncData *data;

  g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), FALSE);

  data = g_slice_new0 (ForEachFuncData);

  if (name)
    data->name = g_strdup (name);
  else
    data->name = g_strdup ("All Contexts");

  data->found = FALSE;
  data->do_select = TRUE;
  data->do_activate = FALSE;
  data->do_cursor = FALSE;
  data->dialog = dialog;

  gtk_tree_model_foreach (GTK_TREE_MODEL (dialog->priv->contexts_store),
                          (GtkTreeModelForeachFunc) scan_for_context_func,
                          data);

  g_free (data->name);
  g_slice_free (ForEachFuncData, data);

  return TRUE;
}

gchar *
glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *dialog)
{
  GtkTreeSelection *sel;
  GtkTreeIter iter;
  gchar *context_name;

  g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), NULL);

  sel =
      gtk_tree_view_get_selection (GTK_TREE_VIEW (dialog->priv->contexts_view));

  if (gtk_tree_selection_get_selected (sel, NULL, &iter))
    {

      gtk_tree_model_get (GTK_TREE_MODEL (dialog->priv->contexts_store), &iter,
                          CONTEXTS_NAME_COLUMN, &context_name, -1);

      /* if context_name is NULL, then it is the 'all categories' special context */
      return context_name;

    }
  else
    {
      return NULL;
    }
}

static gchar *
get_icon_name_from_selection (GladeNamedIconChooserDialog *dialog)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  gchar *name;

  if (!gtk_tree_selection_get_selected (dialog->priv->selection, &model, &iter))
    return NULL;

  gtk_tree_model_get (model, &iter, ICONS_NAME_COLUMN, &name, -1);

  return name;
}

gchar *
glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *dialog)
{
  GtkWidget *current_focus;
  gchar *name;

  g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), NULL);

  current_focus = gtk_window_get_focus (GTK_WINDOW (dialog));

  if (current_focus == dialog->priv->icons_view)
    {

    view:
      name = get_icon_name_from_selection (dialog);

      if (name == NULL)
        goto entry;

    }
  else if (current_focus == dialog->priv->entry)
    {
      gboolean is_wellformed, is_empty;
    entry:
      check_entry_text (dialog, &name, &is_wellformed, &is_empty);

      if (!is_wellformed || is_empty)
        return NULL;

    }
  else if (dialog->priv->last_focus_widget == dialog->priv->icons_view)
    {
      goto view;
    }
  else if (dialog->priv->last_focus_widget == dialog->priv->entry)
    {
      goto entry;
    }
  else
    {
      goto view;
    }

  return name;
}

static void
set_busy_cursor (GladeNamedIconChooserDialog *dialog, gboolean busy)
{
  GdkDisplay *display;
  GdkCursor *cursor;

  if (!gtk_widget_get_realized (GTK_WIDGET (dialog)))
    return;

  display = gtk_widget_get_display (GTK_WIDGET (dialog));

  if (busy)
    cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
  else
    cursor = NULL;

  gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog)), cursor);
  gdk_display_flush (display);

  if (cursor)
    g_object_unref (cursor);
}

static GtkListStore *
populate_icon_contexts_model (void)
{
  GtkListStore *store;
  GtkTreeIter iter;
  guint i;

  store = gtk_list_store_new (CONTEXTS_N_COLUMS,
                              G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING);


  gtk_list_store_append (store, &iter);
  gtk_list_store_set (store, &iter,
                      CONTEXTS_ID_COLUMN, -1,
                      CONTEXTS_NAME_COLUMN, "All Contexts",
                      CONTEXTS_TITLE_COLUMN, _("All Contexts"), -1);

  gtk_list_store_append (store, &iter);
  gtk_list_store_set (store, &iter,
                      CONTEXTS_ID_COLUMN, -1,
                      CONTEXTS_NAME_COLUMN, NULL,
                      CONTEXTS_TITLE_COLUMN, NULL, -1);

  for (i = 0; i < G_N_ELEMENTS (standard_contexts); i++)
    {

      gtk_list_store_append (store, &iter);
      gtk_list_store_set (store, &iter,
                          CONTEXTS_ID_COLUMN, i,
                          CONTEXTS_NAME_COLUMN, standard_contexts[i].name,
                          CONTEXTS_TITLE_COLUMN, _(standard_contexts[i].title),
                          -1);
    }

  return store;
}

static void
icons_row_activated_cb (GtkTreeView                 *view,
                        GtkTreePath                 *path,
                        GtkTreeViewColumn           *column,
                        GladeNamedIconChooserDialog *dialog)
{
  g_signal_emit_by_name (dialog, "icon-activated", NULL);
}

static void
icons_selection_changed_cb (GtkTreeSelection * selection,
                            GladeNamedIconChooserDialog * dialog)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  gchar *name;

  if (gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      gtk_tree_model_get (model, &iter, ICONS_NAME_COLUMN, &name, -1);
      if (name)
        entry_set_name (dialog, name);

      g_free (name);
    }
  else
    {
      /* entry_set_name (dialog, ""); */
    }

  /* we emit "selection-changed" for chooser in insert_text_handler()
   * to avoid emitting the signal twice */
}

static void
contexts_row_activated_cb (GtkTreeView                 *view,
                           GtkTreePath                 *cpath,
                           GtkTreeViewColumn           *column,
                           GladeNamedIconChooserDialog *dialog)
{
  GtkTreeIter iter;
  GtkTreePath *path;

  if (gtk_tree_model_get_iter_first (dialog->priv->filter_model, &iter))
    {

      gtk_tree_selection_select_iter (dialog->priv->selection, &iter);

      path = gtk_tree_model_get_path (dialog->priv->filter_model, &iter);

      gtk_tree_selection_select_path (dialog->priv->selection, path);

      gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (dialog->priv->icons_view),
                                     -1, 0);

      gtk_tree_path_free (path);

    }
  gtk_widget_grab_focus (dialog->priv->icons_view);
}

static void
contexts_selection_changed_cb (GtkTreeSelection            *selection,
                               GladeNamedIconChooserDialog *dialog)
{
  GtkTreeIter iter;
  GtkTreeModel *model;
  gboolean retval;
  gint context_id;

  retval = gtk_tree_selection_get_selected (selection, &model, &iter);

  if (retval)
    {

      gtk_tree_model_get (model, &iter, CONTEXTS_ID_COLUMN, &context_id, -1);

      dialog->priv->context_id = context_id;

      if (!dialog->priv->filter_model)
        return;

      filter_icons_model (dialog);
    }

  entry_set_name (dialog, "");

}

static gboolean
row_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer unused)
{
  gboolean retval;
  gchar *name, *title;

  gtk_tree_model_get (model, iter,
                      CONTEXTS_NAME_COLUMN, &name,
                      CONTEXTS_TITLE_COLUMN, &title, -1);

  retval = !name && !title;

  g_free (name);
  g_free (title);

  return retval;
}

static GtkWidget *
create_contexts_view (GladeNamedIconChooserDialog *dialog)
{
  GtkTreeView *view;
  GtkTreeViewColumn *column;
  GtkTreePath *path;

  dialog->priv->contexts_store = populate_icon_contexts_model ();

  view =
      GTK_TREE_VIEW (gtk_tree_view_new_with_model
                     (GTK_TREE_MODEL (dialog->priv->contexts_store)));

  column = gtk_tree_view_column_new_with_attributes (NULL,
                                                     gtk_cell_renderer_text_new
                                                     (), "text",
                                                     CONTEXTS_TITLE_COLUMN,
                                                     NULL);

  gtk_tree_view_append_column (view, column);
  gtk_tree_view_set_headers_visible (view, FALSE);

  gtk_tree_view_set_row_separator_func (view,
                                        (GtkTreeViewRowSeparatorFunc)
                                        row_separator_func, NULL, NULL);

  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view),
                               GTK_SELECTION_BROWSE);

  path = gtk_tree_path_new_from_indices (0, -1);
  gtk_tree_selection_select_path (gtk_tree_view_get_selection (view), path);
  gtk_tree_path_free (path);

  g_signal_connect (view, "row-activated",
                    G_CALLBACK (contexts_row_activated_cb), dialog);

  g_signal_connect (gtk_tree_view_get_selection (view), "changed",
                    G_CALLBACK (contexts_selection_changed_cb), dialog);

  gtk_widget_show (GTK_WIDGET (view));

  return GTK_WIDGET (view);
}

/* filters the icons model based on the current state */
static void
filter_icons_model (GladeNamedIconChooserDialog *dialog)
{

  set_busy_cursor (dialog, TRUE);

  g_object_ref (dialog->priv->filter_model);
  gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view), NULL);
  gtk_entry_completion_set_model (dialog->priv->entry_completion, NULL);

  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER
                                  (dialog->priv->filter_model));

  gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view),
                           dialog->priv->filter_model);
  gtk_entry_completion_set_model (dialog->priv->entry_completion,
                                  GTK_TREE_MODEL (dialog->priv->icons_store));
  gtk_entry_completion_set_text_column (dialog->priv->entry_completion,
                                        ICONS_NAME_COLUMN);
  g_object_unref (dialog->priv->filter_model);

  set_busy_cursor (dialog, FALSE);
}

static gboolean
filter_visible_func (GtkTreeModel                *model,
                     GtkTreeIter                 *iter,
                     GladeNamedIconChooserDialog *dialog)
{
  gboolean standard;
  gint context_id;

  gtk_tree_model_get (model, iter,
                      ICONS_CONTEXT_COLUMN, &context_id,
                      ICONS_STANDARD_COLUMN, &standard, -1);

  if (dialog->priv->context_id == -1)
    return (dialog->priv->settings_list_standard) ? TRUE && standard : TRUE;

  if (context_id == dialog->priv->context_id)
    return (dialog->priv->settings_list_standard) ? TRUE && standard : TRUE;
  else
    return FALSE;
}


static gboolean
search_equal_func (GtkTreeModel                *model,
                   gint                         column,
                   const gchar                 *key,
                   GtkTreeIter                 *iter,
                   GladeNamedIconChooserDialog *dialog)
{
  gchar *name;
  gboolean retval;

  gtk_tree_model_get (model, iter, ICONS_NAME_COLUMN, &name, -1);

  retval = !g_str_has_prefix (name, key);

  g_free (name);

  return retval;

}

static gboolean
scan_for_context_func (GtkTreeModel *model,
                       GtkTreePath  *path,
                       GtkTreeIter  *iter,
                       gpointer      user_data)
{
  ForEachFuncData *data = (ForEachFuncData *) user_data;
  GtkTreeSelection *selection =
      gtk_tree_view_get_selection (GTK_TREE_VIEW
                                   (data->dialog->priv->contexts_view));
  gchar *name = NULL;

  gtk_tree_model_get (model, iter, CONTEXTS_NAME_COLUMN, &name, -1);
  if (!name)
    return FALSE;

  if (strcmp (name, data->name) == 0)
    {

      data->found = TRUE;

      if (data->do_activate)
        gtk_tree_view_row_activated (GTK_TREE_VIEW
                                     (data->dialog->priv->contexts_view), path,
                                     gtk_tree_view_get_column (GTK_TREE_VIEW
                                                               (data->dialog->
                                                                priv->
                                                                contexts_view),
                                                               0));

      if (data->do_select)
        gtk_tree_selection_select_path (selection, path);
      else
        gtk_tree_selection_unselect_path (selection, path);

      if (data->do_cursor)
        gtk_tree_view_set_cursor (GTK_TREE_VIEW
                                  (data->dialog->priv->contexts_view), path,
                                  NULL, FALSE);

      g_free (name);

      return TRUE;
    }

  g_free (name);

  return FALSE;
}

gboolean
scan_for_name_func (GtkTreeModel *model,
                    GtkTreePath  *path,
                    GtkTreeIter  *iter,
                    gpointer      user_data)
{
  ForEachFuncData *data = (ForEachFuncData *) user_data;
  gchar *name = NULL;

  gtk_tree_model_get (model, iter, ICONS_NAME_COLUMN, &name, -1);
  if (!name)
    return FALSE;

  if (strcmp (name, data->name) == 0)
    {

      data->found = TRUE;

      if (data->do_activate)
        gtk_tree_view_row_activated (GTK_TREE_VIEW
                                     (data->dialog->priv->icons_view), path,
                                     gtk_tree_view_get_column (GTK_TREE_VIEW
                                                               (data->dialog->
                                                                priv->
                                                                icons_view),
                                                               0));

      if (data->do_select)
        gtk_tree_selection_select_path (data->dialog->priv->selection, path);
      else
        gtk_tree_selection_unselect_path (data->dialog->priv->selection, path);

      if (data->do_cursor)
        gtk_tree_view_set_cursor (GTK_TREE_VIEW
                                  (data->dialog->priv->icons_view), path, NULL,
                                  FALSE);

      g_free (name);

      return TRUE;
    }

  g_free (name);

  return FALSE;
}

static void
centre_selected_row (GladeNamedIconChooserDialog *dialog)
{
  GList *l;

  g_assert (dialog->priv->icons_store != NULL);
  g_assert (dialog->priv->selection != NULL);

  l = gtk_tree_selection_get_selected_rows (dialog->priv->selection, NULL);

  if (l)
    {
      g_assert (gtk_widget_get_mapped (GTK_WIDGET (dialog)));
      g_assert (gtk_widget_get_visible (GTK_WIDGET (dialog)));

      gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (dialog->priv->icons_view),
                                    (GtkTreePath *) l->data,
                                    NULL, TRUE, 0.5, 0.0);

/*		gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->priv->icons_view),
					 (GtkTreePath *) l->data,
					 0,
					 FALSE);
					 
		gtk_widget_grab_focus (dialog->priv->icons_view);		
*/
      g_list_foreach (l, (GFunc) gtk_tree_path_free, NULL);
      g_list_free (l);
    }
}

static void
select_first_row (GladeNamedIconChooserDialog *dialog)
{
  GtkTreePath *path;

  if (!dialog->priv->filter_model)
    return;

  path = gtk_tree_path_new_from_indices (0, -1);
  gtk_tree_view_set_cursor (GTK_TREE_VIEW (dialog->priv->icons_view), path,
                            NULL, FALSE);
  gtk_tree_path_free (path);
}

static void
pending_select_name_process (GladeNamedIconChooserDialog *dialog)
{
  ForEachFuncData *data;

  g_assert (dialog->priv->icons_store != NULL);
  g_assert (dialog->priv->selection != NULL);

  if (dialog->priv->pending_select_name)
    {

      data = g_slice_new0 (ForEachFuncData);

      data->name = dialog->priv->pending_select_name;
      data->do_select = TRUE;
      data->do_activate = FALSE;
      data->dialog = dialog;

      gtk_tree_model_foreach (dialog->priv->filter_model,
                              scan_for_name_func, data);

      g_free (dialog->priv->pending_select_name);
      dialog->priv->pending_select_name = NULL;

      g_slice_free (ForEachFuncData, data);

    }
  else
    {
      if (strlen (gtk_entry_get_text (GTK_ENTRY (dialog->priv->entry))) == 0)
        {
          select_first_row (dialog);
        }
    }

  centre_selected_row (dialog);
}

static gboolean
is_standard_icon_name (const gchar *icon_name)
{
  GQuark quark;

  quark = g_quark_try_string (icon_name);

  if (quark == 0)
    return FALSE;

  return (g_hash_table_lookup (standard_icon_quarks, GUINT_TO_POINTER (quark))
          != NULL);

}

static void
cleanup_after_load (GladeNamedIconChooserDialog *dialog)
{
  dialog->priv->load_id = 0;

  pending_select_name_process (dialog);

  set_busy_cursor (dialog, FALSE);
}

static void
chooser_set_model (GladeNamedIconChooserDialog *dialog)
{

  /* filter model */
  dialog->priv->filter_model =
      gtk_tree_model_filter_new (GTK_TREE_MODEL (dialog->priv->icons_store),
                                 NULL);

  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER
                                          (dialog->priv->filter_model),
                                          (GtkTreeModelFilterVisibleFunc)
                                          filter_visible_func, dialog, NULL);

  gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view),
                           dialog->priv->filter_model);
  g_object_unref (dialog->priv->filter_model);

  gtk_entry_completion_set_model (dialog->priv->entry_completion,
                                  GTK_TREE_MODEL (dialog->priv->icons_store));
  gtk_entry_completion_set_text_column (dialog->priv->entry_completion,
                                        ICONS_NAME_COLUMN);

  gtk_tree_view_set_search_column (GTK_TREE_VIEW (dialog->priv->icons_view),
                                   ICONS_NAME_COLUMN);

  dialog->priv->icons_loaded = TRUE;
}

typedef struct
{
  gchar *name;
  gint context;
} IconData;

static gint
icon_data_compare (IconData *a, IconData *b)
{
  return g_ascii_strcasecmp (a->name, b->name);
}

static gboolean
reload_icons (GladeNamedIconChooserDialog *dialog)
{
  GtkListStore *store = dialog->priv->icons_store;
  GtkTreeIter iter;
  guint i;
  GList *l, *icons = NULL;

  /* retrieve icon names from each context */
  for (i = 0; i < G_N_ELEMENTS (standard_contexts); i++)
    {

      GList *icons_in_context =
          gtk_icon_theme_list_icons (dialog->priv->icon_theme,
                                     standard_contexts[i].name);

      for (l = icons_in_context; l; l = l->next)
        {

          IconData *data = g_slice_new (IconData);

          data->name = (gchar *) l->data;
          data->context = i;

          icons = g_list_prepend (icons, data);
        }

      g_list_free (icons_in_context);
    }

  /* sort icon names */
  icons = g_list_sort (icons, (GCompareFunc) icon_data_compare);

  /* put into to model */
  for (l = icons; l; l = l->next)
    {

      IconData *data = (IconData *) l->data;

      gtk_list_store_append (store, &iter);
      gtk_list_store_set (store, &iter,
                          ICONS_CONTEXT_COLUMN, data->context,
                          ICONS_STANDARD_COLUMN,
                          is_standard_icon_name (data->name), ICONS_NAME_COLUMN,
                          data->name, -1);

      g_free (data->name);
      g_slice_free (IconData, data);
    }

  g_list_free (icons);

  chooser_set_model (dialog);

  return FALSE;
}

static void
change_icon_theme (GladeNamedIconChooserDialog *dialog)
{
  if (dialog->priv->icon_theme == NULL)
    dialog->priv->icon_theme = get_icon_theme_for_widget (GTK_WIDGET (dialog));

  gtk_tree_view_set_model (GTK_TREE_VIEW (dialog->priv->icons_view), NULL);
  gtk_list_store_clear (dialog->priv->icons_store);

  set_busy_cursor (dialog, TRUE);

  dialog->priv->load_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 300,
                                           (GSourceFunc) reload_icons,
                                           dialog,
                                           (GDestroyNotify) cleanup_after_load);

}

static void
glade_named_icon_chooser_dialog_screen_changed (GtkWidget *widget,
                                                GdkScreen *previous_screen)
{
  GladeNamedIconChooserDialog *dialog;

  dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (widget);

  if (GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->
      screen_changed)
    GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->
        screen_changed (widget, previous_screen);

  if (gtk_widget_get_mapped (widget))
    change_icon_theme (dialog);

}

static GtkWidget *
create_icons_view (GladeNamedIconChooserDialog *dialog)
{
  GtkTreeView *view;
  GtkTreeViewColumn *column;
  GtkCellRenderer *pixbuf_renderer, *text_renderer;

  view = GTK_TREE_VIEW (gtk_tree_view_new ());

  column = gtk_tree_view_column_new ();
  gtk_tree_view_column_set_min_width (column, 56);
  gtk_tree_view_column_set_title (column, NULL);
  pixbuf_renderer = gtk_cell_renderer_pixbuf_new ();

  gtk_tree_view_column_pack_start (column, pixbuf_renderer, TRUE);

  gtk_tree_view_column_set_attributes (column,
                                       pixbuf_renderer,
                                       "icon-name", ICONS_NAME_COLUMN, NULL);

  gtk_tree_view_append_column (view, column);
  g_object_set (pixbuf_renderer,
                "xpad", 2,
                "xalign", 1.0, "stock-size", GTK_ICON_SIZE_MENU, NULL);

  column = gtk_tree_view_column_new ();
  gtk_tree_view_column_set_title (column, "Name");
  text_renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (text_renderer),
                "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0, NULL);

  gtk_tree_view_column_pack_start (column, text_renderer, TRUE);

  gtk_tree_view_column_set_attributes (column,
                                       text_renderer,
                                       "text", ICONS_NAME_COLUMN, NULL);


  gtk_tree_view_append_column (view, column);
  gtk_tree_view_column_set_expand (column, TRUE);
  gtk_tree_view_column_set_resizable (column, FALSE);

  gtk_tree_view_set_headers_visible (view, FALSE);

  gtk_tree_view_set_enable_search (view, TRUE);
  gtk_tree_view_set_search_equal_func (view,
                                       (GtkTreeViewSearchEqualFunc)
                                       search_equal_func, dialog, NULL);

  g_signal_connect (view, "row-activated",
                    G_CALLBACK (icons_row_activated_cb), dialog);

  g_signal_connect (gtk_tree_view_get_selection (view), "changed",
                    G_CALLBACK (icons_selection_changed_cb), dialog);

  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view),
                               GTK_SELECTION_BROWSE);

  dialog->priv->selection = gtk_tree_view_get_selection (view);

  gtk_tree_view_set_rules_hint (view, TRUE);

  gtk_widget_show (GTK_WIDGET (view));

  return GTK_WIDGET (view);
}

/* sets the 'list-standard' state and refilters the icons model */
static void
button_toggled (GtkToggleButton *button, GladeNamedIconChooserDialog *dialog)
{
  dialog->priv->settings_list_standard = gtk_toggle_button_get_active (button);

  if (dialog->priv->filter_model != NULL)
    filter_icons_model (dialog);
}

static GHashTable *
create_standard_icon_quarks (void)
{
  GHashTable *table;
  GQuark quark;
  guint i;

  table = g_hash_table_new (NULL, NULL);

  for (i = 0; i < G_N_ELEMENTS (standard_icon_names); i++)
    {

      quark = g_quark_from_static_string (standard_icon_names[i]);

      g_hash_table_insert (table,
                           GUINT_TO_POINTER (quark), GUINT_TO_POINTER (quark));
    }

  return table;
}

static void
glade_named_icon_chooser_dialog_style_set (GtkWidget *widget,
                                           GtkStyle  *previous_style)
{
  if (gtk_widget_has_screen (widget) && gtk_widget_get_mapped (widget))
    change_icon_theme (GLADE_NAMED_ICON_CHOOSER_DIALOG (widget));
}

/* override GtkWidget::show_all since we have internal widgets we wish to keep
 * hidden unless we decide otherwise, like the list-standard-icons-only checkbox.
 */
static void
glade_named_icon_chooser_dialog_show_all (GtkWidget *widget)
{
  gtk_widget_show (widget);
}

/* Handler for GtkWindow::set-focus; this is where we save the last-focused
 * widget on our toplevel.  See glade_named_icon_chooser_dialog_hierarchy_changed()
 */
static void
glade_named_icon_chooser_dialog_set_focus (GtkWindow *window, GtkWidget *focus)
{

  GTK_WINDOW_CLASS (glade_named_icon_chooser_dialog_parent_class)->
      set_focus (window, focus);

  GLADE_NAMED_ICON_CHOOSER_DIALOG (window)->priv->last_focus_widget =
      gtk_window_get_focus (window);
}

static void
glade_named_icon_chooser_dialog_finalize (GObject *object)
{
  GladeNamedIconChooserDialog *dialog =
      GLADE_NAMED_ICON_CHOOSER_DIALOG (object);

  if (dialog->priv->pending_select_name)
    {
      g_free (dialog->priv->pending_select_name);
      dialog->priv->pending_select_name = NULL;
    }

  G_OBJECT_CLASS (glade_named_icon_chooser_dialog_parent_class)->
    finalize (object);
}

static void
glade_named_icon_chooser_dialog_map (GtkWidget *widget)
{
  GladeNamedIconChooserDialog *dialog =
      GLADE_NAMED_ICON_CHOOSER_DIALOG (widget);

  GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->map (widget);

  settings_load (dialog);

  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->priv->button),
                                dialog->priv->settings_list_standard);

  gtk_widget_grab_focus (dialog->priv->icons_view);
}

static void
glade_named_icon_chooser_dialog_unmap (GtkWidget *widget)
{
  GladeNamedIconChooserDialog *dialog =
      GLADE_NAMED_ICON_CHOOSER_DIALOG (widget);

  settings_save (dialog);

  GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->
      unmap (widget);
}

/* we load the icons in expose() because we want the widget
 * to be fully painted before loading begins
 */
static gboolean
glade_named_icon_chooser_dialog_draw (GtkWidget *widget, cairo_t *cr)
{
  GladeNamedIconChooserDialog *dialog =
      GLADE_NAMED_ICON_CHOOSER_DIALOG (widget);
  gboolean retval;

  retval =
      GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->
      draw (widget, cr);
  if (!dialog->priv->icons_loaded)
    {
      change_icon_theme (GLADE_NAMED_ICON_CHOOSER_DIALOG (widget));
      dialog->priv->icons_loaded = TRUE;
    }

  return retval;
}

static void
response_cb (GtkDialog *dialog, gint response_id)
{
  /* Act only on response IDs we recognize */
  if (!(response_id == GTK_RESPONSE_ACCEPT
        || response_id == GTK_RESPONSE_OK
        || response_id == GTK_RESPONSE_YES
        || response_id == GTK_RESPONSE_APPLY))
    return;

  if (!should_respond (GLADE_NAMED_ICON_CHOOSER_DIALOG (dialog)))
    {
      g_signal_stop_emission_by_name (dialog, "response");
    }
}

/* we intercept the GladeNamedIconChooser::icon-activated signal and try to
 * make the dialog emit a valid response signal
 */
static void
icon_activated_cb (GladeNamedIconChooserDialog *dialog)
{
  GList *children, *l;

  children =
      gtk_container_get_children (GTK_CONTAINER
                                  (gtk_dialog_get_action_area
                                   (GTK_DIALOG (dialog))));

  for (l = children; l; l = l->next)
    {
      GtkWidget *widget;
      gint response_id;

      widget = GTK_WIDGET (l->data);
      response_id =
          gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget);

      if (response_id == GTK_RESPONSE_ACCEPT ||
          response_id == GTK_RESPONSE_OK ||
          response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY)
        {
          g_list_free (children);

          gtk_dialog_response (GTK_DIALOG (dialog), response_id);

          return;
        }
    }
  g_list_free (children);
}

/* we intercept the GladeNamedIconChooser::selection-changed signal and try to
 * make the affirmative response button insensitive when the selection is empty 
 */
static void
selection_changed_cb (GladeNamedIconChooserDialog *dialog)
{
  GList *children, *l;
  gchar *icon_name;

  children =
      gtk_container_get_children (GTK_CONTAINER
                                  (gtk_dialog_get_action_area
                                   (GTK_DIALOG (dialog))));

  for (l = children; l; l = l->next)
    {
      GtkWidget *widget;
      gint response_id;

      widget = GTK_WIDGET (l->data);
      response_id =
          gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget);

      if (response_id == GTK_RESPONSE_ACCEPT ||
          response_id == GTK_RESPONSE_OK ||
          response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY)
        {
          icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog);

          gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog),
                                             response_id, icon_name != NULL);
          g_free (icon_name);
          g_list_free (children);
          return;
        }
    }
  g_list_free (children);
}

static void
glade_named_icon_chooser_dialog_init (GladeNamedIconChooserDialog *dialog)
{
  GtkWidget *contents;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *sw;
  GtkWidget *label;
  GtkWidget *hpaned;
  GtkWidget *content_area;
  GtkWidget *action_area;
  GtkSizeGroup *group;

  dialog->priv = glade_named_icon_chooser_dialog_get_instance_private (dialog);

  dialog->priv->filter_model = NULL;
  dialog->priv->icons_store = NULL;
  dialog->priv->context_id = -1;
  dialog->priv->pending_select_name = NULL;
  dialog->priv->last_focus_widget = NULL;
  dialog->priv->icons_loaded = FALSE;


  gtk_window_set_title (GTK_WINDOW (dialog), _("Named Icon Chooser"));

  gtk_window_set_default_size (GTK_WINDOW (dialog), 610, 480);

#if !GTK_CHECK_VERSION (2, 21, 8)
  gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE);
#endif
  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
  gtk_container_set_border_width (GTK_CONTAINER (content_area), 12);
  gtk_box_set_spacing (GTK_BOX (content_area), 12);
  action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog));
  gtk_container_set_border_width (GTK_CONTAINER (action_area), 0);
  gtk_box_set_spacing (GTK_BOX (action_area), 6);

  /* We do a signal connection here rather than overriding the method in
   * class_init because GtkDialog::response is a RUN_LAST signal.  We want *our*
   * handler to be run *first*, regardless of whether the user installs response
   * handlers of his own.
   */
  g_signal_connect (dialog, "response", G_CALLBACK (response_cb), NULL);

  g_signal_connect (dialog, "icon-activated",
                    G_CALLBACK (icon_activated_cb), NULL);

  g_signal_connect (dialog, "selection-changed",
                    G_CALLBACK (selection_changed_cb), NULL);


  if (standard_icon_quarks == NULL)
    standard_icon_quarks = create_standard_icon_quarks ();

  contents = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_widget_show (contents);

  label = gtk_label_new_with_mnemonic (_("Icon _Name:"));
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
  gtk_widget_show (label);

  dialog->priv->entry = gtk_entry_new ();
  gtk_entry_set_activates_default (GTK_ENTRY (dialog->priv->entry), TRUE);
  gtk_entry_set_width_chars (GTK_ENTRY (dialog->priv->entry), 40);
  g_object_set (G_OBJECT (dialog->priv->entry), "truncate-multiline", TRUE,
                NULL);
  g_signal_connect (G_OBJECT (dialog->priv->entry), "changed",
                    G_CALLBACK (changed_text_handler), dialog);
  g_signal_connect (G_OBJECT (dialog->priv->entry), "insert-text",
                    G_CALLBACK (insert_text_handler), dialog);
  gtk_widget_show (dialog->priv->entry);

  dialog->priv->entry_completion = gtk_entry_completion_new ();
  gtk_entry_set_completion (GTK_ENTRY (dialog->priv->entry),
                            dialog->priv->entry_completion);
  gtk_entry_completion_set_popup_completion (dialog->priv->entry_completion,
                                             FALSE);
  gtk_entry_completion_set_inline_completion (dialog->priv->entry_completion,
                                              TRUE);

  gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->priv->entry);

  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
  gtk_widget_show (hbox);

  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (hbox), dialog->priv->entry, TRUE, TRUE, 0);
  gtk_box_pack_start (GTK_BOX (contents), hbox, FALSE, FALSE, 6);

  hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL);
  gtk_paned_set_position (GTK_PANED (hpaned), 150);
  gtk_widget_show (hpaned);

  dialog->priv->contexts_view = create_contexts_view (dialog);
  dialog->priv->icons_view = create_icons_view (dialog);

  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_widget_show (vbox);

  group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL);

  label = gtk_label_new_with_mnemonic (_("C_ontexts:"));
  gtk_label_set_mnemonic_widget (GTK_LABEL (label),
                                 dialog->priv->contexts_view);
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
  gtk_size_group_add_widget (group, label);
  gtk_widget_show (label);

  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER,
                                  GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
  gtk_widget_show (sw);

  gtk_container_add (GTK_CONTAINER (sw), dialog->priv->contexts_view);
  gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
  gtk_paned_pack1 (GTK_PANED (hpaned), vbox, FALSE, FALSE);


  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
  gtk_widget_show (vbox);

  label = gtk_label_new_with_mnemonic (_("Icon Na_mes:"));
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), dialog->priv->icons_view);
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
  gtk_size_group_add_widget (group, label);
  gtk_widget_show (label);

  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER,
                                  GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
  gtk_widget_show (sw);

  gtk_container_add (GTK_CONTAINER (sw), dialog->priv->icons_view);
  gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
  gtk_paned_pack2 (GTK_PANED (hpaned), vbox, TRUE, FALSE);

  gtk_box_pack_start (GTK_BOX (contents), hpaned, TRUE, TRUE, 0);


  g_object_unref (G_OBJECT (group));

  dialog->priv->button =
      gtk_check_button_new_with_mnemonic (_("_List standard icons only"));
  gtk_widget_show (dialog->priv->button);

  g_signal_connect (dialog->priv->button, "toggled",
                    G_CALLBACK (button_toggled), dialog);

  gtk_box_pack_start (GTK_BOX (contents), dialog->priv->button, FALSE, FALSE,
                      0);
  gtk_box_pack_start (GTK_BOX (content_area), contents, TRUE, TRUE, 0);

  /* underlying model */
  dialog->priv->icons_store = gtk_list_store_new (ICONS_N_COLUMNS,
                                                  G_TYPE_UINT,
                                                  G_TYPE_BOOLEAN,
                                                  G_TYPE_STRING);
}

static void
glade_named_icon_chooser_dialog_class_init (GladeNamedIconChooserDialogClass *klass)
{
  GObjectClass *object_class;
  GtkWidgetClass *widget_class;
  GtkWindowClass *window_class;

  object_class = G_OBJECT_CLASS (klass);
  widget_class = GTK_WIDGET_CLASS (klass);
  window_class = GTK_WINDOW_CLASS (klass);

  object_class->finalize = glade_named_icon_chooser_dialog_finalize;

  widget_class->map = glade_named_icon_chooser_dialog_map;
  widget_class->unmap = glade_named_icon_chooser_dialog_unmap;
  widget_class->draw = glade_named_icon_chooser_dialog_draw;
  widget_class->show_all = glade_named_icon_chooser_dialog_show_all;
  widget_class->style_set = glade_named_icon_chooser_dialog_style_set;
  widget_class->screen_changed = glade_named_icon_chooser_dialog_screen_changed;

  window_class->set_focus = glade_named_icon_chooser_dialog_set_focus;

        /**
	 * GladeNamedIconChooserDialog::icon-activated
	 * @chooser: the object which received the signal
	 *
	 * This signal is emitted when the user "activates" an icon
	 * in the named icon chooser.  This can happen by double-clicking on an item
	 * in the recently used resources list, or by pressing
	 * <keycap>Enter</keycap>.
	*/
  dialog_signals[ICON_ACTIVATED] =
      g_signal_new ("icon-activated",
                    G_TYPE_FROM_CLASS (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GladeNamedIconChooserDialogClass,
                                     icon_activated), NULL, NULL,
                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);

        /**
	 * GladeNamedIconChooserDialog::selection-changed
	 * @chooser: the object which received the signal
	 *
	 * This signal is emitted when there is a change in the set of
	 * selected icon names.  This can happen when a user
	 * modifies the selection with the mouse or the keyboard, or when
	 * explicitely calling functions to change the selection.
	*/
  dialog_signals[SELECTION_CHANGED] =
      g_signal_new ("selection-changed",
                    G_TYPE_FROM_CLASS (object_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET (GladeNamedIconChooserDialogClass,
                                     selection_changed), NULL, NULL,
                    g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}

static gboolean
should_respond (GladeNamedIconChooserDialog *dialog)
{
  gchar *icon_name;

  /* is there an icon selected? */
  icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog);
  if (!icon_name)
    return FALSE;

  g_free (icon_name);
  return TRUE;
}

/* get's the name of the configuration file */
static gchar *
get_config_filename (void)
{
  return g_build_filename (g_get_user_config_dir (), "gladeui", "config", NULL);
}

/* get's the name of the directory that contains the config file */
static char *
get_config_dirname (void)
{
  return g_build_filename (g_get_user_config_dir (), "gladeui", NULL);
}

/* loads the configuration settings */
static void
settings_load (GladeNamedIconChooserDialog *dialog)
{
  GKeyFile *keyfile;
  gboolean success, boolean_value;
  gchar *filename;
  GError *error = NULL;

  keyfile = g_key_file_new ();

  filename = get_config_filename ();
  success = g_key_file_load_from_file (keyfile,
                                       filename, G_KEY_FILE_NONE, &error);
  g_free (filename);

  if (!success)
    {

      dialog->priv->settings_list_standard = DEFAULT_SETTING_LIST_STANDARD_ONLY;

      g_clear_error (&error);
      g_key_file_free (keyfile);
      return;
    }


  boolean_value = g_key_file_get_boolean (keyfile,
                                          "Named Icon Chooser",
                                          "ListStandardOnly", &error);
  if (error)
    {
      dialog->priv->settings_list_standard = DEFAULT_SETTING_LIST_STANDARD_ONLY;
      g_clear_error (&error);
    }
  else
    {
      dialog->priv->settings_list_standard = boolean_value;
    }

  g_key_file_free (keyfile);
}

/* creates a GKeyFile based on the current settings */
static GKeyFile *
settings_to_keyfile (GladeNamedIconChooserDialog *dialog)
{
  GKeyFile *keyfile;
  gchar *filename;

  keyfile = g_key_file_new ();

  filename = get_config_filename ();
  g_key_file_load_from_file (keyfile,
                             get_config_filename (),
                             G_KEY_FILE_NONE, NULL);
  g_free (filename);

  g_key_file_set_boolean (keyfile,
                          "Named Icon Chooser",
                          "ListStandardOnly",
                          dialog->priv->settings_list_standard);

  return keyfile;
}

/* serializes the the current configuration to the config file */
static void
settings_save (GladeNamedIconChooserDialog *dialog)
{
  GKeyFile *keyfile;
  gchar *contents;
  gsize contents_length;
  gchar *filename = NULL, *dirname = NULL;
  GError *error = NULL;

  keyfile = settings_to_keyfile (dialog);

  contents = g_key_file_to_data (keyfile, &contents_length, &error);

  if (error)
    goto out;

  filename = get_config_filename ();

  if (!g_file_set_contents (filename, contents, contents_length, NULL))
    {
      gchar *dirname;
      gint saved_errno;

      dirname = get_config_dirname ();
      if (g_mkdir_with_parents (dirname, 0700) != 0)    /* 0700 per the XDG basedir spec */
        {

          saved_errno = errno;
          g_set_error (&error,
                       G_FILE_ERROR,
                       g_file_error_from_errno (saved_errno),
                       _("Could not create directory: %s"), dirname);
          goto out;
        }

      if (!g_file_set_contents (filename, contents, contents_length, &error))
        {
          goto out;
        }
    }

out:

  g_free (contents);
  g_free (dirname);
  g_free (filename);
  g_clear_error (&error);
  g_key_file_free (keyfile);
}

static GtkWidget *
glade_named_icon_chooser_dialog_new_valist (const gchar *title,
                                            GtkWindow   *parent,
                                            const gchar *first_button_text,
                                            va_list      varargs)
{
  GtkWidget *result;
  const char *button_text = first_button_text;
  gint response_id;

  result = g_object_new (GLADE_TYPE_NAMED_ICON_CHOOSER_DIALOG,
                         "title", title, "transient-for", parent, NULL);

  while (button_text)
    {
      response_id = va_arg (varargs, gint);
      gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id);
      button_text = va_arg (varargs, const gchar *);
    }

  return result;
}

/**
 * glade_named_icon_chooser_dialog_new:
 * @title: Title of the dialog, or %NULL
 * @parent: Transient parent of the dialog, or %NULL,
 * @first_button_text: stock ID or text to go in the first button, or %NULL
 * @Varargs: response ID for the first button, then additional (button, id)
 *   pairs, ending with %NULL
 *
 * Creates a new #GladeNamedIconChooserDialog.  This function is analogous to
 * gtk_dialog_new_with_buttons().
 *
 * Return value: a new #GladeNamedIconChooserDialog
 */
GtkWidget *
glade_named_icon_chooser_dialog_new (const gchar *title,
                                     GtkWindow   *parent,
                                     const gchar *first_button_text,
                                     ...)
{
  GtkWidget *result;
  va_list varargs;

  va_start (varargs, first_button_text);
  result = glade_named_icon_chooser_dialog_new_valist (title,
                                                       parent,
                                                       first_button_text,
                                                       varargs);
  va_end (varargs);

  return result;
}
