/* LIBGIMP - The GIMP Library
 * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
 *
 * gimpquerybox.c
 * Copyright (C) 1999-2000 Michael Natterer <mitch@gimp.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <gtk/gtk.h>

#include "libgimpbase/gimpbase.h"

#include "gimpwidgetstypes.h"

#include "gimpdialog.h"
#include "gimppixmap.h"
#include "gimpquerybox.h"
#include "gimpsizeentry.h"
#include "gimpwidgets.h"

#include "libgimp/libgimp-intl.h"


/*
 *  String, integer, double and size query boxes
 */

typedef struct _QueryBox QueryBox;

struct _QueryBox
{
  GtkWidget *qbox;
  GtkWidget *vbox;
  GtkWidget *entry;
  GObject   *object;
  gulong     response_handler;
  GCallback  callback;
  gpointer   callback_data;
};


static QueryBox * create_query_box             (const gchar   *title,
                                                GtkWidget     *parent,
						GimpHelpFunc   help_func,
						const gchar   *help_id,
						GCallback      response_callback,
                                                const gchar   *stock_id,
						const gchar   *message,
						const gchar   *ok_button,
						const gchar   *cancel_button,
						GObject       *object,
						const gchar   *signal,
						GCallback      callback,
						gpointer       callback_data);

static void       query_box_disconnect         (QueryBox      *query_box);

static void       string_query_box_response    (GtkWidget     *widget,
                                                gint           response_id,
                                                QueryBox      *query_box);
static void       int_query_box_response       (GtkWidget     *widget,
                                                gint           response_id,
                                                QueryBox      *query_box);
static void       double_query_box_response    (GtkWidget     *widget,
                                                gint           response_id,
                                                QueryBox      *query_box);
static void       size_query_box_response      (GtkWidget     *widget,
                                                gint           response_id,
                                                QueryBox      *query_box);
static void       boolean_query_box_response   (GtkWidget     *widget,
                                                gint           response_id,
                                                QueryBox      *query_box);

static void       query_box_cancel_callback    (QueryBox      *query_box);


/*
 *  create a generic query box without any entry widget
 */
static QueryBox *
create_query_box (const gchar   *title,
                  GtkWidget     *parent,
		  GimpHelpFunc   help_func,
		  const gchar   *help_id,
		  GCallback      response_callback,
                  const gchar   *stock_id,
		  const gchar   *message,
		  const gchar   *ok_button,
		  const gchar   *cancel_button,
		  GObject       *object,
		  const gchar   *signal,
		  GCallback      callback,
		  gpointer       callback_data)
{
  QueryBox  *query_box;
  GtkWidget *hbox = NULL;
  GtkWidget *label;

  /*  make sure the object / signal passed are valid
   */
  g_return_val_if_fail (parent == NULL || GTK_IS_WIDGET (parent), NULL);
  g_return_val_if_fail (object == NULL || G_IS_OBJECT (object), NULL);
  g_return_val_if_fail (object == NULL || signal != NULL, NULL);

  query_box = g_new0 (QueryBox, 1);

  query_box->qbox = gimp_dialog_new (title, "gimp-query-box",
                                     parent, 0,
				     help_func, help_id,

				     cancel_button, GTK_RESPONSE_CANCEL,
				     ok_button,     GTK_RESPONSE_OK,

				     NULL);

  query_box->response_handler =
    g_signal_connect (query_box->qbox, "response",
                      G_CALLBACK (response_callback),
                      query_box);

  g_signal_connect (query_box->qbox, "destroy",
		    G_CALLBACK (gtk_widget_destroyed),
		    &query_box->qbox);

  /*  if we are associated with an object, connect to the provided signal
   */
  if (object)
    {
      GClosure *closure;

      closure = g_cclosure_new_swap (G_CALLBACK (query_box_cancel_callback),
                                     query_box, NULL);
      g_object_watch_closure (G_OBJECT (query_box->qbox), closure);

      g_signal_connect_closure (object, signal, closure, FALSE);
    }

  if (stock_id)
    {
      GtkWidget *image;

      hbox = gtk_hbox_new (FALSE, 12);
      gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
      gtk_container_add (GTK_CONTAINER (GTK_DIALOG (query_box->qbox)->vbox),
                         hbox);
      gtk_widget_show (hbox);

      image = gtk_image_new_from_stock (stock_id, GTK_ICON_SIZE_DIALOG);
      gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
      gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
      gtk_widget_show (image);
    }

  query_box->vbox = gtk_vbox_new (FALSE, 12);

  g_object_set_data (G_OBJECT (query_box->qbox), "gimp-query-box-vbox",
                     query_box->vbox);

  if (hbox)
    {
      gtk_box_pack_start (GTK_BOX (hbox), query_box->vbox, FALSE, FALSE, 0);
    }
  else
    {
      gtk_container_set_border_width (GTK_CONTAINER (query_box->vbox), 12);
      gtk_container_add (GTK_CONTAINER (GTK_DIALOG (query_box->qbox)->vbox),
                         query_box->vbox);
    }

  gtk_widget_show (query_box->vbox);

  if (message)
    {
      label = gtk_label_new (message);
      gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
      gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
      gtk_box_pack_start (GTK_BOX (query_box->vbox), label, FALSE, FALSE, 0);
      gtk_widget_show (label);
    }

  query_box->entry         = NULL;
  query_box->object        = object;
  query_box->callback      = callback;
  query_box->callback_data = callback_data;

  return query_box;
}

