PIKApp/libpikawidgets/pikanumberpairentry.c

1281 lines
41 KiB
C
Raw Permalink Normal View History

2023-09-26 00:35:21 +02:00
/* LIBPIKA - The PIKA Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* pikanumberpairentry.c
* Copyright (C) 2006 Simon Budig <simon@gimp.org>
* Copyright (C) 2007 Sven Neumann <sven@gimp.org>
* Copyright (C) 2007 Martin Nordholts <martin@svn.gnome.org>
*
* 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 <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "pikawidgetstypes.h"
#include "pikaicons.h"
#include "pikanumberpairentry.h"
/**
* SECTION: pikanumberpairentry
* @title: PikaNumberPairEntry
* @short_description: A #GtkEntry subclass to enter ratios.
*
* A #GtkEntry subclass to enter ratios.
**/
#define EPSILON 0.000001
enum
{
NUMBERS_CHANGED,
RATIO_CHANGED,
LAST_SIGNAL
};
enum
{
PROP_0,
PROP_LEFT_NUMBER,
PROP_RIGHT_NUMBER,
PROP_DEFAULT_LEFT_NUMBER,
PROP_DEFAULT_RIGHT_NUMBER,
PROP_USER_OVERRIDE,
PROP_SEPARATORS,
PROP_DEFAULT_TEXT,
PROP_ALLOW_SIMPLIFICATION,
PROP_MIN_VALID_VALUE,
PROP_MAX_VALID_VALUE,
PROP_RATIO,
PROP_ASPECT
};
typedef enum
{
PARSE_VALID,
PARSE_CLEAR,
PARSE_INVALID
} ParseResult;
struct _PikaNumberPairEntryPrivate
{
/* The current number pair displayed in the widget. */
gdouble left_number;
gdouble right_number;
/* What number pair that should be displayed when not in
user_override mode. */
gdouble default_left_number;
gdouble default_right_number;
/* Whether or not the current value in the entry has been explicitly
* set by the user.
*/
gboolean user_override;
/* Is the font style currently set to ITALIC or NORMAL ? */
gboolean font_italic;
/* What separators that are valid when parsing input, e.g. when the
* widget is used for aspect ratio, valid separators are typically
* ':' and '/'.
*/
gunichar *separators;
glong num_separators;
/* A string to be shown in the entry when in automatic mode */
gchar *default_text;
/* Whether or to not to divide the numbers with the greatest common
* divisor when input ends in '='.
*/
gboolean allow_simplification;
/* What range of values considered valid. */
gdouble min_valid_value;
gdouble max_valid_value;
};
#define GET_PRIVATE(obj) (((PikaNumberPairEntry *) (obj))->priv)
static void pika_number_pair_entry_finalize (GObject *entry);
static gboolean pika_number_pair_entry_valid_separator (PikaNumberPairEntry *entry,
gunichar candidate);
static void pika_number_pair_entry_ratio_to_fraction (gdouble ratio,
gdouble *numerator,
gdouble *denominator);
static void pika_number_pair_entry_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_number_pair_entry_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_number_pair_entry_changed (PikaNumberPairEntry *entry);
static void pika_number_pair_entry_icon_press (PikaNumberPairEntry *entry);
static gboolean pika_number_pair_entry_events (GtkWidget *widget,
GdkEvent *event);
static void pika_number_pair_entry_update_text (PikaNumberPairEntry *entry);
static ParseResult pika_number_pair_entry_parse_text (PikaNumberPairEntry *entry,
const gchar *text,
gdouble *left_value,
gdouble *right_value);
static gboolean pika_number_pair_entry_numbers_in_range (PikaNumberPairEntry *entry,
gdouble left_number,
gdouble right_number);
static gchar * pika_number_pair_entry_strdup_number_pair_string
(PikaNumberPairEntry *entry,
gdouble left_number,
gdouble right_number);
G_DEFINE_TYPE_WITH_PRIVATE (PikaNumberPairEntry, pika_number_pair_entry,
GTK_TYPE_ENTRY)
#define parent_class pika_number_pair_entry_parent_class
/* What the user shall end the input with when simplification is desired. */
#define SIMPLIFICATION_CHAR ((gunichar) '=')
#define DEFAULT_SEPARATOR ((gunichar) ',')
static guint entry_signals[LAST_SIGNAL] = { 0 };
static void
pika_number_pair_entry_class_init (PikaNumberPairEntryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
entry_signals[NUMBERS_CHANGED] =
g_signal_new ("numbers-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (PikaNumberPairEntryClass, numbers_changed),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
entry_signals[RATIO_CHANGED] =
g_signal_new ("ratio-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (PikaNumberPairEntryClass, ratio_changed),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
object_class->set_property = pika_number_pair_entry_set_property;
object_class->get_property = pika_number_pair_entry_get_property;
object_class->finalize = pika_number_pair_entry_finalize;
klass->numbers_changed = NULL;
klass->ratio_changed = NULL;
g_object_class_install_property (object_class, PROP_LEFT_NUMBER,
g_param_spec_double ("left-number",
"Left number",
"The left number",
G_MINDOUBLE, G_MAXDOUBLE,
100.0,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_RIGHT_NUMBER,
g_param_spec_double ("right-number",
"Right number",
"The right number",
G_MINDOUBLE, G_MAXDOUBLE,
100.0,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_DEFAULT_LEFT_NUMBER,
g_param_spec_double ("default-left-number",
"Default left number",
"The default left number",
G_MINDOUBLE, G_MAXDOUBLE,
100.0,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_DEFAULT_RIGHT_NUMBER,
g_param_spec_double ("default-right-number",
"Default right number",
"The default right number",
G_MINDOUBLE, G_MAXDOUBLE,
100.0,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_USER_OVERRIDE,
g_param_spec_boolean ("user-override",
"User override",
"Whether the widget is in 'user override' mode",
FALSE,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_SEPARATORS,
g_param_spec_string ("separators",
"Separators",
"A string of valid separators",
NULL,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY));
g_object_class_install_property (object_class, PROP_DEFAULT_TEXT,
g_param_spec_string ("default-text",
"Default text",
"String to show when in automatic mode",
NULL,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_ALLOW_SIMPLIFICATION,
g_param_spec_boolean ("allow-simplification",
"Allow simplification",
"Whether to allow simplification",
FALSE,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_MIN_VALID_VALUE,
g_param_spec_double ("min-valid-value",
"Min valid value",
"Minimum value valid when parsing input",
G_MINDOUBLE, G_MAXDOUBLE,
G_MINDOUBLE,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_MAX_VALID_VALUE,
g_param_spec_double ("max-valid-value",
"Max valid value",
"Maximum value valid when parsing input",
G_MINDOUBLE, G_MAXDOUBLE,
G_MAXDOUBLE,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_RATIO,
g_param_spec_double ("ratio",
"Ratio",
"The value as ratio",
G_MINDOUBLE, G_MAXDOUBLE,
1.0,
PIKA_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_ASPECT,
g_param_spec_enum ("aspect",
"Aspect",
"The value as aspect",
PIKA_TYPE_ASPECT_TYPE,
PIKA_ASPECT_SQUARE,
PIKA_PARAM_READWRITE));
}
static void
pika_number_pair_entry_init (PikaNumberPairEntry *entry)
{
PikaNumberPairEntryPrivate *priv;
entry->priv = pika_number_pair_entry_get_instance_private (entry);
priv = GET_PRIVATE (entry);
priv->left_number = 1.0;
priv->right_number = 1.0;
priv->default_left_number = 1.0;
priv->default_right_number = 1.0;
priv->user_override = FALSE;
priv->font_italic = FALSE;
priv->separators = NULL;
priv->default_text = NULL;
priv->num_separators = 0;
priv->allow_simplification = FALSE;
priv->min_valid_value = G_MINDOUBLE;
priv->max_valid_value = G_MAXDOUBLE;
g_signal_connect (entry, "changed",
G_CALLBACK (pika_number_pair_entry_changed),
NULL);
g_signal_connect (entry, "icon-press",
G_CALLBACK (pika_number_pair_entry_icon_press),
NULL);
g_signal_connect (entry, "focus-out-event",
G_CALLBACK (pika_number_pair_entry_events),
NULL);
g_signal_connect (entry, "key-press-event",
G_CALLBACK (pika_number_pair_entry_events),
NULL);
gtk_widget_set_direction (GTK_WIDGET (entry), GTK_TEXT_DIR_LTR);
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (entry),
GTK_ENTRY_ICON_SECONDARY,
PIKA_ICON_EDIT_CLEAR);
}
static void
pika_number_pair_entry_finalize (GObject *object)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (object);
g_clear_pointer (&priv->separators, g_free);
priv->num_separators = 0;
g_clear_pointer (&priv->default_text, g_free);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* pika_number_pair_entry_new:
* @separators: The allowed separators.
* @allow_simplification: Whether to do simplification on the entered term.
* @min_valid_value: The minimum allowed result value.
* @max_valid_value: The maximum allowed result value.
*
* Creates a new #PikaNumberPairEntry widget, which is a GtkEntry that
* accepts two numbers separated by a separator. Typical input example
* with a 'x' separator: "377x233".
*
* The widget supports simplification of the entered ratio when the
* input ends in '=', if "allow-simplification" is TRUE.
*
* The "separators" property contains a string of characters valid as
* separators when parsing input. The first separator is used when
* displaying the current values.
*
* It is possible to specify what range of values that shall be
* considered as valid when parsing user input, by changing
* "min-valid-value" and "max-valid-value".
*
* The first separator of @separators is used to display the current
* value.
*
* Returns: The new #PikaNumberPairEntry widget.
*
* Since: 2.4
**/
GtkWidget *
pika_number_pair_entry_new (const gchar *separators,
gboolean allow_simplification,
gdouble min_valid_value,
gdouble max_valid_value)
{
return g_object_new (PIKA_TYPE_NUMBER_PAIR_ENTRY,
"separators", separators,
"allow-simplification", allow_simplification,
"min-valid-value", min_valid_value,
"max-valid-value", max_valid_value,
NULL);
}
static void
pika_number_pair_entry_ratio_to_fraction (gdouble ratio,
gdouble *numerator,
gdouble *denominator)
{
gdouble remainder, next_cf;
gint p0, p1, p2;
gint q0, q1, q2;
/* calculate the continued fraction to approximate the desired ratio */
p0 = 1;
q0 = 0;
p1 = floor (ratio);
q1 = 1;
remainder = ratio - p1;
while (fabs (remainder) >= 0.0001 &&
fabs (((gdouble) p1 / q1) - ratio) > 0.0001)
{
remainder = 1.0 / remainder;
next_cf = floor (remainder);
p2 = next_cf * p1 + p0;
q2 = next_cf * q1 + q0;
/* remember the last two fractions */
p0 = p1;
q0 = q1;
p1 = p2;
q1 = q2;
remainder = remainder - next_cf;
}
/* only use the calculated fraction if it is "reasonable" */
if (p1 < 1000 && q1 < 1000)
{
*numerator = p1;
*denominator = q1;
}
else
{
*numerator = ratio;
*denominator = 1.0;
}
}
/**
* pika_number_pair_entry_set_ratio:
* @entry: A #PikaNumberPairEntry widget.
* @ratio: Ratio to set in the widget.
*
* Sets the numbers of the #PikaNumberPairEntry to have the desired
* ratio. If the new ratio is different than the previous ratio, the
* "ratio-changed" signal is emitted.
*
* An attempt is made to convert the decimal number into a fraction
* with left_number and right_number < 1000.
*
* Since: 2.4
**/
void
pika_number_pair_entry_set_ratio (PikaNumberPairEntry *entry,
gdouble ratio)
{
gdouble numerator;
gdouble denominator;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
pika_number_pair_entry_ratio_to_fraction (ratio, &numerator, &denominator);
pika_number_pair_entry_set_values (entry, numerator, denominator);
}
/**
* pika_number_pair_entry_get_ratio:
* @entry: A #PikaNumberPairEntry widget.
*
* Retrieves the ratio of the numbers displayed by a #PikaNumberPairEntry.
*
* Returns: The ratio value.
*
* Since: 2.4
**/
gdouble
pika_number_pair_entry_get_ratio (PikaNumberPairEntry *entry)
{
PikaNumberPairEntryPrivate *priv;
g_return_val_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry), 1.0);
priv = GET_PRIVATE (entry);
return priv->left_number / priv->right_number;
}
/**
* pika_number_pair_entry_set_values:
* @entry: A #PikaNumberPairEntry widget.
* @left: Left number in the entry.
* @right: Right number in the entry.
*
* Forces setting the numbers displayed by a #PikaNumberPairEntry,
* ignoring if the user has set their own value. The state of
* user-override will not be changed.
*
* Since: 2.4
**/
void
pika_number_pair_entry_set_values (PikaNumberPairEntry *entry,
gdouble left,
gdouble right)
{
PikaNumberPairEntryPrivate *priv;
PikaAspectType old_aspect;
gdouble old_ratio;
gdouble old_left_number;
gdouble old_right_number;
gboolean numbers_changed = FALSE;
gboolean ratio_changed = FALSE;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
priv = GET_PRIVATE (entry);
/* Store current values */
old_left_number = priv->left_number;
old_right_number = priv->right_number;
old_ratio = pika_number_pair_entry_get_ratio (entry);
old_aspect = pika_number_pair_entry_get_aspect (entry);
/* Freeze notification */
g_object_freeze_notify (G_OBJECT (entry));
/* Set the new numbers and update the entry */
priv->left_number = left;
priv->right_number = right;
g_object_notify (G_OBJECT (entry), "left-number");
g_object_notify (G_OBJECT (entry), "right-number");
pika_number_pair_entry_update_text (entry);
/* Find out what has changed */
if (fabs (old_ratio - pika_number_pair_entry_get_ratio (entry)) > EPSILON)
{
g_object_notify (G_OBJECT (entry), "ratio");
ratio_changed = TRUE;
if (old_aspect != pika_number_pair_entry_get_aspect (entry))
g_object_notify (G_OBJECT (entry), "aspect");
}
if (old_left_number != priv->left_number ||
old_right_number != priv->right_number)
{
numbers_changed = TRUE;
}
/* Thaw */
g_object_thaw_notify (G_OBJECT (entry));
/* Emit relevant signals */
if (numbers_changed)
g_signal_emit (entry, entry_signals[NUMBERS_CHANGED], 0);
if (ratio_changed)
g_signal_emit (entry, entry_signals[RATIO_CHANGED], 0);
}
/**
* pika_number_pair_entry_get_values:
* @entry: A #PikaNumberPairEntry widget.
* @left: (out) (optional): Location to store the left number (may be %NULL).
* @right: (out) (optional): Location to store the right number (may be %NULL).
*
* Gets the numbers displayed by a #PikaNumberPairEntry.
*
* Since: 2.4
**/
void
pika_number_pair_entry_get_values (PikaNumberPairEntry *entry,
gdouble *left,
gdouble *right)
{
PikaNumberPairEntryPrivate *priv;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
priv = GET_PRIVATE (entry);
if (left != NULL)
*left = priv->left_number;
if (right != NULL)
*right = priv->right_number;
}
/**
* pika_number_pair_entry_set_default_text:
* @entry: A #PikaNumberPairEntry widget.
* @string: Default string.
*
* Causes the entry to show a given string when in automatic mode,
* instead of the default numbers. The only thing this does is making
* the #PikaNumberPairEntry showing this string, the internal state
* and API calls are not affected.
*
* Set the default string to %NULL to display default values as
* normal.
*
* Since: 2.4
*/
void
pika_number_pair_entry_set_default_text (PikaNumberPairEntry *entry,
const gchar *string)
{
PikaNumberPairEntryPrivate *priv;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
priv = GET_PRIVATE (entry);
g_free (priv->default_text);
priv->default_text = g_strdup (string);
pika_number_pair_entry_update_text (entry);
g_object_notify (G_OBJECT (entry), "default-text");
}
/**
* pika_number_pair_entry_get_default_text:
* @entry: A #PikaNumberPairEntry widget.
*
* Returns: (nullable): the string manually set to be shown,
* or %NULL if values are shown in a normal fashion.
*
* Since: 2.4
*/
const gchar *
pika_number_pair_entry_get_default_text (PikaNumberPairEntry *entry)
{
PikaNumberPairEntryPrivate *priv;
g_return_val_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry), NULL);
priv = GET_PRIVATE (entry);
return priv->default_text;
}
/**
* pika_number_pair_entry_set_aspect:
* @entry: A #PikaNumberPairEntry widget.
* @aspect: The new aspect.
*
* Sets the aspect of the ratio by swapping the left_number and
* right_number if necessary (or setting them to 1.0 in case that
* @aspect is %PIKA_ASPECT_SQUARE).
*
* Since: 2.4
**/
void
pika_number_pair_entry_set_aspect (PikaNumberPairEntry *entry,
PikaAspectType aspect)
{
PikaNumberPairEntryPrivate *priv;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
if (pika_number_pair_entry_get_aspect (entry) == aspect)
return;
priv = GET_PRIVATE (entry);
switch (aspect)
{
case PIKA_ASPECT_SQUARE:
pika_number_pair_entry_set_values (entry,
priv->left_number,
priv->left_number);
break;
case PIKA_ASPECT_LANDSCAPE:
case PIKA_ASPECT_PORTRAIT:
pika_number_pair_entry_set_values (entry,
priv->right_number,
priv->left_number);
break;
}
}
/**
* pika_number_pair_entry_get_aspect:
* @entry: A #PikaNumberPairEntry widget.
*
* Gets the aspect of the ratio displayed by a #PikaNumberPairEntry.
*
* Returns: The entry's current aspect.
*
* Since: 2.4
**/
PikaAspectType
pika_number_pair_entry_get_aspect (PikaNumberPairEntry *entry)
{
PikaNumberPairEntryPrivate *priv;
g_return_val_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry), PIKA_ASPECT_SQUARE);
priv = GET_PRIVATE (entry);
if (priv->left_number > priv->right_number)
{
return PIKA_ASPECT_LANDSCAPE;
}
else if (priv->left_number < priv->right_number)
{
return PIKA_ASPECT_PORTRAIT;
}
else
{
return PIKA_ASPECT_SQUARE;
}
}
static void
pika_number_pair_entry_modify_font (PikaNumberPairEntry *entry,
gboolean italic)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
GtkStyleContext *style;
if (priv->font_italic == italic)
return;
style = gtk_widget_get_style_context (GTK_WIDGET (entry));
if (italic)
gtk_style_context_add_class (style, "italic");
else
gtk_style_context_remove_class (style, "italic");
gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
GTK_ENTRY_ICON_SECONDARY,
! italic);
priv->font_italic = italic;
}
/**
* pika_number_pair_entry_set_user_override:
* @entry: A #PikaNumberPairEntry widget.
* @user_override: %TRUE sets the entry in user overridden mode,
* %FALSE disables.
*
* When the entry is not in user overridden mode, the values will
* change when the default values are changed. When in user overridden
* mode, setting default values will not affect the active values.
*
* Since: 2.4
**/
void
pika_number_pair_entry_set_user_override (PikaNumberPairEntry *entry,
gboolean user_override)
{
PikaNumberPairEntryPrivate *priv;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
priv = GET_PRIVATE (entry);
priv->user_override = user_override;
if (! user_override)
{
pika_number_pair_entry_set_default_values (entry,
priv->default_left_number,
priv->default_right_number);
}
pika_number_pair_entry_modify_font (entry, ! user_override);
g_object_notify (G_OBJECT (entry), "user-override");
}
/**
* pika_number_pair_entry_get_user_override:
* @entry: A #PikaNumberPairEntry widget.
*
* Returns: Whether or not the the widget is in user overridden mode.
*
* Since: 2.4
**/
gboolean
pika_number_pair_entry_get_user_override (PikaNumberPairEntry *entry)
{
PikaNumberPairEntryPrivate *priv;
g_return_val_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry), FALSE);
priv = GET_PRIVATE (entry);
return priv->user_override;
}
static void
pika_number_pair_entry_changed (PikaNumberPairEntry *entry)
{
pika_number_pair_entry_modify_font (entry, FALSE);
}
static void
pika_number_pair_entry_icon_press (PikaNumberPairEntry *entry)
{
pika_number_pair_entry_set_user_override (entry, FALSE);
gtk_editable_set_position (GTK_EDITABLE (entry), -1);
}
static gboolean
pika_number_pair_entry_events (GtkWidget *widget,
GdkEvent *event)
{
PikaNumberPairEntry *entry = PIKA_NUMBER_PAIR_ENTRY (widget);
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
gboolean force_user_override;
force_user_override = FALSE;
switch (event->type)
{
case GDK_KEY_PRESS:
{
GdkEventKey *kevent = (GdkEventKey *) event;
if (kevent->keyval != GDK_KEY_Return &&
kevent->keyval != GDK_KEY_KP_Enter &&
kevent->keyval != GDK_KEY_ISO_Enter)
break;
/* If parsing was done due to widgets focus being lost, we only change
* to user-override mode if the values differ from the default ones. If
* Return was pressed however, we always switch to user-override mode.
*/
force_user_override = TRUE;
}
/* Fall through */
case GDK_FOCUS_CHANGE:
{
const gchar *text;
ParseResult parse_result;
gdouble left_value;
gdouble right_value;
text = gtk_entry_get_text (GTK_ENTRY (entry));
parse_result = pika_number_pair_entry_parse_text (entry,
text,
&left_value,
&right_value);
switch (parse_result)
{
case PARSE_VALID:
{
if (priv->left_number != left_value ||
priv->right_number != right_value ||
force_user_override)
{
pika_number_pair_entry_set_values (entry,
left_value,
right_value);
pika_number_pair_entry_set_user_override (entry, TRUE);
}
}
break;
case PARSE_CLEAR:
pika_number_pair_entry_set_user_override (entry, FALSE);
break;
default:
break;
}
/* Make sure the entry text is up to date */
pika_number_pair_entry_update_text (entry);
gtk_editable_set_position (GTK_EDITABLE (entry), -1);
}
break;
default:
break;
}
return FALSE;
}
/**
* pika_number_pair_entry_strdup_number_pair_string:
* @entry:
* @left_number:
* @right_number:
*
* Returns: allocated data, must be g_free:d.
**/
static gchar *
pika_number_pair_entry_strdup_number_pair_string (PikaNumberPairEntry *entry,
gdouble left_number,
gdouble right_number)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
gchar sep[8];
gint len;
if (priv->num_separators > 0)
len = g_unichar_to_utf8 (priv->separators[0], sep);
else
len = g_unichar_to_utf8 (DEFAULT_SEPARATOR, sep);
sep[len] = '\0';
return g_strdup_printf ("%g%s%g", left_number, sep, right_number);
}
static void
pika_number_pair_entry_update_text (PikaNumberPairEntry *entry)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
gchar *buffer;
if (! priv->user_override &&
priv->default_text != NULL)
{
/* Instead of the numbers, show the string explicitly set by a
* client to show when in automatic mode.
*/
buffer = g_strdup (priv->default_text);
}
else
{
buffer = pika_number_pair_entry_strdup_number_pair_string (entry,
priv->left_number,
priv->right_number);
}
g_signal_handlers_block_by_func (entry,
pika_number_pair_entry_changed, NULL);
gtk_entry_set_text (GTK_ENTRY (entry), buffer);
g_free (buffer);
g_signal_handlers_unblock_by_func (entry,
pika_number_pair_entry_changed, NULL);
pika_number_pair_entry_modify_font (entry, ! priv->user_override);
}
static gboolean
pika_number_pair_entry_valid_separator (PikaNumberPairEntry *entry,
gunichar candidate)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
if (priv->num_separators > 0)
{
gint i;
for (i = 0; i < priv->num_separators; i++)
if (priv->separators[i] == candidate)
return TRUE;
}
else if (candidate == DEFAULT_SEPARATOR)
{
return TRUE;
}
return FALSE;
}
static ParseResult
pika_number_pair_entry_parse_text (PikaNumberPairEntry *entry,
const gchar *text,
gdouble *left_value,
gdouble *right_value)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
gdouble new_left_number;
gdouble new_right_number;
gboolean simplify = FALSE;
gchar *end;
/* skip over whitespace */
while (g_unichar_isspace (g_utf8_get_char (text)))
text = g_utf8_next_char (text);
/* check if clear */
if (! *text)
return PARSE_CLEAR;
/* try to parse a number */
new_left_number = strtod (text, &end);
if (end == text)
return PARSE_INVALID;
else
text = end;
/* skip over whitespace */
while (g_unichar_isspace (g_utf8_get_char (text)))
text = g_utf8_next_char (text);
/* check for a valid separator */
if (! pika_number_pair_entry_valid_separator (entry, g_utf8_get_char (text)))
return PARSE_INVALID;
else
text = g_utf8_next_char (text);
/* try to parse another number */
new_right_number = strtod (text, &end);
if (end == text)
return PARSE_INVALID;
else
text = end;
/* skip over whitespace */
while (g_unichar_isspace (g_utf8_get_char (text)))
text = g_utf8_next_char (text);
/* check for the simplification char */
if (g_utf8_get_char (text) == SIMPLIFICATION_CHAR)
{
simplify = priv->allow_simplification;
text = g_utf8_next_char (text);
}
/* skip over whitespace */
while (g_unichar_isspace (g_utf8_get_char (text)))
text = g_utf8_next_char (text);
/* check for trailing garbage */
if (*text)
return PARSE_INVALID;
if (! pika_number_pair_entry_numbers_in_range (entry,
new_left_number,
new_right_number))
return PARSE_INVALID;
if (simplify && new_right_number != 0.0)
{
pika_number_pair_entry_ratio_to_fraction (new_left_number /
new_right_number,
left_value,
right_value);
}
else
{
*left_value = new_left_number;
*right_value = new_right_number;
}
return PARSE_VALID;
}
static void
pika_number_pair_entry_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaNumberPairEntry *entry = PIKA_NUMBER_PAIR_ENTRY (object);
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
switch (property_id)
{
case PROP_LEFT_NUMBER:
pika_number_pair_entry_set_values (entry,
g_value_get_double (value),
priv->right_number);
break;
case PROP_RIGHT_NUMBER:
pika_number_pair_entry_set_values (entry,
priv->left_number,
g_value_get_double (value));
break;
case PROP_DEFAULT_LEFT_NUMBER:
pika_number_pair_entry_set_default_values (entry,
g_value_get_double (value),
priv->default_right_number);
break;
case PROP_DEFAULT_RIGHT_NUMBER:
pika_number_pair_entry_set_default_values (entry,
priv->default_left_number,
g_value_get_double (value));
break;
case PROP_USER_OVERRIDE:
pika_number_pair_entry_set_user_override (entry,
g_value_get_boolean (value));
break;
case PROP_SEPARATORS:
g_free (priv->separators);
priv->num_separators = 0;
if (g_value_get_string (value))
priv->separators = g_utf8_to_ucs4 (g_value_get_string (value), -1,
NULL, &priv->num_separators, NULL);
else
priv->separators = NULL;
break;
case PROP_DEFAULT_TEXT:
pika_number_pair_entry_set_default_text (entry,
g_value_get_string (value));
break;
case PROP_ALLOW_SIMPLIFICATION:
priv->allow_simplification = g_value_get_boolean (value);
break;
case PROP_MIN_VALID_VALUE:
priv->min_valid_value = g_value_get_double (value);
break;
case PROP_MAX_VALID_VALUE:
priv->max_valid_value = g_value_get_double (value);
break;
case PROP_RATIO:
pika_number_pair_entry_set_ratio (entry, g_value_get_double (value));
break;
case PROP_ASPECT:
pika_number_pair_entry_set_aspect (entry, g_value_get_enum (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_number_pair_entry_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaNumberPairEntry *entry = PIKA_NUMBER_PAIR_ENTRY (object);
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
switch (property_id)
{
case PROP_LEFT_NUMBER:
g_value_set_double (value, priv->left_number);
break;
case PROP_RIGHT_NUMBER:
g_value_set_double (value, priv->right_number);
break;
case PROP_DEFAULT_LEFT_NUMBER:
g_value_set_double (value, priv->default_left_number);
break;
case PROP_DEFAULT_RIGHT_NUMBER:
g_value_set_double (value, priv->default_right_number);
break;
case PROP_USER_OVERRIDE:
g_value_set_boolean (value, priv->user_override);
break;
case PROP_SEPARATORS:
g_value_take_string (value,
g_ucs4_to_utf8 (priv->separators,
priv->num_separators,
NULL, NULL, NULL));
break;
case PROP_ALLOW_SIMPLIFICATION:
g_value_set_boolean (value, priv->allow_simplification);
break;
case PROP_DEFAULT_TEXT:
g_value_set_string (value, priv->default_text);
break;
case PROP_MIN_VALID_VALUE:
g_value_set_double (value, priv->min_valid_value);
break;
case PROP_MAX_VALID_VALUE:
g_value_set_double (value, priv->max_valid_value);
break;
case PROP_RATIO:
g_value_set_double (value, pika_number_pair_entry_get_ratio (entry));
break;
case PROP_ASPECT:
g_value_set_enum (value, pika_number_pair_entry_get_aspect (entry));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
/**
* pika_number_pair_entry_set_default_values:
* @entry: A #PikaNumberPairEntry widget.
* @left: Default left value in the entry.
* @right: Default right value in the entry.
*
* Since: 2.4
**/
void
pika_number_pair_entry_set_default_values (PikaNumberPairEntry *entry,
gdouble left,
gdouble right)
{
PikaNumberPairEntryPrivate *priv;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
priv = GET_PRIVATE (entry);
priv->default_left_number = left;
priv->default_right_number = right;
if (! priv->user_override)
{
pika_number_pair_entry_set_values (entry,
priv->default_left_number,
priv->default_right_number);
}
}
/**
* pika_number_pair_entry_get_default_values:
* @entry: A #PikaNumberPairEntry widget.
* @left: (out) (optional): Location to put the default left value, or %NULL.
* @right: (out) (optional): Location to put the default right value, or %NULL.
*
* Since: 2.4
**/
void
pika_number_pair_entry_get_default_values (PikaNumberPairEntry *entry,
gdouble *left,
gdouble *right)
{
PikaNumberPairEntryPrivate *priv;
g_return_if_fail (PIKA_IS_NUMBER_PAIR_ENTRY (entry));
priv = GET_PRIVATE (entry);
if (left != NULL)
*left = priv->default_left_number;
if (right != NULL)
*right = priv->default_right_number;
}
static gboolean
pika_number_pair_entry_numbers_in_range (PikaNumberPairEntry *entry,
gdouble left_number,
gdouble right_number)
{
PikaNumberPairEntryPrivate *priv = GET_PRIVATE (entry);
return (left_number >= priv->min_valid_value &&
left_number <= priv->max_valid_value &&
right_number >= priv->min_valid_value &&
right_number <= priv->max_valid_value);
}