/* 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 3 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, see
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>

#include "libgimpbase/gimpbase.h"

#include "gimpwidgets.h"

#include "libgimp/libgimp-intl.h"


/**
 * SECTION: gimpquerybox
 * @title: GimpQueryBox
 * @short_description: Some simple dialogs to enter a single int,
 *                     double, string or boolean value.
 * @see_also: #GimpSizeEntry, #GimpUnitMenu
 *
 * These functions provide simple dialogs for entering a single
 * string, integer, double, boolean or pixel size value.
 *
 * They return a pointer to a #GtkDialog which has to be shown with
 * gtk_widget_show() by the caller.
 *
 * The dialogs contain an entry widget for the kind of value they ask
 * for and "OK" and "Cancel" buttons. On "Cancel", all query boxes
 * except the boolean one silently destroy themselves. On "OK" the
 * user defined callback function is called and returns the entered
 * value.
 **/


/*
 *  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;
  GDestroyNotify  callback_data_destroy;
};


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

static void       query_box_disconnect         (QueryBox      *query_box);
static void       query_box_destroy            (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   *icon_name,
                  const gchar   *message,
                  const gchar   *ok_button,
                  const gchar   *cancel_button,
                  GObject       *object,
                  const gchar   *signal,
                  GCallback      callback,
                  gpointer       callback_data,
                  GDestroyNotify callback_data_destroy)
{
  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_slice_new0 (QueryBox);

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

  gtk_dialog_set_default_response (GTK_DIALOG (query_box->qbox),
                                   GTK_RESPONSE_OK);
  gimp_dialog_set_alternative_button_order (GTK_DIALOG (query_box->qbox),
                                            GTK_RESPONSE_OK,
                                            GTK_RESPONSE_CANCEL,
                                           -1);

  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 (icon_name)
    {
      GtkWidget *content_area;
      GtkWidget *image;

      content_area = gtk_dialog_get_content_area (GTK_DIALOG (query_box->qbox));

      hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
      gtk_container_set_border_width (GTK_CONTAINER (hbox), 12);
      gtk_box_pack_start (GTK_BOX (content_area), hbox, TRUE, TRUE, 0);
      gtk_widget_show (hbox);

      image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_DIALOG);
      gtk_widget_set_valign (image, GTK_ALIGN_START);
      gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
      gtk_widget_show (image);
    }

  query_box->vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 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
    {
      GtkWidget *content_area;

      content_area = gtk_dialog_get_content_area (GTK_DIALOG (query_box->qbox));

      gtk_container_set_border_width (GTK_CONTAINER (query_box->vbox), 12);
      gtk_box_pack_start (GTK_BOX (content_area), query_box->vbox,
                          TRUE, TRUE, 0);
    }

  gtk_widget_show (query_box->vbox);

  if (message)
    {
      label = gtk_label_new (message);
      gtk_label_set_xalign (GTK_LABEL (label), 0.0);
      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;
  query_box->callback_data_destroy = callback_data_destroy;

  return query_box;
}

/**
 * gimp_query_string_box:
 * @title:        The query box dialog's title.
 * @parent:       The dialog's parent widget.
 * @help_func:    (scope async): 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.
 * @data_destroy: Destroy function for @data.
 *
 * Creates a new #GtkDialog that queries the user for a string value.
 *
 * Returns: (transfer full): 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,
                       GDestroyNotify           data_destroy)
{
  QueryBox  *query_box;
  GtkWidget *entry;

  query_box = create_query_box (title, parent, help_func, help_id,
                                G_CALLBACK (string_query_box_response),
                                "dialog-question",
                                message,
                                _("_OK"), _("_Cancel"),
                                object, signal,
                                G_CALLBACK (callback),
                                data, data_destroy);

  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:    (scope async): 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.
 * @data_destroy: Destroy function for @data.
 *
 * Creates a new #GtkDialog that queries the user for an integer value.
 *
 * Returns: (transfer full): 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,
                    GDestroyNotify        data_destroy)
{
  QueryBox      *query_box;
  GtkWidget     *spinbutton;
  GtkAdjustment *adjustment;

  query_box = create_query_box (title, parent, help_func, help_id,
                                G_CALLBACK (int_query_box_response),
                                "dialog-question",
                                message,
                                _("_OK"), _("_Cancel"),
                                object, signal,
                                G_CALLBACK (callback),
                                data, data_destroy);

  if (! query_box)
    return NULL;

  adjustment = gtk_adjustment_new (initial, lower, upper, 1, 10, 0);
  spinbutton = gimp_spin_button_new (adjustment, 1.0, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  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:   (scope async): 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.
 * @data_destroy: Destroy function for @data.
 *
 * Creates a new #GtkDialog that queries the user for a double value.
 *
 * Returns: (transfer full): 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,
                       GDestroyNotify           data_destroy)
{
  QueryBox      *query_box;
  GtkWidget     *spinbutton;
  GtkAdjustment *adjustment;

  query_box = create_query_box (title, parent, help_func, help_id,
                                G_CALLBACK (double_query_box_response),
                                "dialog-question",
                                message,
                                _("_OK"), _("_Cancel"),
                                object, signal,
                                G_CALLBACK (callback),
                                data, data_destroy);

  if (! query_box)
    return NULL;

  adjustment = gtk_adjustment_new (initial, lower, upper, 1, 10, 0);
  spinbutton = gimp_spin_button_new (adjustment, 1.0, 0);
  gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spinbutton), TRUE);
  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:    (scope async): 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.
 * @data_destroy: Destroy function for @data.
 *
 * Creates a new #GtkDialog that queries the user for a size using a
 * #GimpSizeEntry.
 *
 * Returns: (transfer full): 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,
                     GDestroyNotify         data_destroy)
{
  QueryBox  *query_box;
  GtkWidget *sizeentry;
  GtkWidget *spinbutton;

  query_box = create_query_box (title, parent, help_func, help_id,
                                G_CALLBACK (size_query_box_response),
                                "dialog-question",
                                message,
                                _("_OK"), _("_Cancel"),
                                object, signal,
                                G_CALLBACK (callback),
                                data, data_destroy);

  if (! query_box)
    return NULL;

  sizeentry = gimp_size_entry_new (1, unit, "%n", 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:    (scope async): The help function to show this dialog's help page.
 * @help_id:      A string identifying this dialog's help page.
 * @icon_name:    An icon name 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.
 * @data_destroy: Destroy function for @data.
 *
 * Creates a new #GtkDialog that asks the user to do a boolean decision.
 *
 * Returns: (transfer full): 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              *icon_name,
                        const gchar              *message,
                        const gchar              *true_button,
                        const gchar              *false_button,
                        GObject                  *object,
                        const gchar              *signal,
                        GimpQueryBooleanCallback  callback,
                        gpointer                  data,
                        GDestroyNotify            data_destroy)
{
  QueryBox  *query_box;

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

  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
query_box_destroy (QueryBox *query_box)
{
  /*  Destroy the box  */
  if (query_box->qbox)
    gtk_widget_destroy (query_box->qbox);

  if (query_box->callback_data_destroy)
    query_box->callback_data_destroy (query_box->callback_data);

  g_slice_free (QueryBox, 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);

  query_box_destroy (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);

  query_box_destroy (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);

  query_box_destroy (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);

  query_box_destroy (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);

  query_box_destroy (query_box);
}

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