/**
 * gimp_query_string_box:
 * @title:     The query box dialog's title.
 * @parent:    The dialog's parent widget.
 * @help_func: The help function to show this dialog's help page.
 * @help_id:   A string identifying this dialog's help page.
 * @message:   A string which will be shown above the dialog's entry widget.
 * @initial:   The initial value.
 * @object:    The object this query box is associated with.
 * @signal:    The object's signal which will cause the query box to be closed.
 * @callback:  The function which will be called when the user selects "OK".
 * @data:      The callback's user data.
 *
 * Creates a new #GtkDialog that queries the user for a string value.
 *
 * Returns: A pointer to the new #GtkDialog.
 **/
GtkWidget *
gimp_query_string_box (const gchar             *title,
                       GtkWidget               *parent,
		       GimpHelpFunc             help_func,
		       const gchar             *help_id,
		       const gchar             *message,
		       const gchar             *initial,
		       GObject                 *object,
		       const gchar             *signal,
		       GimpQueryStringCallback  callback,
		       gpointer                 data)
{
  QueryBox  *query_box;
  GtkWidget *entry;

  query_box = create_query_box (title, parent, help_func, help_id,
				G_CALLBACK (string_query_box_response),
                                GTK_STOCK_DIALOG_QUESTION,
				message,
				GTK_STOCK_OK, GTK_STOCK_CANCEL,
				object, signal,
				G_CALLBACK (callback), data);

  if (! query_box)
    return NULL;

  entry = gtk_entry_new ();
  gtk_entry_set_text (GTK_ENTRY (entry), initial ? initial : "");
  gtk_entry_set_activates_default (GTK_ENTRY (entry), TRUE);
  gtk_box_pack_start (GTK_BOX (query_box->vbox), entry, FALSE, FALSE, 0);
  gtk_widget_grab_focus (entry);
  gtk_widget_show (entry);

  query_box->entry = entry;

  return query_box->qbox;
}

/**
 * gimp_query_int_box:
 * @title:     The query box dialog's title.
 * @parent:    The dialog's parent widget.
 * @help_func: The help function to show this dialog's help page.
 * @help_id:   A string identifying this dialog's help page.
 * @message:   A string which will be shown above the dialog's entry widget.
 * @initial:   The initial value.
 * @lower:     The lower boundary of the range of possible values.
 * @upper:     The upper boundray of the range of possible values.
 * @object:    The object this query box is associated with.
 * @signal:    The object's signal which will cause the query box to be closed.
 * @callback:  The function which will be called when the user selects "OK".
 * @data:      The callback's user data.
 *
 * Creates a new #GtkDialog that queries the user for an integer value.
 *
 * Returns: A pointer to the new #GtkDialog.
 **/
