/*
 * Copyright (C) 2001 Ximian, Inc.
 *
 * 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.
 *
 * Authors:
 *   Chema Celorio <chema@celorio.com>
 *   Tristan Van Berkom <tvb@gnome.org>
 */

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

/**
 * SECTION:glade-editor
 * @Short_Description: A Widget to edit a #GladeWidget.
 *
 * This is the Glade Notebook containing all the controls needed to configure a #GladeWidget.
 */

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

#include <gtk/gtk.h>

#include "glade.h"
#include "glade-widget.h"
#include "glade-widget-adaptor.h"
#include "glade-editor.h"
#include "glade-signal-editor.h"
#include "glade-property.h"
#include "glade-property-def.h"
#include "glade-command.h"
#include "glade-debug.h"
#include "glade-marshallers.h"
#include "glade-project.h"
#include "glade-utils.h"
#include "glade-editor-property.h"

static void glade_editor_switch_page (GtkNotebook     *notebook,
                                      GtkWidget       *page,
                                      guint            page_num,
                                      GladeEditor     *editor);

enum
{
  PROP_0,
  PROP_SHOW_INFO,
  PROP_WIDGET,
  PROP_SHOW_CLASS_FIELD,
  PROP_CLASS_FIELD,
  PROP_SHOW_BORDER,
  PROP_SIGNAL_EDITOR,
  N_PROPERTIES
};

typedef struct _GladeEditorPrivate
{

  GtkWidget *notebook; /* The notebook widget */

  GladeWidget *loaded_widget; /* A handy pointer to the GladeWidget
                               * that is loaded in the editor. NULL
                               * if no widgets are selected
                               */

  GladeWidgetAdaptor *loaded_adaptor; /* A pointer to the loaded
                                       * GladeWidgetAdaptor. Note that we can
                                       * have a class loaded without a
                                       * loaded_widget. For this reason we
                                       * can't use loaded_widget->adaptor.
                                       * When a widget is selected we load
                                       * this class in the editor first and
                                       * then fill the values of the inputs
                                       * with the GladeProperty items.
                                       * This is usefull for not having
                                       * to redraw/container_add the widgets
                                       * when we switch from widgets of the
                                       * same class
                                       */

  GtkWidget *page_widget;
  GtkWidget *page_packing;
  GtkWidget *page_common;
  GtkWidget *page_atk;

  GladeSignalEditor *signal_editor; /* The signal editor packed into vbox_signals
                                     */

  GList *editables;     /* A list of GladeEditables. We have a widget
                         * for each GladeWidgetAdaptor and we only load
                         * them on demand
                         */
        
  GtkWidget *packing_page; /* Packing pages are dynamically created each
                            * selection, this pointer is only to free
                            * the last packing page.
                            */
  
  gboolean loading; /* Use when loading a GladeWidget into the editor
                     * we set this flag so that we can ignore the
                     * "changed" signal of the name entry text since
                     * the name has not really changed, just a new name
                     * was loaded.
                     */

  gulong project_closed_signal_id; /* Unload widget when widget's project closes  */
  gulong project_removed_signal_id; /* Unload widget when its removed from the project. */
  gulong widget_warning_id; /* Update when widget changes warning messages. */
  gulong widget_name_id;    /* Update class field when widget name changes  */

  GtkWidget *class_field; /* The class header */

  GtkWidget *warning;   /* A pointer to an icon we can show in the class
                         * field to publish tooltips for class related
                         * versioning errors.
                         */

  GtkWidget *class_icon; /* An image with the current widget's class icon.  */
  GtkWidget *class_label; /* A label with the current class label. */

  gboolean show_class_field; /* Whether or not to show the class field at the top */
} GladeEditorPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (GladeEditor, glade_editor, GTK_TYPE_BOX)

static GParamSpec *properties[N_PROPERTIES];

