384 lines
10 KiB
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;
|
|
}
|