GtkWidget *
gimp_query_int_box (const gchar          *title,
                    GtkWidget            *parent,
		    GimpHelpFunc          help_func,
		    const gchar          *help_id,
		    const gchar          *message,
		    gint                  initial,
		    gint                  lower,
		    gint                  upper,
		    GObject              *object,
		    const gchar          *signal,
		    GimpQueryIntCallback  callback,
		    gpointer              data)
{
  QueryBox  *query_box;
  GtkWidget *spinbutton;
  GtkObject *adjustment;

  query_box = create_query_box (title, parent, help_func, help_id,
				G_CALLBACK (int_query_box_response),
                                GTK_STOCK_DIALOG_QUESTION,
				message,
				GTK_STOCK_OK, GTK_STOCK_CANCEL,
				object, signal,
				G_CALLBACK (callback), data);

  if (! query_box)
    return NULL;

  spinbutton = gimp_spin_button_new (&adjustment,
				     initial, lower, upper, 1, 10, 0,
				     1, 0);
  gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (query_box->vbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_grab_focus (spinbutton);
  gtk_widget_show (spinbutton);

  query_box->entry = spinbutton;

  return query_box->qbox;
}

/**
 * gimp_query_double_box:
 * @title:     The query box dialog's title.
 * @parent:    The dialog's parent widget.
 * @help_func: The help function to show this dialog's help page.
 * @help_id:   A string identifying this dialog's help page.
 * @message:   A string which will be shown above the dialog's entry widget.
 * @initial:   The initial value.
 * @lower:     The lower boundary of the range of possible values.
 * @upper:     The upper boundray of the range of possible values.
 * @digits:    The number of decimal digits the #GtkSpinButton will provide.
 * @object:    The object this query box is associated with.
 * @signal:    The object's signal which will cause the query box to be closed.
 * @callback:  The function which will be called when the user selects "OK".
 * @data:      The callback's user data.
 *
 * Creates a new #GtkDialog that queries the user for a double value.
 *
 * Returns: A pointer to the new #GtkDialog.
 **/
GtkWidget *
gimp_query_double_box (const gchar             *title,
                       GtkWidget               *parent,
		       GimpHelpFunc             help_func,
		       const gchar             *help_id,
		       const gchar             *message,
		       gdouble                  initial,
		       gdouble                  lower,
		       gdouble                  upper,
		       gint                     digits,
		       GObject                 *object,
		       const gchar             *signal,
		       GimpQueryDoubleCallback  callback,
		       gpointer                 data)
{
  QueryBox  *query_box;
  GtkWidget *spinbutton;
  GtkObject *adjustment;

  query_box = create_query_box (title, parent, help_func, help_id,
				G_CALLBACK (double_query_box_response),
                                GTK_STOCK_DIALOG_QUESTION,
				message,
				GTK_STOCK_OK, GTK_STOCK_CANCEL,
				object, signal,
				G_CALLBACK (callback), data);

  if (! query_box)
    return NULL;

  spinbutton = gimp_spin_button_new (&adjustment,
				     initial, lower, upper, 1, 10, 0,
				     1, digits);
  gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE);
  gtk_box_pack_start (GTK_BOX (query_box->vbox), spinbutton, FALSE, FALSE, 0);
  gtk_widget_grab_focus (spinbutton);
  gtk_widget_show (spinbutton);

  query_box->entry = spinbutton;

  return query_box->qbox;
}

/**
 * gimp_query_size_box:
 * @title:       The query box dialog's title.
 * @parent:      The dialog's parent widget.
 * @help_func:   The help function to show this dialog's help page.
 * @help_id:     A string identifying this dialog's help page.
 * @message:     A string which will be shown above the dialog's entry widget.
 * @initial:     The initial value.
 * @lower:       The lower boundary of the range of possible values.
 * @upper:       The upper boundray of the range of possible values.
 * @digits:      The number of decimal digits the #GimpSizeEntry provide in
 *               "pixel" mode.
 * @unit:        The unit initially shown by the #GimpUnitMenu.
 * @resolution:  The resolution (in dpi) which will be used for pixel/unit
 *               calculations.
 * @dot_for_dot: %TRUE if the #GimpUnitMenu's initial unit should be "pixels".
 * @object:      The object this query box is associated with.
 * @signal:      The object's signal which will cause the query box
 *               to be closed.
 * @callback:    The function which will be called when the user selects "OK".
 * @data:        The callback's user data.
 *
 * Creates a new #GtkDialog that queries the user for a size using a
 * #GimpSizeEntry.
 *
 * Returns: A pointer to the new #GtkDialog.
 **/
