/* LIBPIKA - The PIKA Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * pikascaleentry.c * Copyright (C) 2000 Michael Natterer * 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 * . */ #include "config.h" #include #include #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; }