summaryrefslogtreecommitdiffstats
path: root/libgimpwidgets/gimpspinbutton.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libgimpwidgets/gimpspinbutton.c385
1 files changed, 385 insertions, 0 deletions
diff --git a/libgimpwidgets/gimpspinbutton.c b/libgimpwidgets/gimpspinbutton.c
new file mode 100644
index 0000000..4d87e9d
--- /dev/null
+++ b/libgimpwidgets/gimpspinbutton.c
@@ -0,0 +1,385 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpspinbutton.c
+ * Copyright (C) 2018 Ell
+ *
+ * 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpmath/gimpmath.h"
+
+#include "gimpwidgetstypes.h"
+
+#include "gimp3migration.h"
+#include "gimpspinbutton.h"
+
+
+/**
+ * SECTION: gimpspinbutton
+ * @title: GimpSpinButton
+ * @short_description: A #GtkSpinButton with a some tweaked functionality.
+ *
+ * #GimpSpinButton is a drop-in replacement for #GtkSpinButton, with the
+ * following changes:
+ *
+ * - When the spin-button loses focus, its adjustment value is only
+ * updated if the entry text has been changed.
+ *
+ * - When the spin-button's "wrap" property is TRUE, values input through the
+ * entry are wrapped around.
+ *
+ * - Modifiers can be used during scrolling for smaller/bigger increments.
+ **/
+
+
+#define MAX_DIGITS 20
+
+
+struct _GimpSpinButtonPrivate
+{
+ gboolean changed;
+};
+
+
+/* local function prototypes */
+
+static gboolean gimp_spin_button_scroll (GtkWidget *widget,
+ GdkEventScroll *event);
+static gboolean gimp_spin_button_key_press (GtkWidget *widget,
+ GdkEventKey *event);
+static gboolean gimp_spin_button_focus_in (GtkWidget *widget,
+ GdkEventFocus *event);
+static gboolean gimp_spin_button_focus_out (GtkWidget *widget,
+ GdkEventFocus *event);
+
+static gint gimp_spin_button_input (GtkSpinButton *spin_button,
+ gdouble *new_value);
+
+static void gimp_spin_button_changed (GtkEditable *editable,
+ gpointer data);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpSpinButton, gimp_spin_button,
+ GTK_TYPE_SPIN_BUTTON)
+
+#define parent_class gimp_spin_button_parent_class
+
+
+/* private functions */
+
+
+static void
+gimp_spin_button_class_init (GimpSpinButtonClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GtkSpinButtonClass *spin_button_class = GTK_SPIN_BUTTON_CLASS (klass);
+
+ widget_class->scroll_event = gimp_spin_button_scroll;
+ widget_class->key_press_event = gimp_spin_button_key_press;
+ widget_class->focus_in_event = gimp_spin_button_focus_in;
+ widget_class->focus_out_event = gimp_spin_button_focus_out;
+
+ spin_button_class->input = gimp_spin_button_input;
+}
+
+static void
+gimp_spin_button_init (GimpSpinButton *spin_button)
+{
+ spin_button->priv = gimp_spin_button_get_instance_private (spin_button);
+
+ g_signal_connect (spin_button, "changed",
+ G_CALLBACK (gimp_spin_button_changed),
+ NULL);
+}
+
+static gboolean
+gimp_spin_button_scroll (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ if (event->direction == GDK_SCROLL_UP ||
+ event->direction == GDK_SCROLL_DOWN)
+ {
+ GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
+ GtkAdjustment *adjustment = gtk_spin_button_get_adjustment (spin_button);
+ gdouble step_inc;
+ gdouble page_inc;
+ gint digits;
+ gdouble step;
+
+ step_inc = gtk_adjustment_get_step_increment (adjustment);
+ page_inc = gtk_adjustment_get_page_increment (adjustment);
+ digits = gtk_spin_button_get_digits (spin_button);
+
+ if (event->state & GDK_SHIFT_MASK)
+ {
+ step = step_inc * step_inc / page_inc;
+ step = MAX (step, pow (10.0, -digits));
+ }
+ else if (event->state & GDK_CONTROL_MASK)
+ {
+ step = page_inc;
+ }
+ else
+ {
+ step = step_inc;
+ }
+
+ if (event->direction == GDK_SCROLL_DOWN)
+ step = -step;
+
+ if (! gtk_widget_has_focus (widget))
+ gtk_widget_grab_focus (widget);
+
+ gtk_spin_button_spin (spin_button, GTK_SPIN_USER_DEFINED, step);
+
+ return TRUE;
+ }
+
+ return GTK_WIDGET_CLASS (parent_class)->scroll_event (widget, event);
+}
+
+static gboolean
+gimp_spin_button_key_press (GtkWidget *widget,
+ GdkEventKey *event)
+{
+ switch (event->keyval)
+ {
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ case GDK_KEY_Escape:
+ {
+ GtkEntry *entry = GTK_ENTRY (widget);
+ GtkSpinButton *spin_button = GTK_SPIN_BUTTON (widget);
+ gchar *text;
+ gboolean changed;
+
+ text = g_strdup (gtk_entry_get_text (entry));
+
+ if (event->keyval == GDK_KEY_Escape)
+ {
+ gtk_spin_button_set_value (
+ spin_button,
+ gtk_spin_button_get_value (spin_button));
+ }
+ else
+ {
+ gtk_spin_button_update (spin_button);
+ }
+
+ changed = strcmp (gtk_entry_get_text (entry), text);
+
+ g_free (text);
+
+ if (changed)
+ {
+ gtk_editable_set_position (GTK_EDITABLE (widget), -1);
+
+ return TRUE;
+ }
+ }
+ break;
+ }
+
+ return GTK_WIDGET_CLASS (parent_class)->key_press_event (widget, event);
+}
+
+static gboolean
+gimp_spin_button_focus_in (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ GimpSpinButton *spin_button = GIMP_SPIN_BUTTON (widget);
+
+ spin_button->priv->changed = FALSE;
+
+ return GTK_WIDGET_CLASS (parent_class)->focus_in_event (widget, event);
+}
+
+static gboolean
+gimp_spin_button_focus_out (GtkWidget *widget,
+ GdkEventFocus *event)
+{
+ GimpSpinButton *spin_button = GIMP_SPIN_BUTTON (widget);
+ gboolean editable;
+ gboolean result;
+
+ editable = gtk_editable_get_editable (GTK_EDITABLE (widget));
+
+ if (! spin_button->priv->changed)
+ gtk_editable_set_editable (GTK_EDITABLE (widget), FALSE);
+
+ result = GTK_WIDGET_CLASS (parent_class)->focus_out_event (widget, event);
+
+ if (! spin_button->priv->changed)
+ gtk_editable_set_editable (GTK_EDITABLE (widget), editable);
+
+ return result;
+}
+
+static gint
+gimp_spin_button_input (GtkSpinButton *spin_button,
+ gdouble *new_value)
+{
+ if (gtk_spin_button_get_wrap (spin_button))
+ {
+ gdouble value;
+ gdouble min;
+ gdouble max;
+ gchar *endptr;
+
+ value = g_strtod (gtk_entry_get_text (GTK_ENTRY (spin_button)), &endptr);
+
+ if (*endptr)
+ return FALSE;
+
+ gtk_spin_button_get_range (spin_button, &min, &max);
+
+ if (min < max)
+ {
+ gdouble rem;
+
+ rem = fmod (value - min, max - min);
+
+ if (rem < 0.0)
+ rem += max - min;
+
+ if (rem == 0.0)
+ value = CLAMP (value, min, max);
+ else
+ value = min + rem;
+ }
+ else
+ {
+ value = min;
+ }
+
+ *new_value = value;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_spin_button_changed (GtkEditable *editable,
+ gpointer data)
+{
+ GimpSpinButton *spin_button = GIMP_SPIN_BUTTON (editable);
+
+ spin_button->priv->changed = TRUE;
+}
+
+
+/* public functions */
+
+
+/**
+ * gimp_spin_button_new:
+ * @adjustment: (allow-none): the #GtkAdjustment object that this spin
+ * button should use, or %NULL
+ * @climb_rate: specifies by how much the rate of change in the
+ * value will accelerate if you continue to hold
+ * down an up/down button or arrow key
+ * @digits: the number of decimal places to display
+ *
+ * Creates a new #GimpSpinButton.
+ *
+ * Returns: The new spin button as a #GtkWidget
+ *
+ * Since: 2.10.10
+ */
+GtkWidget *
+gimp_spin_button_new_ (GtkAdjustment *adjustment,
+ gdouble climb_rate,
+ guint digits)
+{
+ GtkWidget *spin_button;
+
+ g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
+ NULL);
+
+ spin_button = g_object_new (GIMP_TYPE_SPIN_BUTTON, NULL);
+
+ gtk_spin_button_configure (GTK_SPIN_BUTTON (spin_button),
+ adjustment, climb_rate, digits);
+
+ return spin_button;
+}
+
+/**
+ * gimp_spin_button_new_with_range:
+ * @min: Minimum allowable value
+ * @max: Maximum allowable value
+ * @step: Increment added or subtracted by spinning the widget
+ *
+ * This is a convenience constructor that allows creation of a numeric
+ * #GimpSpinButton without manually creating an adjustment. The value is
+ * initially set to the minimum value and a page increment of 10 * @step
+ * is the default. The precision of the spin button is equivalent to the
+ * precision of @step.
+ *
+ * Note that the way in which the precision is derived works best if @step
+ * is a power of ten. If the resulting precision is not suitable for your
+ * needs, use gtk_spin_button_set_digits() to correct it.
+ *
+ * Returns: The new spin button as a #GtkWidget
+ *
+ * Since: 2.10.10
+ */
+GtkWidget *
+gimp_spin_button_new_with_range (gdouble min,
+ gdouble max,
+ gdouble step)
+{
+ GtkAdjustment *adjustment;
+ GtkWidget *spin_button;
+ gint digits;
+
+ g_return_val_if_fail (min <= max, NULL);
+ g_return_val_if_fail (step != 0.0, NULL);
+
+ spin_button = g_object_new (GIMP_TYPE_SPIN_BUTTON, NULL);
+
+ adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (min, min, max,
+ step, 10.0 * step, 0.0));
+
+ if (fabs (step) >= 1.0 || step == 0.0)
+ {
+ digits = 0;
+ }
+ else
+ {
+ digits = abs ((gint) floor (log10 (fabs (step))));
+
+ if (digits > MAX_DIGITS)
+ digits = MAX_DIGITS;
+ }
+
+ gtk_spin_button_configure (GTK_SPIN_BUTTON (spin_button),
+ adjustment, step, digits);
+ gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (spin_button), TRUE);
+
+ return spin_button;
+}