GtkWidget *
gimp_query_size_box (const gchar           *title,
                     GtkWidget             *parent,
		     GimpHelpFunc           help_func,
		     const gchar           *help_id,
		     const gchar           *message,
		     gdouble                initial,
		     gdouble                lower,
		     gdouble                upper,
		     gint                   digits,
		     GimpUnit               unit,
		     gdouble                resolution,
		     gboolean               dot_for_dot,
		     GObject               *object,
		     const gchar           *signal,
		     GimpQuerySizeCallback  callback,
		     gpointer               data)
{
  QueryBox  *query_box;
  GtkWidget *sizeentry;
  GtkWidget *spinbutton;

  query_box = create_query_box (title, parent, help_func, help_id,
				G_CALLBACK (size_query_box_response),
                                GTK_STOCK_DIALOG_QUESTION,
				message,
				GTK_STOCK_OK, GTK_STOCK_CANCEL,
				object, signal,
				G_CALLBACK (callback), data);

  if (! query_box)
    return NULL;

  sizeentry = gimp_size_entry_new (1, unit, "%p", TRUE, FALSE, FALSE, 12,
				   GIMP_SIZE_ENTRY_UPDATE_SIZE);
  if (dot_for_dot)
    gimp_size_entry_set_unit (GIMP_SIZE_ENTRY (sizeentry), GIMP_UNIT_PIXEL);
  gimp_size_entry_set_resolution (GIMP_SIZE_ENTRY (sizeentry), 0,
				  resolution, FALSE);
  gimp_size_entry_set_refval_digits (GIMP_SIZE_ENTRY (sizeentry), 0, digits);
  gimp_size_entry_set_refval_boundaries (GIMP_SIZE_ENTRY (sizeentry), 0,
					 lower, upper);
  gimp_size_entry_set_refval (GIMP_SIZE_ENTRY (sizeentry), 0, initial);

  spinbutton = gimp_size_entry_get_help_widget (GIMP_SIZE_ENTRY (sizeentry), 0);
  gtk_entry_set_activates_default (GTK_ENTRY (spinbutton), TRUE);

  gtk_box_pack_start (GTK_BOX (query_box->vbox), sizeentry, FALSE, FALSE, 0);
  gimp_size_entry_grab_focus (GIMP_SIZE_ENTRY (sizeentry));
  gtk_widget_show (sizeentry);

  query_box->entry = sizeentry;

  return query_box->qbox;
}

/**
 * gimp_query_boolean_box:
 * @title:        The query box dialog's title.
 * @parent:       The dialog's parent widget.
 * @help_func:    The help function to show this dialog's help page.
 * @help_id:      A string identifying this dialog's help page.
 * @stock_id:     A stock_id to specify an icon to appear on the left
 *                on the dialog's message.
 * @message:      A string which will be shown in the query box.
 * @true_button:  The string to be shown in the dialog's left button.
 * @false_button: The string to be shown in the dialog's right button.
 * @object:       The object this query box is associated with.
 * @signal:       The object's signal which will cause the query box
 *                to be closed.
 * @callback:     The function which will be called when the user clicks one
 *                of the buttons.
 * @data:         The callback's user data.
 *
 * Creates a new #GtkDialog that asks the user to do a boolean decision.
 *
 * Returns: A pointer to the new #GtkDialog.
 **/