static void
glade_editor_set_property (GObject      *object,
                           guint         prop_id,
                           const GValue *value,
                           GParamSpec   *pspec)
{
  GladeEditor *editor = GLADE_EDITOR (object);
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  switch (prop_id)
    {
      case PROP_SHOW_INFO:
        break;
      case PROP_WIDGET:
        glade_editor_load_widget (editor,
                                  GLADE_WIDGET (g_value_get_object (value)));
        break;
      case PROP_SHOW_CLASS_FIELD:
        if (g_value_get_boolean (value))
          glade_editor_show_class_field (editor);
        else
          glade_editor_hide_class_field (editor);
        break;
      case PROP_SHOW_BORDER:
        gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook),
                                      g_value_get_boolean (value));
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
glade_editor_get_property (GObject    *object,
                           guint       prop_id,
                           GValue     *value,
                           GParamSpec *pspec)
{
  GladeEditor *editor = GLADE_EDITOR (object);
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  switch (prop_id)
    {
      case PROP_SHOW_INFO:
        g_value_set_boolean (value, FALSE);
        break;
      case PROP_WIDGET:
        g_value_set_object (value, priv->loaded_widget);
        break;
      case PROP_SHOW_CLASS_FIELD:
        g_value_set_boolean (value, priv->show_class_field);
        break;
      case PROP_CLASS_FIELD:
        g_value_set_static_string (value, gtk_label_get_label (GTK_LABEL (priv->class_label)));
        break;
      case PROP_SHOW_BORDER:
        g_value_set_boolean (value, gtk_notebook_get_show_border (GTK_NOTEBOOK (priv->notebook)));
        break;
      case PROP_SIGNAL_EDITOR:
        g_value_set_object (value, priv->signal_editor);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
glade_editor_dispose (GObject *object)
{
  GladeEditor *editor = GLADE_EDITOR (object);
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  glade_editor_load_widget (editor, NULL);

  /* Unref all the cached pages */
  g_list_free_full (priv->editables, g_object_unref);

  G_OBJECT_CLASS (glade_editor_parent_class)->dispose (object);
}

static void
glade_editor_class_init (GladeEditorClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->dispose      = glade_editor_dispose;
  object_class->set_property = glade_editor_set_property;
  object_class->get_property = glade_editor_get_property;

  /* Properties */
  properties[PROP_SHOW_INFO] =
    g_param_spec_boolean ("show-info",
                          _("Show info"),
                          _("Whether to show an informational "
                            "button for the loaded widget"),
                          FALSE,
                          G_PARAM_READABLE | G_PARAM_DEPRECATED);

  properties[PROP_WIDGET] =
    g_param_spec_object ("widget",
                         _("Widget"),
                         _("The currently loaded widget in this editor"),
                         GLADE_TYPE_WIDGET,
                         G_PARAM_READWRITE);

  properties[PROP_SHOW_CLASS_FIELD] =
    g_param_spec_boolean ("show-class-field",
                          _("Show Class Field"),
                          _("Whether to show the class field at the top"),
                          TRUE,
                          G_PARAM_READWRITE);

  properties[PROP_CLASS_FIELD] =
    g_param_spec_string ("class-field",
                         _("Class Field"),
                         _("The class field string"),
                         NULL,
                         G_PARAM_READABLE);

  properties[PROP_SHOW_BORDER] =
    g_param_spec_boolean ("show-boder",
                          _("Show Border"),
                          _("Whether the border should be shown"),
                          TRUE,
                          G_PARAM_READWRITE);

  properties[PROP_SIGNAL_EDITOR] =
    g_param_spec_object ("signal-editor",
                         _("Signal Editor"),
                         _("The signal editor used to edit signals"),
                         GLADE_TYPE_SIGNAL_EDITOR,
                         G_PARAM_READABLE);
  
  /* Install all properties */
  g_object_class_install_properties (object_class, N_PROPERTIES, properties);

  /* Bind to template */
  gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-editor.ui");

  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, notebook);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_widget);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_packing);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_common);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_atk);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, class_field);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, class_icon);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, class_label);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, warning);
  gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, signal_editor);

  gtk_widget_class_bind_template_callback (widget_class, glade_editor_switch_page);
}

static void
glade_editor_update_class_warning_cb (GladeWidget *widget,
                                      GParamSpec  *pspec,
                                      GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  if (glade_widget_support_warning (widget))
    gtk_widget_show (priv->warning);
  else
    gtk_widget_hide (priv->warning);

  gtk_widget_set_tooltip_text (priv->warning, glade_widget_support_warning (widget));
}


static void
glade_editor_update_class_field (GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  if (priv->loaded_widget)
    {
      GladeWidget *widget = priv->loaded_widget;
      gchar *text;

      gtk_image_set_from_icon_name (GTK_IMAGE (priv->class_icon),
                                    glade_widget_adaptor_get_icon_name (priv->loaded_adaptor),
                                    GTK_ICON_SIZE_BUTTON);
      gtk_widget_show (priv->class_icon);

      if (glade_widget_has_name (widget))
        {
          /* translators: %s(Class Title) Properties - %s (ClassName) [%s(WidgetName)]
           * example: Window Properties - GtkWindow [window1]
           */
          text = g_strdup_printf (_("%s Properties - %s [%s]"),
                                  glade_widget_adaptor_get_title (priv->loaded_adaptor),
                                  glade_widget_adaptor_get_display_name (priv->loaded_adaptor),
                                  glade_widget_get_display_name (widget));
        }
      else
        {
          /* translators: %s(Class Title) Properties - %s (ClassName)
           * example: Window Properties - GtkWindow
           */
          text = g_strdup_printf (_("%s Properties - %s"),
                                  glade_widget_adaptor_get_title (priv->loaded_adaptor),
                                  glade_widget_adaptor_get_display_name (priv->loaded_adaptor));
        }

      gtk_label_set_text (GTK_LABEL (priv->class_label), text);
      g_free (text);

      glade_editor_update_class_warning_cb (priv->loaded_widget, NULL, editor);
    }
  else
    {
      gtk_widget_hide (priv->class_icon);
      gtk_widget_hide (priv->warning);
      gtk_label_set_text (GTK_LABEL (priv->class_label), _("Properties"));
    }

  g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_CLASS_FIELD]);
}

