PIKApp/libpikawidgets/pikascaleentry.c

449 lines
15 KiB
C

/* LIBPIKA - The PIKA Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* pikascaleentry.c
* Copyright (C) 2000 Michael Natterer <mitch@gimp.org>
* Copyright (C) 2020 Jehan
*
* 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
* Library 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 "libpikamath/pikamath.h"
#include "libpikabase/pikabase.h"
#include "pikawidgets.h"
/**
* SECTION: pikascaleentry
* @title: PikaScaleEntry
* @short_description: Widget containing a scale, a spin button and a
* label.
*
* This widget is a #GtkGrid showing a #GtkSpinButton and a #GtkScale
* bound together. It also displays a #GtkLabel which is used as
* mnemonic on the #GtkSpinButton.
**/
typedef struct _PikaScaleEntryPrivate
{
GtkWidget *scale;
GBinding *binding;
gboolean logarithmic;
gboolean limit_scale;
} PikaScaleEntryPrivate;
static void pika_scale_entry_constructed (GObject *object);
static gboolean pika_scale_entry_linear_to_log (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data);
static gboolean pika_scale_entry_log_to_linear (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data);
static void pika_scale_entry_configure (PikaScaleEntry *entry);
G_DEFINE_TYPE_WITH_PRIVATE (PikaScaleEntry, pika_scale_entry, PIKA_TYPE_LABEL_SPIN)
#define parent_class pika_scale_entry_parent_class
static void
pika_scale_entry_class_init (PikaScaleEntryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructed = pika_scale_entry_constructed;
klass->new_range_widget = NULL;
}
static void
pika_scale_entry_init (PikaScaleEntry *entry)
{
PikaScaleEntryPrivate *priv = pika_scale_entry_get_instance_private (entry);
priv->limit_scale = FALSE;
}
static void
pika_scale_entry_constructed (GObject *object)
{
PikaScaleEntryClass *klass;
PikaScaleEntry *entry = PIKA_SCALE_ENTRY (object);
PikaScaleEntryPrivate *priv = pika_scale_entry_get_instance_private (entry);
GtkAdjustment *scale_adjustment;
GtkAdjustment *spin_adjustment;
GtkWidget *spinbutton;
G_OBJECT_CLASS (parent_class)->constructed (object);
spinbutton = pika_label_spin_get_spin_button (PIKA_LABEL_SPIN (entry));
spin_adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
scale_adjustment = gtk_adjustment_new (gtk_adjustment_get_value (spin_adjustment),
gtk_adjustment_get_lower (spin_adjustment),
gtk_adjustment_get_upper (spin_adjustment),
gtk_adjustment_get_step_increment (spin_adjustment),
gtk_adjustment_get_page_increment (spin_adjustment),
gtk_adjustment_get_page_size (spin_adjustment));
klass = PIKA_SCALE_ENTRY_GET_CLASS (entry);
if (klass->new_range_widget)
{
priv->scale = klass->new_range_widget (scale_adjustment);
g_return_if_fail (GTK_IS_RANGE (priv->scale));
g_return_if_fail (scale_adjustment == gtk_range_get_adjustment (GTK_RANGE (priv->scale)));
}
else
{
priv->scale = gtk_scale_new (GTK_ORIENTATION_HORIZONTAL, scale_adjustment);
gtk_scale_set_draw_value (GTK_SCALE (priv->scale), FALSE);
g_object_bind_property (G_OBJECT (spinbutton), "digits",
G_OBJECT (priv->scale), "digits",
G_BINDING_BIDIRECTIONAL |
G_BINDING_SYNC_CREATE);
}
gtk_widget_set_hexpand (priv->scale, TRUE);
/* Move the spin button to the right. */
gtk_container_remove (GTK_CONTAINER (entry), g_object_ref (spinbutton));
gtk_grid_attach (GTK_GRID (entry), spinbutton, 2, 0, 1, 1);
gtk_grid_attach (GTK_GRID (entry), priv->scale, 1, 0, 1, 1);
gtk_widget_show (priv->scale);
g_signal_connect_swapped (spin_adjustment, "changed",
G_CALLBACK (pika_scale_entry_configure),
entry);
pika_scale_entry_configure (entry);
}
static gboolean
pika_scale_entry_linear_to_log (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
GtkAdjustment *spin_adjustment;
gdouble value = g_value_get_double (from_value);
spin_adjustment = GTK_ADJUSTMENT (g_binding_dup_source (binding));
if (gtk_adjustment_get_lower (spin_adjustment) <= 0.0)
value = log (value - gtk_adjustment_get_lower (spin_adjustment) + 0.1);
else
value = log (value);
g_value_set_double (to_value, value);
g_clear_object (&spin_adjustment);
return TRUE;
}
static gboolean
pika_scale_entry_log_to_linear (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
GtkAdjustment *spin_adjustment;
gdouble value = g_value_get_double (from_value);
spin_adjustment = GTK_ADJUSTMENT (g_binding_dup_source (binding));
value = exp (value);
if (gtk_adjustment_get_lower (spin_adjustment) <= 0.0)
value += gtk_adjustment_get_lower (spin_adjustment) - 0.1;
g_value_set_double (to_value, value);
g_clear_object (&spin_adjustment);
return TRUE;
}
static void
pika_scale_entry_configure (PikaScaleEntry *entry)
{
PikaScaleEntryPrivate *priv;
GBinding *binding;
GtkWidget *spinbutton;
GtkAdjustment *spin_adj;
GtkAdjustment *scale_adj;
gdouble scale_lower;
gdouble scale_upper;
g_return_if_fail (PIKA_IS_SCALE_ENTRY (entry));
priv = pika_scale_entry_get_instance_private (entry);
scale_adj = gtk_range_get_adjustment (GTK_RANGE (priv->scale));
spinbutton = pika_label_spin_get_spin_button (PIKA_LABEL_SPIN (entry));
spin_adj = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
g_clear_object (&priv->binding);
if (priv->limit_scale)
{
scale_lower = gtk_adjustment_get_lower (scale_adj);
scale_upper = gtk_adjustment_get_upper (scale_adj);
}
else
{
scale_lower = gtk_adjustment_get_lower (spin_adj);
scale_upper = gtk_adjustment_get_upper (spin_adj);
}
if (priv->logarithmic)
{
gdouble correction;
gdouble log_value, log_lower, log_upper;
gdouble log_step_increment, log_page_increment;
correction = (scale_lower > 0 ? 0 : 0.1 + - scale_lower);
log_value = log (gtk_adjustment_get_value (scale_adj) + correction);
log_lower = log (scale_lower + correction);
log_upper = log (scale_upper + correction);
log_step_increment =
(log_upper - log_lower) / ((scale_upper - scale_lower) /
gtk_adjustment_get_step_increment (spin_adj));
log_page_increment =
(log_upper - log_lower) / ((scale_upper - scale_lower) /
gtk_adjustment_get_page_increment (spin_adj));
gtk_adjustment_configure (scale_adj,
log_value, log_lower, log_upper,
log_step_increment, log_page_increment, 0.0);
binding = g_object_bind_property_full (G_OBJECT (spin_adj), "value",
G_OBJECT (scale_adj), "value",
G_BINDING_BIDIRECTIONAL |
G_BINDING_SYNC_CREATE,
pika_scale_entry_linear_to_log,
pika_scale_entry_log_to_linear,
NULL, NULL);
}
else
{
gtk_adjustment_configure (scale_adj,
gtk_adjustment_get_value (spin_adj),
scale_lower, scale_upper,
gtk_adjustment_get_step_increment (spin_adj),
gtk_adjustment_get_page_increment (spin_adj),
0.0);
binding = g_object_bind_property (G_OBJECT (spin_adj), "value",
G_OBJECT (scale_adj), "value",
G_BINDING_BIDIRECTIONAL |
G_BINDING_SYNC_CREATE);
}
priv->binding = binding;
}
/* Public functions */
/**
* pika_scale_entry_new:
* @text: The text for the #GtkLabel which will appear left of
* the #GtkHScale.
* @value: The initial value.
* @lower: The lower boundary.
* @upper: The upper boundary.
* @digits: The number of decimal digits.
*
* This function creates a #GtkLabel, a #GtkHScale and a #GtkSpinButton and
* attaches them to a 3-column #GtkGrid.
*
* Returns: (transfer full): The new #PikaScaleEntry.
**/
GtkWidget *
pika_scale_entry_new (const gchar *text,
gdouble value,
gdouble lower,
gdouble upper,
guint digits)
{
GtkWidget *entry;
entry = g_object_new (PIKA_TYPE_SCALE_ENTRY,
"label", text,
"value", value,
"lower", lower,
"upper", upper,
"digits", digits,
NULL);
return entry;
}
/**
* pika_scale_entry_get_range:
* @entry: The #GtkScaleEntry.
*
* This function returns the #GtkRange packed in @entry. This can be
* useful if you need to customize some aspects of the widget
*
* By default, it is a #GtkScale, but it can be any other type of
* #GtkRange if a subclass overrode the new_range_widget() protected
* method.
*
* Returns: (transfer none) (type GtkRange): The #GtkRange contained in @entry.
**/
GtkWidget *
pika_scale_entry_get_range (PikaScaleEntry *entry)
{
PikaScaleEntryPrivate *priv = pika_scale_entry_get_instance_private (entry);
g_return_val_if_fail (PIKA_IS_SCALE_ENTRY (entry), NULL);
return priv->scale;
}
/**
* pika_scale_entry_set_bounds:
* @entry: The #GtkScaleEntry.
* @lower: the lower value for the whole widget if @limit_scale is
* %FALSE, or only for the #GtkScale if %TRUE.
* @upper: the upper value for the whole widget if @limit_scale is
* %FALSE, or only for the #GtkSpinButton if %TRUE.
* @limit_scale: Whether the range should only apply to the #GtkScale or
* if it should share its #GtkAdjustement with the
* #GtkSpinButton. If %TRUE, both @lower and @upper must be
* included in current #GtkSpinButton range.
*
* By default the #GtkSpinButton and #GtkScale will have the same range.
* In some case, you want to set a different range. In particular when
* the finale range is huge, the #GtkScale might become nearly useless
* as every tiny slider move would dramatically update the value. In
* this case, it is common to set the #GtkScale to a smaller common
* range, while the #GtkSpinButton would allow for the full allowed
* range.
* This function allows this. Obviously the #GtkAdjustment of both
* widgets would be synced but if the set value is out of the #GtkScale
* range, the slider would simply show at one extreme.
*
* If @limit_scale is %FALSE though, it would sync back both widgets
* range to the new values.
*
* Note that the step and page increments are updated when the range is
* updated according to some common usage algorithm which should work if
* you don't have very specific needs. If you want to customize the step
* increments yourself, you may call pika_label_spin_set_increments()
**/
void
pika_scale_entry_set_bounds (PikaScaleEntry *entry,
gdouble lower,
gdouble upper,
gboolean limit_scale)
{
PikaScaleEntryPrivate *priv;
GtkWidget *spinbutton;
GtkAdjustment *spin_adjustment;
GtkAdjustment *scale_adjustment;
g_return_if_fail (PIKA_IS_SCALE_ENTRY (entry));
g_return_if_fail (lower <= upper);
priv = pika_scale_entry_get_instance_private (entry);
scale_adjustment = gtk_range_get_adjustment (GTK_RANGE (priv->scale));
spinbutton = pika_label_spin_get_spin_button (PIKA_LABEL_SPIN (entry));
spin_adjustment = gtk_spin_button_get_adjustment (GTK_SPIN_BUTTON (spinbutton));
priv->limit_scale = limit_scale;
if (limit_scale)
{
g_return_if_fail (lower >= gtk_adjustment_get_lower (spin_adjustment) &&
upper <= gtk_adjustment_get_upper (spin_adjustment));
gtk_adjustment_set_lower (scale_adjustment, lower);
gtk_adjustment_set_upper (scale_adjustment, upper);
pika_scale_entry_configure (entry);
}
else if (! limit_scale)
{
g_object_set (entry,
"lower", lower,
"upper", upper,
NULL);
}
}
/**
* pika_scale_entry_set_logarithmic:
* @entry: a #PikaScaleEntry as returned by pika_scale_entry_new()
* @logarithmic: a boolean value to set or reset logarithmic behavior
* of the scale widget
*
* Sets whether @entry's scale widget will behave in a linear
* or logarithmic fashion. Useful when an entry has to attend large
* ranges, but smaller selections on that range require a finer
* adjustment.
*
* Since: 2.2
**/
void
pika_scale_entry_set_logarithmic (PikaScaleEntry *entry,
gboolean logarithmic)
{
PikaScaleEntryPrivate *priv;
g_return_if_fail (PIKA_IS_SCALE_ENTRY (entry));
priv = pika_scale_entry_get_instance_private (entry);
if (logarithmic != priv->logarithmic)
{
priv->logarithmic = logarithmic;
pika_scale_entry_configure (entry);
}
}
/**
* pika_scale_entry_get_logarithmic:
* @entry: a #PikaScaleEntry as returned by pika_scale_entry_new()
*
* Returns: %TRUE if @entry's scale widget will behave in
* logarithmic fashion, %FALSE for linear behavior.
*
* Since: 2.2
**/
gboolean
pika_scale_entry_get_logarithmic (PikaScaleEntry *entry)
{
PikaScaleEntryPrivate *priv = pika_scale_entry_get_instance_private (entry);
g_return_val_if_fail (PIKA_IS_SCALE_ENTRY (entry), FALSE);
return priv->logarithmic;
}