mirror of https://github.com/GNOME/gimp.git
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:
parent
6742ebc949
commit
bcee243fa3
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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__ */
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue