PIKApp/libpikawidgets/pikaspinbutton.c

384 lines
10 KiB
C

/* LIBPIKA - The PIKA Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* pikaspinbutton.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 "libpikamath/pikamath.h"
#include "pikawidgetstypes.h"
#include "pikaspinbutton.h"
/**
* SECTION: pikaspinbutton
* @title: PikaSpinButton
* @short_description: A #GtkSpinButton with a some tweaked functionality.
*
* #PikaSpinButton 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 _PikaSpinButtonPrivate
{
gboolean changed;
};
/* local function prototypes */
static gboolean pika_spin_button_scroll (GtkWidget *widget,
GdkEventScroll *event);
static gboolean pika_spin_button_key_press (GtkWidget *widget,
GdkEventKey *event);
static gboolean pika_spin_button_focus_in (GtkWidget *widget,
GdkEventFocus *event);
static gboolean pika_spin_button_focus_out (GtkWidget *widget,
GdkEventFocus *event);
static gint pika_spin_button_input (GtkSpinButton *spin_button,
gdouble *new_value);
static void pika_spin_button_changed (GtkEditable *editable,
gpointer data);
G_DEFINE_TYPE_WITH_PRIVATE (PikaSpinButton, pika_spin_button,
GTK_TYPE_SPIN_BUTTON)
#define parent_class pika_spin_button_parent_class
/* private functions */
static void
pika_spin_button_class_init (PikaSpinButtonClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GtkSpinButtonClass *spin_button_class = GTK_SPIN_BUTTON_CLASS (klass);
widget_class->scroll_event = pika_spin_button_scroll;
widget_class->key_press_event = pika_spin_button_key_press;
widget_class->focus_in_event = pika_spin_button_focus_in;
widget_class->focus_out_event = pika_spin_button_focus_out;
spin_button_class->input = pika_spin_button_input;
}
static void
pika_spin_button_init (PikaSpinButton *spin_button)
{
spin_button->priv = pika_spin_button_get_instance_private (spin_button);
g_signal_connect (spin_button, "changed",
G_CALLBACK (pika_spin_button_changed),
NULL);
}
static gboolean
pika_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
pika_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
pika_spin_button_focus_in (GtkWidget *widget,
GdkEventFocus *event)
{
PikaSpinButton *spin_button = PIKA_SPIN_BUTTON (widget);
spin_button->priv->changed = FALSE;
return GTK_WIDGET_CLASS (parent_class)->focus_in_event (widget, event);
}
static gboolean
pika_spin_button_focus_out (GtkWidget *widget,
GdkEventFocus *event)
{
PikaSpinButton *spin_button = PIKA_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
pika_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
pika_spin_button_changed (GtkEditable *editable,
gpointer data)
{
PikaSpinButton *spin_button = PIKA_SPIN_BUTTON (editable);
spin_button->priv->changed = TRUE;
}
/* public functions */
/**
* pika_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 #PikaSpinButton.
*
* Returns: The new spin button as a #GtkWidget
*
* Since: 2.10.10
*/
GtkWidget *
pika_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 (PIKA_TYPE_SPIN_BUTTON, NULL);
gtk_spin_button_configure (GTK_SPIN_BUTTON (spin_button),
adjustment, climb_rate, digits);
return spin_button;
}
/**
* pika_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
* #PikaSpinButton 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 *
pika_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 (PIKA_TYPE_SPIN_BUTTON, NULL);
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;
}