static void
glade_editor_update_widget_name_cb (GladeWidget *widget,
                                    GParamSpec  *pspec,
                                    GladeEditor *editor)
{
  glade_editor_update_class_field (editor);
}

static void
glade_editor_switch_page (GtkNotebook *notebook,
                          GtkWidget   *page,
                          guint        page_num,
                          GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  gtk_widget_hide (priv->page_widget);
  gtk_widget_hide (priv->page_packing);
  gtk_widget_hide (priv->page_common);
  gtk_widget_hide (priv->page_atk);

  switch (page_num)
    {
    case 0:
      gtk_widget_show (priv->page_widget);
      break;
    case 1:
      gtk_widget_show (priv->page_packing);
      break;
    case 2:
      gtk_widget_show (priv->page_common);
      break;
    case 4:
      gtk_widget_show (priv->page_atk);
      break;
    }
}

static void
glade_editor_init (GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);
  gint                icon_height;

  priv->show_class_field = TRUE;

  gtk_widget_init_template (GTK_WIDGET (editor));

  gtk_icon_size_lookup (GTK_ICON_SIZE_BUTTON, NULL, &icon_height);
  gtk_widget_set_size_request (priv->class_label, -1, icon_height + 2);

  glade_editor_update_class_field (editor);
}

static GtkWidget *
glade_editor_get_editable_by_adaptor (GladeEditor        *editor,
                                      GladeWidgetAdaptor *adaptor,
                                      GladeEditorPageType type)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);
  GtkWidget *editable;
  GList *list;

  g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL);

  for (list = priv->editables; list; list = list->next)
    {
      editable = list->data;
      if (type !=
          GPOINTER_TO_INT (g_object_get_data
                           (G_OBJECT (editable), "glade-editor-page-type")))
        continue;
      if (g_object_get_data (G_OBJECT (editable), "glade-widget-adaptor") ==
          adaptor)
        return editable;
    }

  editable = (GtkWidget *) glade_widget_adaptor_create_editable (adaptor, type);
  g_return_val_if_fail (editable != NULL, NULL);

  g_object_set_data (G_OBJECT (editable), "glade-editor-page-type",
                     GINT_TO_POINTER (type));
  g_object_set_data (G_OBJECT (editable), "glade-widget-adaptor", adaptor);

  if (type != GLADE_PAGE_PACKING)
    {
      priv->editables = g_list_prepend (priv->editables, editable);
      g_object_ref_sink (editable);
    }

  return editable;
}

static void
hide_or_remove_visible_child (GtkContainer *container, gboolean remove)
{
  GList *l, *children = gtk_container_get_children (container);
  GtkWidget *widget;

  for (l = children; l; l = l->next)
    {
      widget = l->data;

      if (gtk_widget_get_visible (widget))
        {
          gtk_widget_hide (widget);

          if (remove)
            gtk_container_remove (container, widget);

          break;
        }
    }
  g_list_free (children);
}

static GtkWidget *
glade_editor_load_editable_in_page (GladeEditor        *editor,
                                    GladeWidgetAdaptor *adaptor,
                                    GladeEditorPageType type)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);
  GtkContainer *container = NULL;
  GtkWidget *scrolled_window, *editable;
  GtkAdjustment *adj;

  /* Remove the old table that was in this container */
  switch (type)
    {
      case GLADE_PAGE_GENERAL:
        container = GTK_CONTAINER (priv->page_widget);
        break;
      case GLADE_PAGE_COMMON:
        container = GTK_CONTAINER (priv->page_common);
        break;
      case GLADE_PAGE_PACKING:
        container = GTK_CONTAINER (priv->page_packing);
        break;
      case GLADE_PAGE_ATK:
        container = GTK_CONTAINER (priv->page_atk);
        break;
      case GLADE_PAGE_QUERY:
      default:
        g_critical ("Unreachable code reached !");
        break;
    }

  /* Hide the editable (this will destroy on packing pages) */
  hide_or_remove_visible_child (container, type == GLADE_PAGE_PACKING);

  if (!adaptor)
    return NULL;

  if ((editable =
       glade_editor_get_editable_by_adaptor (editor, adaptor, type)) == NULL)
    return NULL;

  /* Attach the new page */
  if (!gtk_widget_get_parent (editable))
    gtk_container_add (GTK_CONTAINER (container), editable);
  gtk_widget_show (editable);

  if ((scrolled_window = 
       gtk_widget_get_ancestor (GTK_WIDGET (container), 
                                GTK_TYPE_SCROLLED_WINDOW)) != NULL)
    {
      adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window));
      gtk_container_set_focus_vadjustment (GTK_CONTAINER (editable), adj);

      adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window));
      gtk_container_set_focus_hadjustment (GTK_CONTAINER (editable), adj);
    }

  return editable;
}