GtkWidget *
gimp_query_boolean_box (const gchar              *title,
                        GtkWidget                *parent,
			GimpHelpFunc              help_func,
			const gchar              *help_id,
			const gchar              *stock_id,
			const gchar              *message,
			const gchar              *true_button,
			const gchar              *false_button,
			GObject                  *object,
			const gchar              *signal,
			GimpQueryBooleanCallback  callback,
			gpointer                  data)
{
  QueryBox  *query_box;

  query_box = create_query_box (title, parent, help_func, help_id,
				G_CALLBACK (boolean_query_box_response),
                                stock_id,
				message,
				true_button, false_button,
				object, signal,
				G_CALLBACK (callback), data);

  if (! query_box)
    return NULL;

  return query_box->qbox;
}


/*
 *  private functions
 */

static void
query_box_disconnect (QueryBox *query_box)
{
  gtk_widget_set_sensitive (query_box->qbox, FALSE);

  /*  disconnect the response callback to avoid that it may be run twice  */
  if (query_box->response_handler)
    {
      g_signal_handler_disconnect (query_box->qbox,
                                   query_box->response_handler);

      query_box->response_handler = 0;
    }

  /*  disconnect, if we are connected to some signal  */
  if (query_box->object)
    g_signal_handlers_disconnect_by_func (query_box->object,
                                          query_box_cancel_callback,
                                          query_box);
}

static void
string_query_box_response (GtkWidget *widget,
                           gint       response_id,
                           QueryBox  *query_box)
{
  const gchar *string;

  query_box_disconnect (query_box);

  /*  Get the entry data  */
  string = gtk_entry_get_text (GTK_ENTRY (query_box->entry));

  /*  Call the user defined callback  */
  if (response_id == GTK_RESPONSE_OK)
    (* (GimpQueryStringCallback) query_box->callback) (query_box->qbox,
                                                       string,
                                                       query_box->callback_data);

  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  g_free (query_box);
}

static void
int_query_box_response (GtkWidget *widget,
                        gint       response_id,
                        QueryBox  *query_box)
{
  gint value;

  query_box_disconnect (query_box);

  /*  Get the spinbutton data  */
  value = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (query_box->entry));

  /*  Call the user defined callback  */
  if (response_id == GTK_RESPONSE_OK)
    (* (GimpQueryIntCallback) query_box->callback) (query_box->qbox,
                                                    value,
                                                    query_box->callback_data);

  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  g_free (query_box);
}

static void
double_query_box_response (GtkWidget *widget,
                           gint       response_id,
                           QueryBox  *query_box)
{
  gdouble value;

  query_box_disconnect (query_box);

  /*  Get the spinbutton data  */
  value = gtk_spin_button_get_value (GTK_SPIN_BUTTON (query_box->entry));

  /*  Call the user defined callback  */
  if (response_id == GTK_RESPONSE_OK)
    (* (GimpQueryDoubleCallback) query_box->callback) (query_box->qbox,
                                                       value,
                                                       query_box->callback_data);

  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  g_free (query_box);
}

static void
size_query_box_response (GtkWidget *widget,
                         gint       response_id,
                         QueryBox  *query_box)
{
  gdouble  size;
  GimpUnit unit;

  query_box_disconnect (query_box);

  /*  Get the sizeentry data  */
  size = gimp_size_entry_get_refval (GIMP_SIZE_ENTRY (query_box->entry), 0);
  unit = gimp_size_entry_get_unit (GIMP_SIZE_ENTRY (query_box->entry));

  /*  Call the user defined callback  */
  if (response_id == GTK_RESPONSE_OK)
    (* (GimpQuerySizeCallback) query_box->callback) (query_box->qbox,
                                                     size,
                                                     unit,
                                                     query_box->callback_data);

  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  g_free (query_box);
}

static void
boolean_query_box_response (GtkWidget *widget,
                            gint       response_id,
                            QueryBox  *query_box)
{
  query_box_disconnect (query_box);

  /*  Call the user defined callback  */
  (* (GimpQueryBooleanCallback) query_box->callback) (query_box->qbox,
						      (response_id ==
                                                       GTK_RESPONSE_OK),
						      query_box->callback_data);

  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  g_free (query_box);
}

static void
query_box_cancel_callback (QueryBox *query_box)
{
  query_box_disconnect (query_box);

  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  g_free (query_box);
}
