summaryrefslogtreecommitdiffstats
path: root/libgimpwidgets/gimpnumberpairentry.c
diff options
context:
space:
mode:
Diffstat (limited to 'libgimpwidgets/gimpnumberpairentry.c')
-rw-r--r--libgimpwidgets/gimpnumberpairentry.c1301
1 files changed, 1301 insertions, 0 deletions
diff --git a/libgimpwidgets/gimpnumberpairentry.c b/libgimpwidgets/gimpnumberpairentry.c
new file mode 100644
index 0000000..c41fda7
--- /dev/null
+++ b/libgimpwidgets/gimpnumberpairentry.c
@@ -0,0 +1,1301 @@
+/* LIBGIMP - The GIMP Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * gimpnumberpairentry.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 "libgimpmath/gimpmath.h"
+
+#include "gimpwidgetstypes.h"
+
+#include "gimpicons.h"
+#include "gimpnumberpairentry.h"
+
+
+/**
+ * SECTION: gimpnumberpairentry
+ * @title: GimpNumberPairEntry
+ * @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;
+
+typedef struct
+{
+ /* 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;
+} GimpNumberPairEntryPrivate;
+
+#define GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE(obj) \
+ ((GimpNumberPairEntryPrivate *) ((GimpNumberPairEntry *) (obj))->priv)
+
+
+static void gimp_number_pair_entry_finalize (GObject *entry);
+
+static gboolean gimp_number_pair_entry_valid_separator (GimpNumberPairEntry *entry,
+ gunichar candidate);
+static void gimp_number_pair_entry_ratio_to_fraction (gdouble ratio,
+ gdouble *numerator,
+ gdouble *denominator);
+
+static void gimp_number_pair_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_number_pair_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gimp_number_pair_entry_changed (GimpNumberPairEntry *entry);
+static void gimp_number_pair_entry_icon_press (GimpNumberPairEntry *entry);
+static gboolean gimp_number_pair_entry_events (GtkWidget *widget,
+ GdkEvent *event);
+
+static void gimp_number_pair_entry_update_text (GimpNumberPairEntry *entry);
+
+static ParseResult gimp_number_pair_entry_parse_text (GimpNumberPairEntry *entry,
+ const gchar *text,
+ gdouble *left_value,
+ gdouble *right_value);
+static gboolean gimp_number_pair_entry_numbers_in_range (GimpNumberPairEntry *entry,
+ gdouble left_number,
+ gdouble right_number);
+
+static gchar * gimp_number_pair_entry_strdup_number_pair_string
+ (GimpNumberPairEntry *entry,
+ gdouble left_number,
+ gdouble right_number);
+
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpNumberPairEntry, gimp_number_pair_entry,
+ GTK_TYPE_ENTRY)
+
+
+#define parent_class gimp_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
+gimp_number_pair_entry_class_init (GimpNumberPairEntryClass *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 (GimpNumberPairEntryClass, numbers_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ 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 (GimpNumberPairEntryClass, ratio_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ object_class->set_property = gimp_number_pair_entry_set_property;
+ object_class->get_property = gimp_number_pair_entry_get_property;
+ object_class->finalize = gimp_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,
+ GIMP_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,
+ GIMP_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,
+ GIMP_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,
+ GIMP_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,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SEPARATORS,
+ g_param_spec_string ("separators",
+ "Separators",
+ "A string of valid separators",
+ NULL,
+ GIMP_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,
+ GIMP_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,
+ GIMP_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,
+ GIMP_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,
+ GIMP_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,
+ GIMP_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_ASPECT,
+ g_param_spec_enum ("aspect",
+ "Aspect",
+ "The value as aspect",
+ GIMP_TYPE_ASPECT_TYPE,
+ GIMP_ASPECT_SQUARE,
+ GIMP_PARAM_READWRITE));
+}
+
+static void
+gimp_number_pair_entry_init (GimpNumberPairEntry *entry)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ entry->priv = gimp_number_pair_entry_get_instance_private (entry);
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_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 (gimp_number_pair_entry_changed),
+ NULL);
+ g_signal_connect (entry, "icon-press",
+ G_CALLBACK (gimp_number_pair_entry_icon_press),
+ NULL);
+ g_signal_connect (entry, "focus-out-event",
+ G_CALLBACK (gimp_number_pair_entry_events),
+ NULL);
+ g_signal_connect (entry, "key-press-event",
+ G_CALLBACK (gimp_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,
+ GIMP_ICON_EDIT_CLEAR);
+}
+
+static void
+gimp_number_pair_entry_finalize (GObject *object)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_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);
+}
+
+/**
+ * gimp_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 #GimpNumberPairEntry 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.
+ *
+ * Return value: The new #GimpNumberPairEntry widget.
+ *
+ * Since: 2.4
+ **/
+GtkWidget *
+gimp_number_pair_entry_new (const gchar *separators,
+ gboolean allow_simplification,
+ gdouble min_valid_value,
+ gdouble max_valid_value)
+{
+ return g_object_new (GIMP_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
+gimp_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;
+ }
+}
+
+/**
+ * gimp_number_pair_entry_set_ratio:
+ * @entry: A #GimpNumberPairEntry widget.
+ * @ratio: Ratio to set in the widget.
+ *
+ * Sets the numbers of the #GimpNumberPairEntry 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
+gimp_number_pair_entry_set_ratio (GimpNumberPairEntry *entry,
+ gdouble ratio)
+{
+ gdouble numerator;
+ gdouble denominator;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ gimp_number_pair_entry_ratio_to_fraction (ratio, &numerator, &denominator);
+
+ gimp_number_pair_entry_set_values (entry, numerator, denominator);
+}
+
+/**
+ * gimp_number_pair_entry_get_ratio:
+ * @entry: A #GimpNumberPairEntry widget.
+ *
+ * Retrieves the ratio of the numbers displayed by a #GimpNumberPairEntry.
+ *
+ * Returns: The ratio value.
+ *
+ * Since: 2.4
+ **/
+gdouble
+gimp_number_pair_entry_get_ratio (GimpNumberPairEntry *entry)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), 1.0);
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ return priv->left_number / priv->right_number;
+}
+
+/**
+ * gimp_number_pair_entry_set_values:
+ * @entry: A #GimpNumberPairEntry widget.
+ * @left: Left number in the entry.
+ * @right: Right number in the entry.
+ *
+ * Forces setting the numbers displayed by a #GimpNumberPairEntry,
+ * ignoring if the user has set his/her own value. The state of
+ * user-override will not be changed.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_number_pair_entry_set_values (GimpNumberPairEntry *entry,
+ gdouble left,
+ gdouble right)
+{
+ GimpNumberPairEntryPrivate *priv;
+ GimpAspectType 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 (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ /* Store current values */
+
+ old_left_number = priv->left_number;
+ old_right_number = priv->right_number;
+ old_ratio = gimp_number_pair_entry_get_ratio (entry);
+ old_aspect = gimp_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");
+
+ gimp_number_pair_entry_update_text (entry);
+
+
+ /* Find out what has changed */
+
+ if (fabs (old_ratio - gimp_number_pair_entry_get_ratio (entry)) > EPSILON)
+ {
+ g_object_notify (G_OBJECT (entry), "ratio");
+
+ ratio_changed = TRUE;
+
+ if (old_aspect != gimp_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);
+}
+
+/**
+ * gimp_number_pair_entry_get_values:
+ * @entry: A #GimpNumberPairEntry widget.
+ * @left: Pointer of where to store the left number (may be %NULL).
+ * @right: Pointer of to store the right number (may be %NULL).
+ *
+ * Gets the numbers displayed by a #GimpNumberPairEntry.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_number_pair_entry_get_values (GimpNumberPairEntry *entry,
+ gdouble *left,
+ gdouble *right)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ if (left != NULL)
+ *left = priv->left_number;
+
+ if (right != NULL)
+ *right = priv->right_number;
+}
+
+/**
+ * gimp_number_pair_entry_set_default_text:
+ * @entry: A #GimpNumberPairEntry 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 #GimpNumberPairEntry 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
+gimp_number_pair_entry_set_default_text (GimpNumberPairEntry *entry,
+ const gchar *string)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ g_free (priv->default_text);
+ priv->default_text = g_strdup (string);
+
+ gimp_number_pair_entry_update_text (entry);
+
+ g_object_notify (G_OBJECT (entry), "default-text");
+}
+
+/**
+ * gimp_number_pair_entry_get_default_text:
+ * @entry: A #GimpNumberPairEntry widget.
+ *
+ * Returns: the string manually set to be shown, or %NULL if values are
+ * shown in a normal fashion.
+ *
+ * Since: 2.4
+ */
+const gchar *
+gimp_number_pair_entry_get_default_text (GimpNumberPairEntry *entry)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), NULL);
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ return priv->default_text;
+}
+
+/**
+ * gimp_number_pair_entry_set_aspect:
+ * @entry: A #GimpNumberPairEntry 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 %GIMP_ASPECT_SQUARE).
+ *
+ * Since: 2.4
+ **/
+void
+gimp_number_pair_entry_set_aspect (GimpNumberPairEntry *entry,
+ GimpAspectType aspect)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ if (gimp_number_pair_entry_get_aspect (entry) == aspect)
+ return;
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ switch (aspect)
+ {
+ case GIMP_ASPECT_SQUARE:
+ gimp_number_pair_entry_set_values (entry,
+ priv->left_number,
+ priv->left_number);
+ break;
+
+ case GIMP_ASPECT_LANDSCAPE:
+ case GIMP_ASPECT_PORTRAIT:
+ gimp_number_pair_entry_set_values (entry,
+ priv->right_number,
+ priv->left_number);
+ break;
+ }
+}
+
+/**
+ * gimp_number_pair_entry_get_aspect:
+ * @entry: A #GimpNumberPairEntry widget.
+ *
+ * Gets the aspect of the ratio displayed by a #GimpNumberPairEntry.
+ *
+ * Returns: The entry's current aspect.
+ *
+ * Since: 2.4
+ **/
+GimpAspectType
+gimp_number_pair_entry_get_aspect (GimpNumberPairEntry *entry)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), GIMP_ASPECT_SQUARE);
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ if (priv->left_number > priv->right_number)
+ {
+ return GIMP_ASPECT_LANDSCAPE;
+ }
+ else if (priv->left_number < priv->right_number)
+ {
+ return GIMP_ASPECT_PORTRAIT;
+ }
+ else
+ {
+ return GIMP_ASPECT_SQUARE;
+ }
+}
+
+static void
+gimp_number_pair_entry_modify_font (GimpNumberPairEntry *entry,
+ gboolean italic)
+{
+ GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+ GtkRcStyle *rc_style;
+
+ if (priv->font_italic == italic)
+ return;
+
+ rc_style = gtk_widget_get_modifier_style (GTK_WIDGET (entry));
+
+ if (! rc_style->font_desc)
+ {
+ PangoContext *context;
+ PangoFontDescription *font_desc;
+
+ context = gtk_widget_get_pango_context (GTK_WIDGET (entry));
+ font_desc = pango_context_get_font_description (context);
+
+ rc_style->font_desc = pango_font_description_copy (font_desc);
+ }
+
+ pango_font_description_set_style (rc_style->font_desc,
+ italic ?
+ PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
+
+ gtk_widget_modify_style (GTK_WIDGET (entry), rc_style);
+
+ gtk_entry_set_icon_sensitive (GTK_ENTRY (entry),
+ GTK_ENTRY_ICON_SECONDARY,
+ ! italic);
+
+ priv->font_italic = italic;
+}
+
+
+/**
+ * gimp_number_pair_entry_set_user_override:
+ * @entry: A #GimpNumberPairEntry 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
+gimp_number_pair_entry_set_user_override (GimpNumberPairEntry *entry,
+ gboolean user_override)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ priv->user_override = user_override;
+
+ if (! user_override)
+ {
+ gimp_number_pair_entry_set_default_values (entry,
+ priv->default_left_number,
+ priv->default_right_number);
+ }
+
+ gimp_number_pair_entry_modify_font (entry, ! user_override);
+
+ g_object_notify (G_OBJECT (entry), "user-override");
+}
+
+/**
+ * gimp_number_pair_entry_get_user_override:
+ * @entry: A #GimpNumberPairEntry widget.
+ *
+ * Returns: Whether or not the the widget is in user overridden mode.
+ *
+ * Since: 2.4
+ **/
+gboolean
+gimp_number_pair_entry_get_user_override (GimpNumberPairEntry *entry)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_val_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry), FALSE);
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ return priv->user_override;
+}
+
+static void
+gimp_number_pair_entry_changed (GimpNumberPairEntry *entry)
+{
+ gimp_number_pair_entry_modify_font (entry, FALSE);
+}
+
+static void
+gimp_number_pair_entry_icon_press (GimpNumberPairEntry *entry)
+{
+ gimp_number_pair_entry_set_user_override (entry, FALSE);
+
+ gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+}
+
+static gboolean
+gimp_number_pair_entry_events (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GimpNumberPairEntry *entry;
+ GimpNumberPairEntryPrivate *priv;
+ gboolean force_user_override;
+
+ entry = GIMP_NUMBER_PAIR_ENTRY (widget);
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+ 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 = gimp_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)
+ {
+ gimp_number_pair_entry_set_values (entry,
+ left_value,
+ right_value);
+
+ gimp_number_pair_entry_set_user_override (entry, TRUE);
+ }
+ }
+ break;
+
+ case PARSE_CLEAR:
+ gimp_number_pair_entry_set_user_override (entry, FALSE);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Make sure the entry text is up to date */
+
+ gimp_number_pair_entry_update_text (entry);
+
+ gtk_editable_set_position (GTK_EDITABLE (entry), -1);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/**
+ * gimp_number_pair_entry_strdup_number_pair_string:
+ * @entry:
+ * @left_number:
+ * @right_number:
+ *
+ * Returns: allocated data, must be g_free:d.
+ **/
+static gchar *
+gimp_number_pair_entry_strdup_number_pair_string (GimpNumberPairEntry *entry,
+ gdouble left_number,
+ gdouble right_number)
+{
+ GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_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
+gimp_number_pair_entry_update_text (GimpNumberPairEntry *entry)
+{
+ GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_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 = gimp_number_pair_entry_strdup_number_pair_string (entry,
+ priv->left_number,
+ priv->right_number);
+ }
+
+ g_signal_handlers_block_by_func (entry,
+ gimp_number_pair_entry_changed, NULL);
+
+ gtk_entry_set_text (GTK_ENTRY (entry), buffer);
+ g_free (buffer);
+
+ g_signal_handlers_unblock_by_func (entry,
+ gimp_number_pair_entry_changed, NULL);
+
+ gimp_number_pair_entry_modify_font (entry, ! priv->user_override);
+}
+
+static gboolean
+gimp_number_pair_entry_valid_separator (GimpNumberPairEntry *entry,
+ gunichar candidate)
+{
+ GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_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
+gimp_number_pair_entry_parse_text (GimpNumberPairEntry *entry,
+ const gchar *text,
+ gdouble *left_value,
+ gdouble *right_value)
+{
+ GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_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 (! gimp_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 (! gimp_number_pair_entry_numbers_in_range (entry,
+ new_left_number,
+ new_right_number))
+ return PARSE_INVALID;
+
+ if (simplify && new_right_number != 0.0)
+ {
+ gimp_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
+gimp_number_pair_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpNumberPairEntry *entry = GIMP_NUMBER_PAIR_ENTRY (object);
+ GimpNumberPairEntryPrivate *priv;
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ switch (property_id)
+ {
+ case PROP_LEFT_NUMBER:
+ gimp_number_pair_entry_set_values (entry,
+ g_value_get_double (value),
+ priv->right_number);
+ break;
+ case PROP_RIGHT_NUMBER:
+ gimp_number_pair_entry_set_values (entry,
+ priv->left_number,
+ g_value_get_double (value));
+ break;
+ case PROP_DEFAULT_LEFT_NUMBER:
+ gimp_number_pair_entry_set_default_values (entry,
+ g_value_get_double (value),
+ priv->default_right_number);
+ break;
+ case PROP_DEFAULT_RIGHT_NUMBER:
+ gimp_number_pair_entry_set_default_values (entry,
+ priv->default_left_number,
+ g_value_get_double (value));
+ break;
+ case PROP_USER_OVERRIDE:
+ gimp_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:
+ gimp_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:
+ gimp_number_pair_entry_set_ratio (entry, g_value_get_double (value));
+ break;
+ case PROP_ASPECT:
+ gimp_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
+gimp_number_pair_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpNumberPairEntry *entry = GIMP_NUMBER_PAIR_ENTRY (object);
+ GimpNumberPairEntryPrivate *priv;
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_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, gimp_number_pair_entry_get_ratio (entry));
+ break;
+ case PROP_ASPECT:
+ g_value_set_enum (value, gimp_number_pair_entry_get_aspect (entry));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * gimp_number_pair_entry_set_default_values:
+ * @entry: A #GimpNumberPairEntry widget.
+ * @left: Default left value in the entry.
+ * @right: Default right value in the entry.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_number_pair_entry_set_default_values (GimpNumberPairEntry *entry,
+ gdouble left,
+ gdouble right)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ priv->default_left_number = left;
+ priv->default_right_number = right;
+
+ if (! priv->user_override)
+ {
+ gimp_number_pair_entry_set_values (entry,
+ priv->default_left_number,
+ priv->default_right_number);
+ }
+}
+
+/**
+ * gimp_number_pair_entry_get_default_values:
+ * @entry: A #GimpNumberPairEntry widget.
+ * @left: Pointer of where to put left value.
+ * @right: Pointer of where to put right value.
+ *
+ * Since: 2.4
+ **/
+void
+gimp_number_pair_entry_get_default_values (GimpNumberPairEntry *entry,
+ gdouble *left,
+ gdouble *right)
+{
+ GimpNumberPairEntryPrivate *priv;
+
+ g_return_if_fail (GIMP_IS_NUMBER_PAIR_ENTRY (entry));
+
+ priv = GIMP_NUMBER_PAIR_ENTRY_GET_PRIVATE (entry);
+
+ if (left != NULL)
+ *left = priv->default_left_number;
+
+ if (right != NULL)
+ *right = priv->default_right_number;
+}
+
+static gboolean
+gimp_number_pair_entry_numbers_in_range (GimpNumberPairEntry *entry,
+ gdouble left_number,
+ gdouble right_number)
+{
+ GimpNumberPairEntryPrivate *priv = GIMP_NUMBER_PAIR_ENTRY_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);
+}