static void
glade_editor_load_widget_class (GladeEditor *editor,
                                GladeWidgetAdaptor *adaptor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  glade_editor_load_editable_in_page (editor, adaptor, GLADE_PAGE_GENERAL);
  glade_editor_load_editable_in_page (editor, adaptor, GLADE_PAGE_COMMON);
  glade_editor_load_editable_in_page (editor, adaptor, GLADE_PAGE_ATK);
  glade_editor_load_editable_in_page (editor, NULL, GLADE_PAGE_PACKING);

  priv->loaded_adaptor = adaptor;
}

static void
glade_editor_close_cb (GladeProject *project, GladeEditor *editor)
{
  /* project we are viewing was closed,
   * detouch from editor.
   */
  glade_editor_load_widget (editor, NULL);
}

static void
glade_editor_removed_cb (GladeProject *project,
                         GladeWidget *widget,
                         GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  /* Widget we were viewing was removed from project,
   * detouch from editor.
   */
  if (widget == priv->loaded_widget)
    glade_editor_load_widget (editor, NULL);

}


static void
glade_editor_load_editable (GladeEditor        *editor,
                            GladeWidget        *widget,
                            GladeEditorPageType type)
{
  GtkWidget   *editable;
  GladeWidget *parent = glade_widget_get_parent (widget);

  /* Use the parenting adaptor for packing pages... so deffer creating the widgets
   * until load time.
   */
  if (type == GLADE_PAGE_PACKING)
    {
      GladeWidgetAdaptor *adaptor;

      if (!parent)
        return;

      adaptor = glade_widget_get_adaptor (parent);
      editable =
        glade_editor_load_editable_in_page (editor, adaptor,
                                            GLADE_PAGE_PACKING);
    }
  else
    editable = 
      glade_editor_get_editable_by_adaptor (editor, 
                                            glade_widget_get_adaptor (widget), 
                                            type);

  g_assert (editable);

  if (!widget)
    gtk_widget_hide (editable);

  glade_editable_load (GLADE_EDITABLE (editable), widget);

  if (widget)
    gtk_widget_show (editable);
}

static void
clear_editables (GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);
  GladeEditable *editable;
  GList *l;

  for (l = priv->editables; l; l = l->next)
    {
      editable = l->data;
      glade_editable_load (editable, NULL);
    }
}

static void
glade_editor_load_widget_real (GladeEditor *editor, GladeWidget *widget)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);
  GladeWidgetAdaptor *adaptor;
  GladeProject *project;

  /* Disconnect from last widget */
  if (priv->loaded_widget != NULL)
    {
      /* better pay a small price now and avoid unseen editables
       * waking up on project metadata changes.
       */
      clear_editables (editor);

      project = glade_widget_get_project (priv->loaded_widget);
      g_signal_handler_disconnect (G_OBJECT (project),
                                   priv->project_closed_signal_id);
      g_signal_handler_disconnect (G_OBJECT (project),
                                   priv->project_removed_signal_id);
      g_signal_handler_disconnect (G_OBJECT (priv->loaded_widget),
                                   priv->widget_warning_id);
      g_signal_handler_disconnect (G_OBJECT (priv->loaded_widget),
                                   priv->widget_name_id);
    }

  /* Load the GladeWidgetAdaptor */
  adaptor = widget ? glade_widget_get_adaptor (widget) : NULL;
  if (priv->loaded_adaptor != adaptor || adaptor == NULL)
    glade_editor_load_widget_class (editor, adaptor);

  glade_signal_editor_load_widget (priv->signal_editor, widget);

  /* we are just clearing, we are done */
  if (widget == NULL)
    {
      priv->loaded_widget = NULL;

      /* Clear class header */
      glade_editor_update_class_field (editor);

      g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_WIDGET]);

      return;
    }

  priv->loading = TRUE;

  /* Load each GladeEditorProperty from 'widget' */
  glade_editor_load_editable (editor, widget, GLADE_PAGE_GENERAL);
  glade_editor_load_editable (editor, widget, GLADE_PAGE_COMMON);
  glade_editor_load_editable (editor, widget, GLADE_PAGE_ATK);
  glade_editor_load_editable (editor, widget, GLADE_PAGE_PACKING);

  priv->loaded_widget = widget;
  priv->loading = FALSE;

  /* Update class header */
  glade_editor_update_class_field (editor);

  /* Connect to new widget */
  project = glade_widget_get_project (priv->loaded_widget);
  priv->project_closed_signal_id =
      g_signal_connect (G_OBJECT (project), "close",
                        G_CALLBACK (glade_editor_close_cb), editor);
  priv->project_removed_signal_id =
      g_signal_connect (G_OBJECT (project), "remove-widget",
                        G_CALLBACK (glade_editor_removed_cb), editor);
  priv->widget_warning_id =
      g_signal_connect (G_OBJECT (widget), "notify::support-warning",
                        G_CALLBACK (glade_editor_update_class_warning_cb),
                        editor);
  priv->widget_name_id =
      g_signal_connect (G_OBJECT (widget), "notify::name",
                        G_CALLBACK (glade_editor_update_widget_name_cb),
                        editor);

  g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_WIDGET]);
}


/**
 * glade_editor_new:
 *
 * Returns: a new #GladeEditor
 */
GladeEditor *
glade_editor_new (void)
{
  GladeEditor *editor;

  editor = g_object_new (GLADE_TYPE_EDITOR, "spacing", 6, NULL);

  return editor;
}

/**
 * glade_editor_load_widget:
 * @editor: a #GladeEditor
 * @widget: a #GladeWidget
 *
 * Load @widget into @editor. If @widget is %NULL, clear the editor.
 */
void
glade_editor_load_widget (GladeEditor *editor, GladeWidget *widget)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  g_return_if_fail (GLADE_IS_EDITOR (editor));
  g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget));

  if (priv->loaded_widget == widget)
    return;

  glade_editor_load_widget_real (editor, widget);
}

static void
query_dialog_style_set_cb (GtkWidget *dialog,
                           GtkStyle *previous_style,
                           gpointer user_data)
{
  GtkWidget *content_area, *action_area;

  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);
}

static gboolean
query_dialog_delete_event_cb (GtkDialog *dialog,
                              GdkEvent  *event,
                              gpointer   user_data)
{
  gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL);
  return TRUE;
}

gboolean
glade_editor_query_dialog (GladeWidget *widget)
{
  GladeWidgetAdaptor *adaptor;
  GtkWidget *dialog, *editable, *content_area;
  GtkWidget *create;
  gchar *title;
  gint answer;
  gboolean retval = TRUE;

  g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE);
  
  adaptor = glade_widget_get_adaptor (widget);

  title = g_strdup_printf (_("Create a %s"), glade_widget_adaptor_get_display_name (adaptor));
  dialog = gtk_dialog_new_with_buttons (title, NULL,
                                        GTK_DIALOG_MODAL |
                                        GTK_DIALOG_DESTROY_WITH_PARENT,
                                        _("_Cancel"), GTK_RESPONSE_CANCEL,
                                        NULL);
  g_free (title);

  create = gtk_button_new_with_mnemonic (_("Crea_te"));
  gtk_widget_show (create);
  gtk_widget_set_can_default (create, TRUE);
  gtk_dialog_add_action_widget (GTK_DIALOG (dialog), create, GTK_RESPONSE_OK);

  gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
                                           GTK_RESPONSE_OK,
                                           GTK_RESPONSE_CANCEL, -1);
  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);

  editable = (GtkWidget *) glade_widget_adaptor_create_editable (adaptor, GLADE_PAGE_QUERY);
  gtk_widget_show (editable);

  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
  gtk_box_pack_start (GTK_BOX (content_area), editable, FALSE, FALSE, 6);

  glade_editable_load (GLADE_EDITABLE (editable), widget);

  g_signal_connect (dialog, "style-set",
                    G_CALLBACK (query_dialog_style_set_cb), NULL);

  g_signal_connect (dialog, "delete-event",
                    G_CALLBACK (query_dialog_delete_event_cb), NULL);

  answer = gtk_dialog_run (GTK_DIALOG (dialog));

  /*
   * If user cancel's we cancel the whole "create operation" by
   * return FALSE. glade_widget_new() will see the FALSE, and
   * take care of canceling the "create" operation.
   */
  if (answer == GTK_RESPONSE_CANCEL)
    retval = FALSE;

  gtk_widget_destroy (dialog);
  return retval;
}

enum
{
  COLUMN_ENABLED = 0,
  COLUMN_PROP_NAME,
  COLUMN_PROPERTY,
  COLUMN_WEIGHT,
  COLUMN_CHILD,
  COLUMN_DEFAULT,
  COLUMN_NDEFAULT,
  COLUMN_DEFSTRING,
  NUM_COLUMNS
};


static void
glade_editor_reset_toggled (GtkCellRendererToggle *cell,
                            gchar                 *path_str,
                            GtkTreeModel          *model)
{
  GtkTreePath *path = gtk_tree_path_new_from_string (path_str);
  GtkTreeIter iter;
  gboolean enabled;

  /* get toggled iter */
  gtk_tree_model_get_iter (model, &iter, path);
  gtk_tree_model_get (model, &iter, COLUMN_ENABLED, &enabled, -1);
  gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
                      COLUMN_ENABLED, !enabled, -1);
  gtk_tree_path_free (path);
}

static GtkWidget *
glade_editor_reset_view (void)
{
  GtkWidget *view_widget;
  GtkTreeModel *model;
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;

  model = (GtkTreeModel *) gtk_tree_store_new (NUM_COLUMNS, G_TYPE_BOOLEAN,     /* Enabled  value      */
                                               G_TYPE_STRING,   /* Property name       */
                                               GLADE_TYPE_PROPERTY,     /* The property        */
                                               G_TYPE_INT,      /* Parent node ?       */
                                               G_TYPE_BOOLEAN,  /* Child node ?        */
                                               G_TYPE_BOOLEAN,  /* Has default value   */
                                               G_TYPE_BOOLEAN,  /* Doesn't have default */
                                               G_TYPE_STRING);  /* Default string      */

  view_widget = gtk_tree_view_new_with_model (model);
  g_object_set (G_OBJECT (view_widget), "enable-search", FALSE, NULL);

  /********************* fake invisible column *********************/
  renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (renderer), "editable", FALSE, "visible", FALSE, NULL);

  column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL);
  gtk_tree_view_append_column (GTK_TREE_VIEW (view_widget), column);

  gtk_tree_view_column_set_visible (column, FALSE);
  gtk_tree_view_set_expander_column (GTK_TREE_VIEW (view_widget), column);

  /************************ enabled column ************************/
  renderer = gtk_cell_renderer_toggle_new ();
  g_object_set (G_OBJECT (renderer),
                "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
                "activatable", TRUE, NULL);
  g_signal_connect (renderer, "toggled",
                    G_CALLBACK (glade_editor_reset_toggled), model);
  gtk_tree_view_insert_column_with_attributes
      (GTK_TREE_VIEW (view_widget), COLUMN_ENABLED,
       _("Reset"), renderer,
       "sensitive", COLUMN_NDEFAULT,
       "activatable", COLUMN_NDEFAULT,
       "active", COLUMN_ENABLED, "visible", COLUMN_CHILD, NULL);

  /********************* property name column *********************/
  renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (renderer), "editable", FALSE, NULL);

  gtk_tree_view_insert_column_with_attributes
      (GTK_TREE_VIEW (view_widget), COLUMN_PROP_NAME,
       _("Property"), renderer,
       "text", COLUMN_PROP_NAME, "weight", COLUMN_WEIGHT, NULL);

  /******************* default indicator column *******************/
  renderer = gtk_cell_renderer_text_new ();
  g_object_set (G_OBJECT (renderer),
                "editable", FALSE,
                "style", PANGO_STYLE_ITALIC, "foreground", "Gray", NULL);

  gtk_tree_view_insert_column_with_attributes
      (GTK_TREE_VIEW (view_widget), COLUMN_DEFSTRING,
       NULL, renderer,
       "text", COLUMN_DEFSTRING, "visible", COLUMN_DEFAULT, NULL);

  return view_widget;
}

static void
glade_editor_populate_reset_view (GladeWidget *widget, GtkTreeView *tree_view)
{
  GtkTreeStore *model = GTK_TREE_STORE (gtk_tree_view_get_model (tree_view));
  GtkTreeIter property_iter, general_iter, common_iter, atk_iter, *iter;
  GList *list;
  GladeProperty *property;
  GladePropertyDef *pdef;
  gboolean def;

  g_return_if_fail (widget != NULL);

  gtk_tree_store_append (model, &general_iter, NULL);
  gtk_tree_store_set (model, &general_iter,
                      COLUMN_PROP_NAME, _("General"),
                      COLUMN_PROPERTY, NULL,
                      COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
                      COLUMN_CHILD, FALSE,
                      COLUMN_DEFAULT, FALSE, COLUMN_NDEFAULT, FALSE, -1);

  gtk_tree_store_append (model, &common_iter, NULL);
  gtk_tree_store_set (model, &common_iter,
                      COLUMN_PROP_NAME, _("Common"),
                      COLUMN_PROPERTY, NULL,
                      COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
                      COLUMN_CHILD, FALSE,
                      COLUMN_DEFAULT, FALSE, COLUMN_NDEFAULT, FALSE, -1);

  gtk_tree_store_append (model, &atk_iter, NULL);
  gtk_tree_store_set (model, &atk_iter,
                      COLUMN_PROP_NAME, _("Accessibility"),
                      COLUMN_PROPERTY, NULL,
                      COLUMN_WEIGHT, PANGO_WEIGHT_BOLD,
                      COLUMN_CHILD, FALSE,
                      COLUMN_DEFAULT, FALSE, COLUMN_NDEFAULT, FALSE, -1);

  /* General & Common */
  for (list = glade_widget_get_properties (widget); list; list = list->next)
    {
      property = list->data;
      pdef     = glade_property_get_def (property);

      if (glade_property_def_is_visible (pdef) == FALSE)
        continue;

      if (glade_property_def_atk (pdef))
        iter = &atk_iter;
      else if (glade_property_def_common (pdef))
        iter = &common_iter;
      else
        iter = &general_iter;

      def = glade_property_default (property);

      gtk_tree_store_append (model, &property_iter, iter);
      gtk_tree_store_set (model, &property_iter,
                          COLUMN_ENABLED, !def,
                          COLUMN_PROP_NAME, glade_property_def_get_name (pdef),
                          COLUMN_PROPERTY, property,
                          COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL,
                          COLUMN_CHILD, TRUE,
                          COLUMN_DEFAULT, def,
                          COLUMN_NDEFAULT, !def,
                          COLUMN_DEFSTRING, _("(default)"), -1);
    }
}

static gboolean
glade_editor_reset_selection_changed_cb (GtkTreeSelection *selection,
                                         GtkTextView      *desc_view)
{
  GtkTreeIter iter;
  GladeProperty *property = NULL;
  GtkTreeModel *model = NULL;
  GtkTextBuffer *text_buffer;
  GladePropertyDef *pdef = NULL;

  const gchar *message =
      _("Select the properties that you want to reset to their default values");

  /* Find selected data and show property blurb here */
  if (gtk_tree_selection_get_selected (selection, &model, &iter))
    {
      text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (desc_view));
      gtk_tree_model_get (model, &iter, COLUMN_PROPERTY, &property, -1);

      if (property)
        pdef = glade_property_get_def (property);

      gtk_text_buffer_set_text (text_buffer,
                                pdef ? glade_property_def_get_tooltip (pdef) : message,
                                -1);
      if (property)
        g_object_unref (G_OBJECT (property));
    }
  return TRUE;
}

static gboolean
glade_editor_reset_foreach_selection (GtkTreeModel *model,
                                      GtkTreePath  *path,
                                      GtkTreeIter  *iter,
                                      gpointer      data)
{
  gboolean select = GPOINTER_TO_INT (data);
  gboolean def;

  gtk_tree_model_get (model, iter, COLUMN_DEFAULT, &def, -1);
  /* Dont modify rows that are already default */
  if (def == FALSE)
    gtk_tree_store_set (GTK_TREE_STORE (model), iter,
                        COLUMN_ENABLED, select, -1);
  return FALSE;
}


static void
glade_editor_reset_select_all_clicked (GtkButton   *button,
                                       GtkTreeView *tree_view)
{
  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
  gtk_tree_model_foreach (model,
                          glade_editor_reset_foreach_selection,
                          GINT_TO_POINTER (TRUE));
}

static void
glade_editor_reset_unselect_all_clicked (GtkButton   *button,
                                         GtkTreeView *tree_view)
{
  GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
  gtk_tree_model_foreach (model,
                          glade_editor_reset_foreach_selection,
                          GINT_TO_POINTER (FALSE));
}

static gboolean
glade_editor_reset_accumulate_selected_props (GtkTreeModel *model,
                                              GtkTreePath  *path,
                                              GtkTreeIter  *iter,
                                              gpointer      data)
{
  GList **accum = data;
  GladeProperty *property;
  gboolean enabled, def;

  gtk_tree_model_get (model, iter,
                      COLUMN_PROPERTY, &property,
                      COLUMN_ENABLED, &enabled, COLUMN_DEFAULT, &def, -1);

  if (property && enabled && !def)
    *accum = g_list_prepend (*accum, property);


  if (property)
    g_object_unref (G_OBJECT (property));

  return FALSE;
}

static GList *
glade_editor_reset_get_selected_props (GtkTreeModel *model)
{
  GList *ret = NULL;

  gtk_tree_model_foreach (model,
                          glade_editor_reset_accumulate_selected_props,
                          &ret);

  return ret;
}

static void
glade_editor_reset_properties (GList *props)
{
  GList *list, *sdata_list = NULL;
  GladeCommandSetPropData *sdata;
  GladeProperty *prop;
  GladeWidget   *widget;
  GladeProject *project = NULL;

  for (list = props; list; list = list->next)
    {
      prop    = list->data;
      widget  = glade_property_get_widget (prop);
      project = glade_widget_get_project (widget);

      sdata = g_new (GladeCommandSetPropData, 1);
      sdata->property = prop;

      sdata->old_value = g_new0 (GValue, 1);
      sdata->new_value = g_new0 (GValue, 1);

      glade_property_get_value (prop, sdata->old_value);
      glade_property_get_default (prop, sdata->new_value);

      sdata_list = g_list_prepend (sdata_list, sdata);
    }

  if (project)
    /* GladeCommand takes ownership of allocated list, ugly but practical */
    glade_command_set_properties_list (project, sdata_list);

}

void
glade_editor_reset_dialog_run (GtkWidget *parent, GladeWidget *gwidget)
{
  GtkTreeSelection *selection;
  GtkWidget *dialog;
  GtkWidget *vbox, *hbox, *label, *sw, *button;
  GtkWidget *tree_view, *description_view;
  gint res;
  GList *list;

  dialog = gtk_dialog_new_with_buttons (_("Reset Widget Properties"),
                                        parent ? GTK_WINDOW (parent) : NULL,
                                        GTK_DIALOG_MODAL |
                                        GTK_DIALOG_DESTROY_WITH_PARENT,
                                        _("_Cancel"), GTK_RESPONSE_CANCEL,
                                        _("_OK"), GTK_RESPONSE_OK, NULL);

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

  gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);

  gtk_box_pack_start (GTK_BOX
                      (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), vbox,
                      TRUE, TRUE, 0);

  /* Checklist */
  label = gtk_label_new_with_mnemonic (_("_Properties:"));
  gtk_widget_show (label);
  gtk_widget_set_halign (label, GTK_ALIGN_START);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (sw);
  gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
  gtk_widget_set_size_request (sw, 400, 200);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);


  tree_view = glade_editor_reset_view ();
  if (gwidget)
    glade_editor_populate_reset_view (gwidget, GTK_TREE_VIEW (tree_view));
  gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view));

  gtk_widget_show (tree_view);
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), tree_view);
  gtk_container_add (GTK_CONTAINER (sw), tree_view);

  /* Select all / Unselect all */
  hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
  gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
  gtk_widget_show (hbox);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0);

  button = gtk_button_new_with_mnemonic (_("_Select All"));
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (button), 6);
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (glade_editor_reset_select_all_clicked),
                    tree_view);

  button = gtk_button_new_with_mnemonic (_("_Unselect All"));
  gtk_widget_show (button);
  gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (button), 6);
  g_signal_connect (G_OBJECT (button), "clicked",
                    G_CALLBACK (glade_editor_reset_unselect_all_clicked),
                    tree_view);


  /* Description */
  label = gtk_label_new_with_mnemonic (_("Property _Description:"));
  gtk_widget_show (label);
  gtk_widget_set_halign (label, GTK_ALIGN_START);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);

  sw = gtk_scrolled_window_new (NULL, NULL);
  gtk_widget_show (sw);
  gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0);
  gtk_widget_set_size_request (sw, 400, 80);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);

  description_view = gtk_text_view_new ();
  gtk_text_view_set_editable (GTK_TEXT_VIEW (description_view), FALSE);
  gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (description_view), GTK_WRAP_WORD);

  gtk_widget_show (description_view);
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), description_view);
  gtk_container_add (GTK_CONTAINER (sw), description_view);

  /* Update description */
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
  g_signal_connect (G_OBJECT (selection), "changed",
                    G_CALLBACK (glade_editor_reset_selection_changed_cb),
                    description_view);



  /* Run the dialog */
  res = gtk_dialog_run (GTK_DIALOG (dialog));
  if (res == GTK_RESPONSE_OK)
    {

      /* get all selected properties and reset properties through glade_command */
      if ((list = glade_editor_reset_get_selected_props
           (gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)))) != NULL)
        {
          glade_editor_reset_properties (list);
          g_list_free (list);
        }
    }
  gtk_widget_destroy (dialog);
}

void
glade_editor_show_info (GladeEditor *editor)
{
  g_warning ("%s function is deprecated and does nothing", __func__);
}

void
glade_editor_hide_info (GladeEditor *editor)
{
  g_warning ("%s function is deprecated and does nothing", __func__);
}

void
glade_editor_show_class_field (GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  g_return_if_fail (GLADE_IS_EDITOR (editor));

  if (priv->show_class_field != TRUE)
    {
      priv->show_class_field = TRUE;
      gtk_widget_show (priv->class_field);

      g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_SHOW_CLASS_FIELD]);
    }
}

void
glade_editor_hide_class_field (GladeEditor *editor)
{
  GladeEditorPrivate *priv = glade_editor_get_instance_private (editor);

  g_return_if_fail (GLADE_IS_EDITOR (editor));

  if (priv->show_class_field != FALSE)
    {
      priv->show_class_field = FALSE;
      gtk_widget_hide (priv->class_field);

      g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_SHOW_CLASS_FIELD]);
    }
}

static void
editor_widget_name_changed (GladeWidget *widget,
                            GParamSpec  *pspec,
                            GtkWindow   *window)
{
  gchar *title, *prj_name;

  prj_name = glade_project_get_name (glade_widget_get_project (widget));
  /* Translators: first %s is the project name, second is a widget name */
  title = g_strdup_printf (_("%s - %s Properties"), prj_name,
                           glade_widget_get_display_name (widget));
  gtk_window_set_title (window, title);
  g_free (title);
  g_free (prj_name);
}

/**
 * glade_editor_dialog_for_widget:
 * @widget: a #GladeWidget
 *
 * This convenience function creates a new dialog window to edit @widget
 * specifically.
 *
 * Returns: (transfer full): the newly created dialog window
 */
GtkWidget *
glade_editor_dialog_for_widget (GladeWidget *widget)
{
  GtkWidget *window, *editor;

  g_return_val_if_fail (GLADE_IS_WIDGET (widget), NULL);

  /* Window */
  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_UTILITY);

  /* Keep the title up to date */
  editor_widget_name_changed (widget, NULL, GTK_WINDOW (window));
  g_signal_connect_object (G_OBJECT (widget), "notify::name",
                           G_CALLBACK (editor_widget_name_changed), window, 0);

  if (glade_app_get_accel_group ())
    {
      gtk_window_add_accel_group (GTK_WINDOW (window),
                                  glade_app_get_accel_group ());
      g_signal_connect (G_OBJECT (window), "key-press-event",
                        G_CALLBACK (glade_utils_hijack_key_press), NULL);
    }

  editor = (GtkWidget *)glade_editor_new ();
  glade_editor_load_widget (GLADE_EDITOR (editor), widget);

  g_signal_connect_swapped (G_OBJECT (editor), "notify::widget",
                            G_CALLBACK (gtk_widget_destroy), window);

  gtk_container_set_border_width (GTK_CONTAINER (editor), 6);
  gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor));

  gtk_window_set_default_size (GTK_WINDOW (window), 400, 480);

  gtk_widget_show (editor);

  return window;
}
