PIKApp/libpikawidgets/pikaquerybox.c

713 lines
27 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/* LIBPIKA - The PIKA Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* pikaquerybox.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 "libpikabase/pikabase.h"
#include "pikawidgets.h"
#include "libpika/libpika-intl.h"
/**
* SECTION: pikaquerybox
* @title: PikaQueryBox
* @short_description: Some simple dialogs to enter a single int,
* double, string or boolean value.
* @see_also: #PikaSizeEntry, #PikaUnitMenu
*
* 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,
PikaHelpFunc 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,
PikaHelpFunc 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 = pika_dialog_new (title, "pika-query-box",
parent, 0,
help_func, help_id,
cancel_button, GTK_RESPONSE_CANCEL,
ok_button, GTK_RESPONSE_OK,
NULL);
pika_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), "pika-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;
}
/**
* pika_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 *
pika_query_string_box (const gchar *title,
GtkWidget *parent,
PikaHelpFunc help_func,
const gchar *help_id,
const gchar *message,
const gchar *initial,
GObject *object,
const gchar *signal,
PikaQueryStringCallback 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;
}
/**
* pika_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 *
pika_query_int_box (const gchar *title,
GtkWidget *parent,
PikaHelpFunc help_func,
const gchar *help_id,
const gchar *message,
gint initial,
gint lower,
gint upper,
GObject *object,
const gchar *signal,
PikaQueryIntCallback 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 = pika_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;
}
/**
* pika_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 *
pika_query_double_box (const gchar *title,
GtkWidget *parent,
PikaHelpFunc help_func,
const gchar *help_id,
const gchar *message,
gdouble initial,
gdouble lower,
gdouble upper,
gint digits,
GObject *object,
const gchar *signal,
PikaQueryDoubleCallback 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 = pika_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;
}
/**
* pika_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 #PikaSizeEntry provide in
* "pixel" mode.
* @unit: The unit initially shown by the #PikaUnitMenu.
* @resolution: The resolution (in dpi) which will be used for pixel/unit
* calculations.
* @dot_for_dot: %TRUE if the #PikaUnitMenu'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
* #PikaSizeEntry.
*
* Returns: (transfer full): A pointer to the new #GtkDialog.
**/
GtkWidget *
pika_query_size_box (const gchar *title,
GtkWidget *parent,
PikaHelpFunc help_func,
const gchar *help_id,
const gchar *message,
gdouble initial,
gdouble lower,
gdouble upper,
gint digits,
PikaUnit unit,
gdouble resolution,
gboolean dot_for_dot,
GObject *object,
const gchar *signal,
PikaQuerySizeCallback 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 = pika_size_entry_new (1, unit, "%p", TRUE, FALSE, FALSE, 12,
PIKA_SIZE_ENTRY_UPDATE_SIZE);
if (dot_for_dot)
pika_size_entry_set_unit (PIKA_SIZE_ENTRY (sizeentry), PIKA_UNIT_PIXEL);
pika_size_entry_set_resolution (PIKA_SIZE_ENTRY (sizeentry), 0,
resolution, FALSE);
pika_size_entry_set_refval_digits (PIKA_SIZE_ENTRY (sizeentry), 0, digits);
pika_size_entry_set_refval_boundaries (PIKA_SIZE_ENTRY (sizeentry), 0,
lower, upper);
pika_size_entry_set_refval (PIKA_SIZE_ENTRY (sizeentry), 0, initial);
spinbutton = pika_size_entry_get_help_widget (PIKA_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);
pika_size_entry_grab_focus (PIKA_SIZE_ENTRY (sizeentry));
gtk_widget_show (sizeentry);
query_box->entry = sizeentry;
return query_box->qbox;
}
/**
* pika_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 *
pika_query_boolean_box (const gchar *title,
GtkWidget *parent,
PikaHelpFunc 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,
PikaQueryBooleanCallback 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)
(* (PikaQueryStringCallback) 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)
(* (PikaQueryIntCallback) 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)
(* (PikaQueryDoubleCallback) 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;
PikaUnit unit;
query_box_disconnect (query_box);
/* Get the sizeentry data */
size = pika_size_entry_get_refval (PIKA_SIZE_ENTRY (query_box->entry), 0);
unit = pika_size_entry_get_unit (PIKA_SIZE_ENTRY (query_box->entry));
/* Call the user defined callback */
if (response_id == GTK_RESPONSE_OK)
(* (PikaQuerySizeCallback) 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 */
(* (PikaQueryBooleanCallback) 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);
}