Bug 76616 – Size entry widgets could use some simple math

Add a simple parser to the GimpSizeEntry widget so that one can write
things such as "40in" and "50%" in a size entry widget and get that
converted to the current unit.

The parser also handles basic expresions such as "20cm + 20px" and
"2 * 3.14in".
This commit is contained in:
Fredrik Alströmer 2009-05-17 11:33:35 +02:00 committed by Martin Nordholts
parent 6742ebc949
commit bcee243fa3
5 changed files with 1063 additions and 15 deletions

View File

@ -102,6 +102,8 @@ libgimpwidgets_2_0_la_sources = \
gimpcontroller.h \
gimpdialog.c \
gimpdialog.h \
gimpeevl.c \
gimpeevl.h \
gimpenumcombobox.c \
gimpenumcombobox.h \
gimpenumlabel.c \
@ -321,7 +323,10 @@ gimp-wilber-pixbufs.h: $(WILBER_IMAGES) Makefile.am
# test programs, not installed
#
EXTRA_PROGRAMS = test-preview-area
EXTRA_PROGRAMS = \
test-preview-area \
test-eevl
test_preview_area_SOURCES = test-preview-area.c
@ -334,6 +339,28 @@ test_preview_area_LDADD = \
$(test_preview_area_DEPENDENCIES)
test_eevl_SOURCES = \
test-eevl.c
test_eevl_DEPENDENCIES = \
$(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
test_eevl_LDADD = \
$(GLIB_LIBS) \
$(test_eevl_DEPENDENCIES)
#
# test programs, not to be built by default and never installed
#
TESTS = test-eevl$(EXEEXT)
CLEANFILES += $(EXTRA_PROGRAMS)
install-data-local: install-ms-lib install-libtool-import-lib

579
libgimpwidgets/gimpeevl.c Normal file
View File

@ -0,0 +1,579 @@
/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* gimpeevl.c
* Copyright (C) 2008 Fredrik Alstromer <roe@excu.se>
* Copyright (C) 2008 Martin Nordholts <martinn@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
* 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
* <http://www.gnu.org/licenses/>.
*/
/* Introducing eevl eva, the evaluator. A straightforward recursive
* descent parser, no fuss, no new dependencies. The lexer is hand
* coded, tedious, not extremely fast but works. It evaluates the
* expression as it goes along, and does not create a parse tree or
* anything, and will not optimize anything. It uses doubles for
* precision, with the given use case, that's enough to combat any
* rounding errors (as opposed to optimizing the evalutation).
*
* It relies on external unit resolving through a callback and does
* elementary dimensionality constraint check (e.g. "2 mm + 3 px * 4
* in" is an error, as L + L^2 is a missmatch). It uses setjmp/longjmp
* for try/catch like pattern on error, it uses g_strtod() for numeric
* conversions and it's non-destructive in terms of the paramters, and
* it's reentrant.
*
* EBNF:
*
* expression ::= term { ('+' | '-') term }* |
* <empty string> ;
*
* term ::= signed factor { ( '*' | '/' ) signed factor }* ;
*
* signed factor ::= ( '+' | '-' )? factor ;
*
* unit factor ::= factor unit? ;
*
* factor ::= number | '(' expression ')' ;
*
* number ::= ? what g_strtod() consumes ? ;
*
* unit ::= ? what not g_strtod() consumes and not whitespace ? ;
*
* The code should match the EBNF rather closely (except for the
* non-terminal unit factor, which is inlined into factor) for
* maintainability reasons.
*
* It will allow 1++1 and 1+-1 (resulting in 2 and 0, respectively),
* but I figured one might want that, and I don't think it's going to
* throw anyone off.
*/
#include "config.h"
#include <setjmp.h>
#include <string.h>
#include <glib-object.h>
#include "gimpeevl.h"
#include "gimpwidgets-error.h"
#include "libgimp/libgimp-intl.h"
typedef enum
{
GIMP_EEVL_TOKEN_NUM = 30000,
GIMP_EEVL_TOKEN_IDENTIFIER = 30001,
GIMP_EEVL_TOKEN_ANY = 40000,
GIMP_EEVL_TOKEN_END = 50000
} GimpEevlTokenType;
typedef struct
{
GimpEevlTokenType type;
union
{
gdouble fl;
struct
{
const gchar *c;
gint size;
};
} value;
} GimpEevlToken;
typedef struct
{
const gchar *string;
GimpEevlUnitResolverProc unit_resolver_proc;
gpointer data;
GimpEevlToken current_token;
const gchar *start_of_current_token;
jmp_buf catcher;
const gchar *error_message;
} GimpEevl;
static void gimp_eevl_init (GimpEevl *eva,
const gchar *string,
GimpEevlUnitResolverProc unit_resolver_proc,
gpointer data);
static GimpEevlQuantity gimp_eevl_complete (GimpEevl *eva);
static GimpEevlQuantity gimp_eevl_expression (GimpEevl *eva);
static GimpEevlQuantity gimp_eevl_term (GimpEevl *eva);
static GimpEevlQuantity gimp_eevl_signed_factor (GimpEevl *eva);
static GimpEevlQuantity gimp_eevl_factor (GimpEevl *eva);
static gboolean gimp_eevl_accept (GimpEevl *eva,
GimpEevlTokenType token_type,
GimpEevlToken *consumed_token);
static void gimp_eevl_lex (GimpEevl *eva);
static void gimp_eevl_lex_accept_count (GimpEevl *eva,
gint count,
GimpEevlTokenType token_type);
static void gimp_eevl_lex_accept_to (GimpEevl *eva,
gchar *to,
GimpEevlTokenType token_type);
static void gimp_eevl_move_past_whitespace (GimpEevl *eva);
static gboolean gimp_eevl_unit_identifier_start (gunichar c);
static gboolean gimp_eevl_unit_identifier_continue (gunichar c);
static gint gimp_eevl_unit_identifier_size (const gchar *s,
gint start);
static void gimp_eevl_expect (GimpEevl *eva,
GimpEevlTokenType token_type,
GimpEevlToken *value);
static void gimp_eevl_error (GimpEevl *eva,
gchar *msg);
/**
* gimp_eevl_evaluate:
* @string: The NULL-terminated string to be evaluated.
* @unit_resolver_proc: Unit resolver callback.
* @result: Result of evaluation.
* @data: Data passed to unit resolver.
* @error_pos: Will point to the poisiton within the string,
* before which the parse / evaluation error
* occured. Will be set to null of no error occured.
* @error_message: Will point to a static string with a semi-descriptive
* error message if parsing / evaluation failed.
*
* Evaluates the given arithmetic expression, along with an optional dimension
* analysis, and basic unit conversions.
*
* All units conversions factors are relative to some implicit
* base-unit (which in GIMP is inches). This is also the unit of the
* returned value.
*
* Returns: A #GimpEevlQuantity with a value given in the base unit along with
* the order of the dimension (i.e. if the base unit is inches, a dimension
* order of two menas in^2).
**/
gboolean
gimp_eevl_evaluate (const gchar *string,
GimpEevlUnitResolverProc unit_resolver_proc,
GimpEevlQuantity *result,
gpointer data,
const gchar **error_pos,
GError **error)
{
GimpEevl eva;
g_return_val_if_fail (g_utf8_validate (string, -1, NULL), FALSE);
g_return_val_if_fail (unit_resolver_proc != NULL, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
gimp_eevl_init (&eva,
string,
unit_resolver_proc,
data);
if (!setjmp (eva.catcher)) /* try... */
{
*result = gimp_eevl_complete (&eva);
return TRUE;
}
else /* catch.. */
{
if (error_pos)
*error_pos = eva.start_of_current_token;
g_set_error_literal (error,
GIMP_WIDGETS_ERROR,
GIMP_WIDGETS_PARSE_ERROR,
eva.error_message);
return FALSE;
}
}
static void
gimp_eevl_init (GimpEevl *eva,
const gchar *string,
GimpEevlUnitResolverProc unit_resolver_proc,
gpointer data)
{
eva->string = string;
eva->unit_resolver_proc = unit_resolver_proc;
eva->data = data;
eva->current_token.type = GIMP_EEVL_TOKEN_END;
eva->error_message = NULL;
/* Preload symbol... */
gimp_eevl_lex (eva);
}
static GimpEevlQuantity
gimp_eevl_complete (GimpEevl *eva)
{
GimpEevlQuantity result = {0, 0};
GimpEevlQuantity default_unit_factor;
/* Empty expression evaluates to 0 */
if (gimp_eevl_accept (eva, GIMP_EEVL_TOKEN_END, NULL))
return result;
result = gimp_eevl_expression (eva);
/* There should be nothing left to parse by now */
gimp_eevl_expect (eva, GIMP_EEVL_TOKEN_END, 0);
eva->unit_resolver_proc (NULL,
&default_unit_factor,
eva->data);
/* Entire expression is dimensionless, apply default unit if
* applicable
*/
if (result.dimension == 0 && default_unit_factor.dimension != 0)
{
result.value /= default_unit_factor.value;
result.dimension = default_unit_factor.dimension;
}
return result;
}
static GimpEevlQuantity
gimp_eevl_expression (GimpEevl *eva)
{
gboolean subtract;
GimpEevlQuantity evaluated_terms;
evaluated_terms = gimp_eevl_term (eva);
/* continue evaluating terms, chained with + or -. */
for (subtract = FALSE;
gimp_eevl_accept (eva, '+', NULL) ||
(subtract = gimp_eevl_accept (eva, '-', NULL));
subtract = FALSE)
{
GimpEevlQuantity new_term = gimp_eevl_term (eva);
/* If dimensions missmatch, attempt default unit assignent */
if (new_term.dimension != evaluated_terms.dimension)
{
GimpEevlQuantity default_unit_factor;
eva->unit_resolver_proc (NULL,
&default_unit_factor,
eva->data);
if (new_term.dimension == 0 &&
evaluated_terms.dimension == default_unit_factor.dimension)
{
new_term.value /= default_unit_factor.value;
new_term.dimension = default_unit_factor.dimension;
}
else if (evaluated_terms.dimension == 0 &&
new_term.dimension == default_unit_factor.dimension)
{
evaluated_terms.value /= default_unit_factor.value;
evaluated_terms.dimension = default_unit_factor.dimension;
}
else
{
gimp_eevl_error (eva, "Dimension missmatch during addition");
}
}
evaluated_terms.value += (subtract ? -new_term.value : new_term.value);
}
return evaluated_terms;
}
static GimpEevlQuantity
gimp_eevl_term (GimpEevl *eva)
{
gboolean division;
GimpEevlQuantity evaluated_signed_factors;
evaluated_signed_factors = gimp_eevl_signed_factor (eva);
for (division = FALSE;
gimp_eevl_accept (eva, '*', NULL) ||
(division = gimp_eevl_accept (eva, '/', NULL));
division = FALSE)
{
GimpEevlQuantity new_signed_factor = gimp_eevl_signed_factor (eva);
if (division)
{
evaluated_signed_factors.value /= new_signed_factor.value;
evaluated_signed_factors.dimension -= new_signed_factor.dimension;
}
else
{
evaluated_signed_factors.value *= new_signed_factor.value;
evaluated_signed_factors.dimension += new_signed_factor.dimension;
}
}
return evaluated_signed_factors;
}
static GimpEevlQuantity
gimp_eevl_signed_factor (GimpEevl *eva)
{
GimpEevlQuantity result;
gboolean negate = FALSE;
if (! gimp_eevl_accept (eva, '+', NULL))
negate = gimp_eevl_accept (eva, '-', NULL);
result = gimp_eevl_factor (eva);
if (negate) result.value = -result.value;
return result;
}
static GimpEevlQuantity
gimp_eevl_factor (GimpEevl *eva)
{
GimpEevlQuantity evaluated_factor = { 0, 0 };
GimpEevlToken consumed_token;
if (gimp_eevl_accept (eva,
GIMP_EEVL_TOKEN_NUM,
&consumed_token))
{
evaluated_factor.value = consumed_token.value.fl;
}
else if (gimp_eevl_accept (eva, '(', NULL))
{
evaluated_factor = gimp_eevl_expression (eva);
gimp_eevl_expect (eva, ')', 0);
}
else
{
gimp_eevl_error (eva, "Expected number or '('");
}
if (eva->current_token.type == GIMP_EEVL_TOKEN_IDENTIFIER)
{
gchar *identifier;
GimpEevlQuantity result;
gimp_eevl_accept (eva,
GIMP_EEVL_TOKEN_ANY,
&consumed_token);
identifier = g_newa (gchar, consumed_token.value.size + 1);
strncpy (identifier, consumed_token.value.c, consumed_token.value.size);
identifier[consumed_token.value.size] = '\0';
if (eva->unit_resolver_proc (identifier,
&result,
eva->data))
{
evaluated_factor.value /= result.value;
evaluated_factor.dimension += result.dimension;
}
else
{
gimp_eevl_error (eva, "Unit was not resolved");
}
}
return evaluated_factor;
}
static gboolean
gimp_eevl_accept (GimpEevl *eva,
GimpEevlTokenType token_type,
GimpEevlToken *consumed_token)
{
gboolean existed = FALSE;
if (token_type == eva->current_token.type ||
token_type == GIMP_EEVL_TOKEN_ANY)
{
existed = TRUE;
if (consumed_token)
*consumed_token = eva->current_token;
/* Parse next token */
gimp_eevl_lex (eva);
}
return existed;
}
static void
gimp_eevl_lex (GimpEevl *eva)
{
const gchar *s;
gimp_eevl_move_past_whitespace (eva);
s = eva->string;
eva->start_of_current_token = s;
if (! s || s[0] == '\0')
{
/* We're all done */
eva->current_token.type = GIMP_EEVL_TOKEN_END;
}
else if (s[0] == '+' || s[0] == '-')
{
/* Snatch these before the g_strtod() does, othewise they might
* be used in a numeric conversion.
*/
gimp_eevl_lex_accept_count (eva, 1, s[0]);
}
else
{
/* Attempt to parse a numeric value */
gchar *endptr = NULL;
gdouble value = g_strtod (s, &endptr);
if (endptr && endptr != s)
{
/* A numeric could be parsed, use it */
eva->current_token.value.fl = value;
gimp_eevl_lex_accept_to (eva, endptr, GIMP_EEVL_TOKEN_NUM);
}
else if (gimp_eevl_unit_identifier_start (s[0]))
{
/* Unit identifier */
eva->current_token.value.c = s;
eva->current_token.value.size = gimp_eevl_unit_identifier_size (s, 0);
gimp_eevl_lex_accept_count (eva,
eva->current_token.value.size,
GIMP_EEVL_TOKEN_IDENTIFIER);
}
else
{
/* Everything else is a single character token */
gimp_eevl_lex_accept_count (eva, 1, s[0]);
}
}
}
static void
gimp_eevl_lex_accept_count (GimpEevl *eva,
gint count,
GimpEevlTokenType token_type)
{
eva->current_token.type = token_type;
eva->string += count;
}
static void
gimp_eevl_lex_accept_to (GimpEevl *eva,
gchar *to,
GimpEevlTokenType token_type)
{
eva->current_token.type = token_type;
eva->string = to;
}
static void
gimp_eevl_move_past_whitespace (GimpEevl *eva)
{
if (! eva->string)
return;
while (g_ascii_isspace (*eva->string))
eva->string++;
}
static gboolean
gimp_eevl_unit_identifier_start (gunichar c)
{
return (g_unichar_isalpha (c) ||
c == (gunichar) '%' ||
c == (gunichar) '\'');
}
static gboolean
gimp_eevl_unit_identifier_continue (gunichar c)
{
return (gimp_eevl_unit_identifier_start (c) ||
g_unichar_isdigit (c));
}
/**
* gimp_eevl_unit_identifier_size:
* @s:
* @start:
*
* Returns: Size of identifier in bytes (not including NULL
* terminator).
**/
static gint
gimp_eevl_unit_identifier_size (const gchar *string,
gint start_offset)
{
const gchar *start = g_utf8_offset_to_pointer (string, start_offset);
const gchar *s = start;
gunichar c = g_utf8_get_char (s);
gint length = 0;
if (gimp_eevl_unit_identifier_start (c))
{
s = g_utf8_next_char (s);
c = g_utf8_get_char (s);
length++;
while (gimp_eevl_unit_identifier_continue (c))
{
s = g_utf8_next_char (s);
c = g_utf8_get_char (s);
length++;
}
}
return g_utf8_offset_to_pointer (start, length) - start;
}
static void
gimp_eevl_expect (GimpEevl *eva,
GimpEevlTokenType token_type,
GimpEevlToken *value)
{
if (! gimp_eevl_accept (eva, token_type, value))
gimp_eevl_error (eva, "Unexpected token");
}
static void
gimp_eevl_error (GimpEevl *eva,
gchar *msg)
{
eva->error_message = msg;
longjmp (eva->catcher, 1);
}

67
libgimpwidgets/gimpeevl.h Normal file
View File

@ -0,0 +1,67 @@
/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* gimpeevl.h
* Copyright (C) 2008-2009 Fredrik Alstromer <roe@excu.se>
* Copyright (C) 2008-2009 Martin Nordholts <martinn@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
* 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
* <http://www.gnu.org/licenses/>.
*/
#ifndef __GIMP_EEVL_H__
#define __GIMP_EEVL_H__
G_BEGIN_DECLS
/**
* GimpEevlQuantity:
* @value: In reference units.
* @dimension: in has a dimension of 1, in^2 has a dimension of 2 etc
*/
typedef struct
{
gdouble value;
gint dimension;
} GimpEevlQuantity;
/**
* GimpEevlUnitResolverProc:
* @identifier: Identifier of unit to resolve or %NULL if default unit
* should be resolved.
* @result: Units per reference unit. For example, in GIMP the
* reference unit is inches so resolving "mm" should
* return 25.4 since there are 25.4 millimeters per inch.
* @data: Data given to gimp_eevl_evaluate().
*
* Returns: If the unit was successfully resolved or not.
*
*/
typedef gboolean (* GimpEevlUnitResolverProc) (const gchar *identifier,
GimpEevlQuantity *result,
gpointer data);
gboolean gimp_eevl_evaluate (const gchar *string,
GimpEevlUnitResolverProc unit_resolver_proc,
GimpEevlQuantity *result,
gpointer data,
const gchar **error_pos,
GError **error);
G_END_DECLS
#endif /* __GIMP_EEVL_H__ */

View File

@ -22,12 +22,15 @@
#include "config.h"
#include <string.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "gimpwidgets.h"
#include "gimpeevl.h"
#include "gimpsizeentry.h"
@ -70,20 +73,27 @@ struct _GimpSizeEntryField
};
static void gimp_size_entry_finalize (GObject *object);
static void gimp_size_entry_update_value (GimpSizeEntryField *gsef,
gdouble value);
static void gimp_size_entry_value_callback (GtkWidget *widget,
gpointer data);
static void gimp_size_entry_update_refval (GimpSizeEntryField *gsef,
gdouble refval);
static void gimp_size_entry_refval_callback (GtkWidget *widget,
gpointer data);
static void gimp_size_entry_update_unit (GimpSizeEntry *gse,
GimpUnit unit);
static void gimp_size_entry_unit_callback (GtkWidget *widget,
GimpSizeEntry *sizeentry);
static void gimp_size_entry_finalize (GObject *object);
static void gimp_size_entry_update_value (GimpSizeEntryField *gsef,
gdouble value);
static void gimp_size_entry_value_callback (GtkWidget *widget,
gpointer data);
static void gimp_size_entry_update_refval (GimpSizeEntryField *gsef,
gdouble refval);
static void gimp_size_entry_refval_callback (GtkWidget *widget,
gpointer data);
static void gimp_size_entry_update_unit (GimpSizeEntry *gse,
GimpUnit unit);
static void gimp_size_entry_unit_callback (GtkWidget *widget,
GimpSizeEntry *sizeentry);
static void gimp_size_entry_attach_eevl (GtkSpinButton *spin_button,
GimpSizeEntryField *gsef);
static gint gimp_size_entry_eevl_input_callback (GtkSpinButton *spinner,
gdouble *return_val,
gpointer *data);
static gboolean gimp_size_entry_eevl_unit_resolver (const gchar *ident,
GimpEevlQuantity *result,
gpointer data);
G_DEFINE_TYPE (GimpSizeEntry, gimp_size_entry, GTK_TYPE_TABLE)
@ -287,6 +297,9 @@ gimp_size_entry_new (gint number_of_fields,
1.0, 10.0, 0.0,
1.0, digits);
gimp_size_entry_attach_eevl (GTK_SPIN_BUTTON (gsef->value_spinbutton),
gsef);
if (spinbutton_width > 0)
{
if (spinbutton_width < 17)
@ -403,6 +416,9 @@ gimp_size_entry_add_field (GimpSizeEntry *gse,
G_CALLBACK (gimp_size_entry_value_callback),
gsef);
gimp_size_entry_attach_eevl (GTK_SPIN_BUTTON (gsef->value_spinbutton),
gsef);
if (gse->show_refval)
{
gsef->refval_adjustment =
@ -1143,6 +1159,174 @@ gimp_size_entry_unit_callback (GtkWidget *widget,
gimp_size_entry_update_unit (gse, new_unit);
}
/**
* gimp_size_entry_attach_eevl:
* @spin_button:
* @gsef:
*
* Hooks in the GimpEevl unit expression parser into the
* #GtkSpinButton of the #GimpSizeEntryField.
**/
static void
gimp_size_entry_attach_eevl (GtkSpinButton *spin_button,
GimpSizeEntryField *gsef)
{
gtk_spin_button_set_numeric (spin_button,
FALSE);
gtk_spin_button_set_update_policy (spin_button,
GTK_UPDATE_IF_VALID);
g_signal_connect (spin_button, "input",
G_CALLBACK (gimp_size_entry_eevl_input_callback),
gsef);
}
static gint
gimp_size_entry_eevl_input_callback (GtkSpinButton *spinner,
gdouble *return_val,
gpointer *data)
{
GimpSizeEntryField *gsef = (GimpSizeEntryField *) data;
gboolean success = FALSE;
const gchar *error_pos = 0;
GError *error = NULL;
GimpEevlQuantity result;
g_return_val_if_fail (GTK_IS_SPIN_BUTTON (spinner), FALSE);
g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gsef->gse), FALSE);
success = gimp_eevl_evaluate (gtk_entry_get_text (GTK_ENTRY (spinner)),
gimp_size_entry_eevl_unit_resolver,
&result,
data,
&error_pos,
&error);
if (! success)
{
if (error && error_pos)
{
g_printerr ("ERROR: %s at '%s'\n",
error->message,
*error_pos ? error_pos : "<End of input>");
}
else
{
g_printerr ("ERROR: Expression evaluation failed without error.\n");
}
gtk_widget_error_bell (GTK_WIDGET (spinner));
return GTK_INPUT_ERROR;
}
else if (result.dimension != 1 && gsef->gse->unit != GIMP_UNIT_PERCENT)
{
g_printerr ("ERROR: result has wrong dimension (expected 1, got %d)\n", result.dimension);
gtk_widget_error_bell (GTK_WIDGET (spinner));
return GTK_INPUT_ERROR;
}
else if (result.dimension != 0 && gsef->gse->unit == GIMP_UNIT_PERCENT)
{
g_printerr ("ERROR: result has wrong dimension (expected 0, got %d)\n", result.dimension);
gtk_widget_error_bell (GTK_WIDGET (spinner));
return GTK_INPUT_ERROR;
}
else
{
/* transform back to UI-unit */
GimpEevlQuantity ui_unit;
switch (gsef->gse->unit)
{
case GIMP_UNIT_PIXEL:
ui_unit.value = gsef->resolution;
ui_unit.dimension = 1;
break;
case GIMP_UNIT_PERCENT:
ui_unit.value = 1.0;
ui_unit.dimension = 0;
break;
default:
ui_unit.value = gimp_unit_get_factor(gsef->gse->unit);
ui_unit.dimension = 1;
break;
}
*return_val = result.value * ui_unit.value;
return TRUE;
}
}
static gboolean
gimp_size_entry_eevl_unit_resolver (const gchar *identifier,
GimpEevlQuantity *result,
gpointer data)
{
GimpSizeEntryField *gsef = (GimpSizeEntryField *) data;
gboolean resolve_default_unit = (identifier == NULL);
GimpUnit unit;
g_return_val_if_fail (gsef, FALSE);
g_return_val_if_fail (result != NULL, FALSE);
g_return_val_if_fail (GIMP_IS_SIZE_ENTRY (gsef->gse), FALSE);
for (unit = 0;
unit <= gimp_unit_get_number_of_units ();
unit++)
{
/* Hack to handle percent within the loop */
if (unit == gimp_unit_get_number_of_units ())
unit = GIMP_UNIT_PERCENT;
if ((resolve_default_unit && unit == gsef->gse->unit) ||
(identifier &&
(strcmp (gimp_unit_get_symbol (unit), identifier) == 0 ||
strcmp (gimp_unit_get_abbreviation (unit), identifier) == 0)))
{
switch (unit)
{
case GIMP_UNIT_PERCENT:
if (gsef->gse->unit == GIMP_UNIT_PERCENT)
{
result->value = 1;
result->dimension = 0;
}
else
{
/* gsef->upper contains the '100%'-value */
result->value = 100*gsef->resolution/gsef->upper;
result->dimension = 1;
}
/* return here, don't perform percentage conversion */
return TRUE;
case GIMP_UNIT_PIXEL:
result->value = gsef->resolution;
break;
default:
result->value = gimp_unit_get_factor (unit);
break;
}
if (gsef->gse->unit == GIMP_UNIT_PERCENT)
{
/* map non-percentages onto percent */
result->value = gsef->upper/(100*gsef->resolution);
result->dimension = 0;
}
else
{
result->dimension = 1;
}
/* We are done */
return TRUE;
}
}
return FALSE;
}
/**
* gimp_size_entry_show_unit_menu:
* @gse: a #GimpSizeEntry

191
libgimpwidgets/test-eevl.c Normal file
View File

@ -0,0 +1,191 @@
/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* test-eevl.c
* Copyright (C) 2008 Fredrik Alstromer <roe@excu.se>
* Copyright (C) 2008 Martin Nordholts <martinn@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 2 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/* A small regression test case for the evaluator */
#include "config.h"
#include <string.h>
#include <glib-object.h>
#include "gimpeevl.h"
typedef struct
{
const gchar *string;
GimpEevlQuantity result;
gboolean should_succeed;
} TestCase;
static TestCase cases[] =
{
/* "Default" test case */
{ "2in + 3in", { 2 + 3, 1}, TRUE },
/* Whitespace variations */
{ "2in+3in", { 2 + 3, 1}, TRUE },
{ " 2in + 3in", { 2 + 3, 1}, TRUE },
{ "2in + 3in ", { 2 + 3, 1}, TRUE },
{ "2 in + 3 in", { 2 + 3, 1}, TRUE },
{ " 2 in + 3 in ", { 2 + 3, 1}, TRUE },
/* Make sure the default unit is applied as it should */
{ "2 + 3in", { 2 + 3, 1 }, TRUE },
{ "3", { 3, 1 }, TRUE },
/* Somewhat complicated input */
{ "(2 + 3)in", { 2 + 3, 1}, TRUE },
// { "2 / 3 in", { 2 / 3., 1}, TRUE },
{ "(2 + 2/3)in", { 2 + 2 / 3., 1}, TRUE },
{ "1/2 + 1/2", { 1, 1}, TRUE },
/* Mixing of units */
{ "2mm + 3in", { 2 / 25.4 + 3, 1}, TRUE },
/* 'odd' behavior */
{ "2 ++ 1", { 3, 1}, TRUE },
{ "2 +- 1", { 1, 1}, TRUE },
{ "2 -- 1", { 3, 1}, TRUE },
/* End of test cases */
{ NULL, { 0, 0 }, TRUE }
};
static gboolean
test_units (const gchar *ident,
GimpEevlQuantity *result,
gpointer data)
{
gboolean resolved = FALSE;
gboolean default_unit = (ident == NULL);
if (default_unit ||
(ident && strcmp ("in", ident) == 0))
{
result->dimension = 1;
result->value = 1.;
resolved = TRUE;
}
else if (ident && strcmp ("mm", ident) == 0)
{
result->dimension = 1;
result->value = 25.4;
resolved = TRUE;
}
return resolved;
}
int
main(void)
{
gint i;
gint failed = 0;
gint succeeded = 0;
g_print ("Testing Eevl Eva, the Evaluator\n\n");
for (i = 0; cases[i].string; i++)
{
const gchar *test = cases[i].string;
GimpEevlQuantity should = cases[i].result;
gboolean success = FALSE;
GimpEevlQuantity result = { 0, -1 };
gboolean should_succeed = cases[i].should_succeed;
GError *error = NULL;
const gchar *error_pos = 0;
success = gimp_eevl_evaluate (test,
test_units,
&result,
NULL,
&error_pos,
&error);
g_print ("%s = %lg (%d): ", test, result.value, result.dimension);
if (error || error_pos)
{
if (should_succeed)
{
failed++;
g_print ("evaluation failed ");
if (error)
{
g_print ("with: %s, ", error->message);
}
else
{
g_print ("without reason, ");
}
if (error_pos)
{
if (*error_pos) g_print ("'%s'.", error_pos);
else g_print ("at end of input.");
}
else
{
g_print ("but didn't say where.");
}
g_print ("\n");
}
else
{
g_print ("OK (failure test case)\n");
succeeded++;
}
}
else if (!should_succeed)
{
g_print ("evaluation should've failed, but didn't.\n");
failed++;
}
else if (should.value != result.value || should.dimension != result.dimension)
{
g_print ("results don't match, should be: %lg (%d)\n",
should.value, should.dimension);
failed++;
}
else
{
g_print ("OK\n");
succeeded++;
}
}
g_print ("\n");
if (!failed)
g_print ("All OK. ");
else
g_print ("Test failed! ");
g_print ("(%d/%d) %lg%%\n\n", succeeded, succeeded+failed,
100*succeeded/(gdouble)(succeeded+failed));
return failed;
}