1 /*
2  * validation.c: Implementation of validation.
3  *
4  * Copyright (C) Jody Goldberg <jody@gnome.org>
5  *
6  * based on work by
7  *	 Almer S. Tigelaar <almer@gnome.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, see <https://www.gnu.org/licenses/>.
21  */
22 
23 #include <gnumeric-config.h>
24 #include <gnumeric.h>
25 #include <validation.h>
26 #include <validation-combo.h>
27 
28 #include <numbers.h>
29 #include <expr.h>
30 #include <mstyle.h>
31 #include <sheet.h>
32 #include <cell.h>
33 #include <value.h>
34 #include <workbook.h>
35 #include <workbook-control.h>
36 #include <parse-util.h>
37 
38 #include <sheet-view.h>
39 #include <sheet-object.h>
40 #include <sheet-style.h>
41 #include <widgets/gnm-validation-combo-view.h>
42 #include <widgets/gnm-cell-combo-view.h>
43 #include <gsf/gsf-impl-utils.h>
44 
45 #include <glib/gi18n-lib.h>
46 
47 static const struct {
48 	gboolean errors_not_allowed;
49 	gboolean strings_not_allowed;
50 	gboolean bool_always_ok;
51 } typeinfo[] = {
52 	{ FALSE, FALSE, TRUE },		/* ANY */
53 	{ TRUE,  TRUE,  TRUE },		/* AS_INT */
54 	{ TRUE,  TRUE,  TRUE },		/* AS_NUMBER */
55 	{ TRUE,  FALSE, FALSE },	/* IN_LIST */
56 	{ TRUE,  TRUE,  TRUE },		/* AS_DATE */
57 	{ TRUE,  TRUE,  TRUE },		/* AS_TIME */
58 	{ TRUE,  FALSE, FALSE },	/* TEXT_LENGTH */
59 	{ FALSE, FALSE, FALSE }		/* CUSTOM */
60 };
61 
62 #define NONE (GnmExprOp)-1
63 
64 static struct {
65 	int nops;
66 	GnmExprOp ops[2];
67 	int ntrue;
68 	char const *name;
69 } const opinfo[] = {
70 	/* Note: no entry for GNM_VALIDATION_OP_NONE */
71 	{ 2, { GNM_EXPR_OP_GTE,       GNM_EXPR_OP_LTE }, 2, N_("Between") },
72 	{ 2, { GNM_EXPR_OP_LT,        GNM_EXPR_OP_GT  }, 1, N_("Not_Between") },
73 	{ 1, { GNM_EXPR_OP_EQUAL,     NONE            }, 1, N_("Equal") },
74 	{ 1, { GNM_EXPR_OP_NOT_EQUAL, NONE            }, 1, N_("Not Equal") },
75 	{ 1, { GNM_EXPR_OP_GT,        NONE            }, 1, N_("Greater Than") },
76 	{ 1, { GNM_EXPR_OP_LT,        NONE            }, 1, N_("Less Than") },
77 	{ 1, { GNM_EXPR_OP_GTE,       NONE            }, 1, N_("Greater than or Equal") },
78 	{ 1, { GNM_EXPR_OP_LTE,       NONE            }, 1, N_("Less than or Equal") },
79 };
80 
81 #undef NONE
82 
83 /***************************************************************************/
84 
85 static GObjectClass *gvc_parent_klass;
86 
87 static void
gnm_validation_combo_finalize(GObject * object)88 gnm_validation_combo_finalize (GObject *object)
89 {
90 	GnmValidationCombo *vcombo = GNM_VALIDATION_COMBO (object);
91 
92 	if (NULL != vcombo->validation) {
93 		gnm_validation_unref (vcombo->validation);
94 		vcombo->validation = NULL;
95 	}
96 
97 	gvc_parent_klass->finalize (object);
98 }
99 
100 static void
gnm_validation_combo_init(G_GNUC_UNUSED SheetObject * so)101 gnm_validation_combo_init (G_GNUC_UNUSED SheetObject *so)
102 {
103 }
104 
105 static SheetObjectView *
gnm_validation_combo_view_new(SheetObject * so,SheetObjectViewContainer * container)106 gnm_validation_combo_view_new (SheetObject *so, SheetObjectViewContainer *container)
107 {
108 	return gnm_cell_combo_view_new (so,
109 		gnm_validation_combo_view_get_type (), container);
110 }
111 
112 static void
gnm_validation_combo_class_init(GObjectClass * gobject_class)113 gnm_validation_combo_class_init (GObjectClass *gobject_class)
114 {
115 	SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
116 	gobject_class->finalize	= gnm_validation_combo_finalize;
117 	so_class->new_view = gnm_validation_combo_view_new;
118 
119 	gvc_parent_klass = g_type_class_peek_parent (gobject_class);
120 }
121 
122 typedef SheetObjectClass GnmValidationComboClass;
GSF_CLASS(GnmValidationCombo,gnm_validation_combo,gnm_validation_combo_class_init,gnm_validation_combo_init,gnm_cell_combo_get_type ())123 GSF_CLASS (GnmValidationCombo, gnm_validation_combo,
124 	   gnm_validation_combo_class_init, gnm_validation_combo_init,
125 	   gnm_cell_combo_get_type ())
126 
127 SheetObject *
128 gnm_validation_combo_new (GnmValidation const *val, SheetView *sv)
129 {
130 	GnmValidationCombo *vcombo;
131 
132 	g_return_val_if_fail (val != NULL, NULL);
133 	g_return_val_if_fail (sv  != NULL, NULL);
134 
135 	vcombo = g_object_new (GNM_VALIDATION_COMBO_TYPE, "sheet-view", sv, NULL);
136 	gnm_validation_ref (vcombo->validation = val);
137 	return GNM_SO (vcombo);
138 }
139 
140 /***************************************************************************/
141 
142 GType
gnm_validation_style_get_type(void)143 gnm_validation_style_get_type (void)
144 {
145 	static GType etype = 0;
146 	if (etype == 0) {
147 		static GEnumValue const values[] = {
148 			{ GNM_VALIDATION_STYLE_NONE,
149 			  "GNM_VALIDATION_STYLE_NONE", "none"},
150 			{ GNM_VALIDATION_STYLE_STOP,
151 			  "GNM_VALIDATION_STYLE_STOP", "stop"},
152 			{ GNM_VALIDATION_STYLE_WARNING,
153 			  "GNM_VALIDATION_STYLE_WARNING", "warning"},
154 			{ GNM_VALIDATION_STYLE_INFO,
155 			  "GNM_VALIDATION_STYLE_INFO", "info"},
156 			{ GNM_VALIDATION_STYLE_PARSE_ERROR,
157 			  "GNM_VALIDATION_STYLE_PARSE_ERROR", "parse-error"},
158 			{ 0, NULL, NULL }
159 		};
160 		etype = g_enum_register_static ("GnmValidationStyle",
161 						values);
162 	}
163 	return etype;
164 }
165 
166 GType
gnm_validation_type_get_type(void)167 gnm_validation_type_get_type (void)
168 {
169 	static GType etype = 0;
170 	if (etype == 0) {
171 		static GEnumValue const values[] = {
172 			{ GNM_VALIDATION_TYPE_ANY,
173 			  "GNM_VALIDATION_TYPE_ANY", "any"},
174 			{ GNM_VALIDATION_TYPE_AS_INT,
175 			  "GNM_VALIDATION_TYPE_AS_INT", "int"},
176 			{ GNM_VALIDATION_TYPE_AS_NUMBER,
177 			  "GNM_VALIDATION_TYPE_AS_NUMBER", "number"},
178 			{ GNM_VALIDATION_TYPE_IN_LIST,
179 			  "GNM_VALIDATION_TYPE_IN_LIST", "list"},
180 			{ GNM_VALIDATION_TYPE_AS_DATE,
181 			  "GNM_VALIDATION_TYPE_AS_DATE", "date"},
182 			{ GNM_VALIDATION_TYPE_AS_TIME,
183 			  "GNM_VALIDATION_TYPE_AS_TIME", "time"},
184 			{ GNM_VALIDATION_TYPE_TEXT_LENGTH,
185 			  "GNM_VALIDATION_TYPE_TEXT_LENGTH", "length"},
186 			{ GNM_VALIDATION_TYPE_CUSTOM,
187 			  "GNM_VALIDATION_TYPE_CUSTOM", "custom"},
188 			{ 0, NULL, NULL }
189 		};
190 		etype = g_enum_register_static ("GnmValidationType",
191 						values);
192 	}
193 	return etype;
194 }
195 
196 GType
gnm_validation_op_get_type(void)197 gnm_validation_op_get_type (void)
198 {
199 	static GType etype = 0;
200 	if (etype == 0) {
201 		static GEnumValue const values[] = {
202 			{ GNM_VALIDATION_OP_NONE,
203 			  "GNM_VALIDATION_OP_NONE", "none"},
204 			{ GNM_VALIDATION_OP_BETWEEN,
205 			  "GNM_VALIDATION_OP_BETWEEN", "between"},
206 			{ GNM_VALIDATION_OP_NOT_BETWEEN,
207 			  "GNM_VALIDATION_OP_NOT_BETWEEN", "not-between"},
208 			{ GNM_VALIDATION_OP_EQUAL,
209 			  "GNM_VALIDATION_OP_EQUAL", "equal"},
210 			{ GNM_VALIDATION_OP_NOT_EQUAL,
211 			  "GNM_VALIDATION_OP_NOT_EQUAL", "not-equal"},
212 			{ GNM_VALIDATION_OP_GT,
213 			  "GNM_VALIDATION_OP_GT", "gt"},
214 			{ GNM_VALIDATION_OP_LT,
215 			  "GNM_VALIDATION_OP_LT", "lt"},
216 			{ GNM_VALIDATION_OP_GTE,
217 			  "GNM_VALIDATION_OP_GTE", "gte"},
218 			{ GNM_VALIDATION_OP_LTE,
219 			  "GNM_VALIDATION_OP_LTE", "lte"},
220 			{ 0, NULL, NULL }
221 		};
222 		etype = g_enum_register_static ("GnmValidationOp",
223 						values);
224 	}
225 	return etype;
226 }
227 
228 
229 /***************************************************************************/
230 
231 /**
232  * gnm_validation_new:
233  * @style: #ValidationStyle
234  * @op: #ValidationOp
235  * @sheet: #Sheet
236  * @title: will be copied.
237  * @msg: will be copied.
238  * @texpr0: (transfer full) (nullable): first expression
239  * @texpr1: (transfer full) (nullable): second expression
240  * @allow_blank:
241  * @use_dropdown:
242  *
243  * Does _NOT_ require all necessary information to be set here.
244  * gnm_validation_set_expr can be used to change the expressions after creation,
245  * and gnm_validation_is_ok can be used to ensure that things are properly
246  * setup.
247  *
248  * Returns: (transfer full): a new @GnmValidation object
249  **/
250 GnmValidation *
gnm_validation_new(ValidationStyle style,ValidationType type,ValidationOp op,Sheet * sheet,char const * title,char const * msg,GnmExprTop const * texpr0,GnmExprTop const * texpr1,gboolean allow_blank,gboolean use_dropdown)251 gnm_validation_new (ValidationStyle style,
252 		    ValidationType type,
253 		    ValidationOp op,
254 		    Sheet *sheet,
255 		    char const *title, char const *msg,
256 		    GnmExprTop const *texpr0, GnmExprTop const *texpr1,
257 		    gboolean allow_blank, gboolean use_dropdown)
258 {
259 	GnmValidation *v;
260 	int nops;
261 
262 	g_return_val_if_fail ((size_t)type < G_N_ELEMENTS (typeinfo), NULL);
263 	g_return_val_if_fail (op >= GNM_VALIDATION_OP_NONE, NULL);
264 	g_return_val_if_fail (op < (int)G_N_ELEMENTS (opinfo), NULL);
265 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
266 
267 	switch (type) {
268 	case GNM_VALIDATION_TYPE_CUSTOM:
269 	case GNM_VALIDATION_TYPE_IN_LIST:
270 		nops = 1;
271 		if (op != GNM_VALIDATION_OP_NONE) {
272 			/*
273 			 * This can happen if an .xls file was saved
274 			 * as a .gnumeric.
275 			 */
276 			op = GNM_VALIDATION_OP_NONE;
277 		}
278 		break;
279 	case GNM_VALIDATION_TYPE_ANY:
280 		nops = 0;
281 		break;
282 	default:
283 		nops = (op == GNM_VALIDATION_OP_NONE) ? 0 : opinfo[op].nops;
284 	}
285 
286 	v = g_new0 (GnmValidation, 1);
287 	v->ref_count = 1;
288 	v->title = title && title[0] ? go_string_new (title) : NULL;
289 	v->msg = msg && msg[0] ? go_string_new (msg) : NULL;
290 
291 	dependent_managed_init (&v->deps[0], sheet);
292 	if (texpr0) {
293 		if (nops > 0)
294 			dependent_managed_set_expr (&v->deps[0], texpr0);
295 		gnm_expr_top_unref (texpr0);
296 	}
297 
298 	dependent_managed_init (&v->deps[1], sheet);
299 	if (texpr1) {
300 		if (nops > 1)
301 			dependent_managed_set_expr (&v->deps[1], texpr1);
302 		gnm_expr_top_unref (texpr1);
303 	}
304 
305 	v->style = style;
306 	v->type = type;
307 	v->op = op;
308 	v->allow_blank = (allow_blank != FALSE);
309 	v->use_dropdown = (use_dropdown != FALSE);
310 
311 	return v;
312 }
313 
314 GnmValidation *
gnm_validation_dup_to(GnmValidation * v,Sheet * sheet)315 gnm_validation_dup_to (GnmValidation *v, Sheet *sheet)
316 {
317 	GnmValidation *dst;
318 	int i;
319 
320 	g_return_val_if_fail (v != NULL, NULL);
321 
322 	dst = gnm_validation_new (v->style, v->type, v->op,
323 				  sheet,
324 				  v->title ? v->title->str : NULL,
325 				  v->msg ? v->msg->str : NULL,
326 				  NULL, NULL,
327 				  v->allow_blank, v->use_dropdown);
328 	for (i = 0; i < 2; i++)
329 		gnm_validation_set_expr (dst, v->deps[i].base.texpr, i);
330 	return dst;
331 }
332 
333 gboolean
gnm_validation_equal(GnmValidation const * a,GnmValidation const * b,gboolean relax_sheet)334 gnm_validation_equal (GnmValidation const *a, GnmValidation const *b,
335 		      gboolean relax_sheet)
336 {
337 	int i;
338 
339 	g_return_val_if_fail (a != NULL, FALSE);
340 	g_return_val_if_fail (b != NULL, FALSE);
341 
342 	if (a == b)
343 		return TRUE;
344 
345 	if (!relax_sheet &&
346 	    gnm_validation_get_sheet (a) != gnm_validation_get_sheet (b))
347 		return FALSE;
348 
349 	if (!(g_strcmp0 (a->title ? a->title->str : NULL,
350 			 b->title ? b->title->str : NULL) == 0 &&
351 	      g_strcmp0 (a->msg ? a->msg->str : NULL,
352 			 b->msg ? b->msg->str : NULL) == 0 &&
353 	      a->style == b->style &&
354 	      a->type == b->type &&
355 	      a->op == b->op &&
356 	      a->allow_blank == b->allow_blank &&
357 	      a->use_dropdown == b->use_dropdown))
358 		return FALSE;
359 
360 	for (i = 0; i < 2; i++)
361 		if (!gnm_expr_top_equal (a->deps[i].base.texpr, b->deps[i].base.texpr))
362 			return FALSE;
363 
364 	return TRUE;
365 }
366 
367 
368 GnmValidation *
gnm_validation_ref(GnmValidation const * v)369 gnm_validation_ref (GnmValidation const *v)
370 {
371 	g_return_val_if_fail (v != NULL, NULL);
372 	((GnmValidation *)v)->ref_count++;
373 	return ((GnmValidation *)v);
374 }
375 
376 void
gnm_validation_unref(GnmValidation const * val)377 gnm_validation_unref (GnmValidation const *val)
378 {
379 	GnmValidation *v = (GnmValidation *)val;
380 
381 	g_return_if_fail (v != NULL);
382 
383 	v->ref_count--;
384 
385 	if (v->ref_count < 1) {
386 		int i;
387 
388 		go_string_unref (v->title);
389 		v->title = NULL;
390 
391 		go_string_unref (v->msg);
392 		v->msg = NULL;
393 
394 		for (i = 0 ; i < 2 ; i++)
395 			dependent_managed_set_expr (&v->deps[i], NULL);
396 		g_free (v);
397 	}
398 }
399 
400 GType
gnm_validation_get_type(void)401 gnm_validation_get_type (void)
402 {
403 	static GType t = 0;
404 
405 	if (t == 0) {
406 		t = g_boxed_type_register_static ("GnmValidation",
407 			 (GBoxedCopyFunc)gnm_validation_ref,
408 			 (GBoxedFreeFunc)gnm_validation_unref);
409 	}
410 	return t;
411 }
412 
413 /**
414  * gnm_validation_get_sheet:
415  * @v: #GnmValidation
416  *
417  * Returns: (transfer none): the sheet.
418  **/
419 Sheet *
gnm_validation_get_sheet(GnmValidation const * v)420 gnm_validation_get_sheet (GnmValidation const *v)
421 {
422 	g_return_val_if_fail (v != NULL, NULL);
423 	return v->deps[0].base.sheet;
424 }
425 
426 /**
427  * gnm_validation_set_expr:
428  * @v: #GnmValidation
429  * @texpr: #GnmExprTop
430  * @indx: 0 or 1
431  *
432  * Assign an expression to a validation.  gnm_validation_is_ok can be used to
433  * verify that @v has all of the required information.
434  **/
435 void
gnm_validation_set_expr(GnmValidation * v,GnmExprTop const * texpr,unsigned indx)436 gnm_validation_set_expr (GnmValidation *v,
437 			 GnmExprTop const *texpr, unsigned indx)
438 {
439 	g_return_if_fail (indx <= 1);
440 
441 	dependent_managed_set_expr (&v->deps[indx], texpr);
442 }
443 
444 GError *
gnm_validation_is_ok(GnmValidation const * v)445 gnm_validation_is_ok (GnmValidation const *v)
446 {
447 	unsigned nops, i;
448 
449 	switch (v->type) {
450 	case GNM_VALIDATION_TYPE_CUSTOM:
451 	case GNM_VALIDATION_TYPE_IN_LIST:
452 		nops = 1;
453 		break;
454 	case GNM_VALIDATION_TYPE_ANY:
455 		nops = 0;
456 		break;
457 	default: nops = (v->op == GNM_VALIDATION_OP_NONE) ? 0 : opinfo[v->op].nops;
458 	}
459 
460 	for (i = 0 ; i < 2 ; i++)
461 		if (v->deps[i].base.texpr == NULL) {
462 			if (i < nops)
463 				return g_error_new (1, 0, N_("Missing formula for validation"));
464 		} else {
465 			if (i >= nops)
466 				return g_error_new (1, 0, N_("Extra formula for validation"));
467 		}
468 
469 	return NULL;
470 }
471 
472 static ValidationStatus
validation_barf(WorkbookControl * wbc,GnmValidation const * gv,char * def_msg,gboolean * showed_dialog)473 validation_barf (WorkbookControl *wbc, GnmValidation const *gv,
474 		 char *def_msg, gboolean *showed_dialog)
475 {
476 	char const *msg = gv->msg ? gv->msg->str : def_msg;
477 	char const *title = gv->title ? gv->title->str : _("Gnumeric: Validation");
478 	ValidationStatus result;
479 
480 	if (gv->style == GNM_VALIDATION_STYLE_NONE) {
481 		/* Invalid, but we're asked to ignore.  */
482 		result = GNM_VALIDATION_STATUS_VALID;
483 	} else {
484 		if (showed_dialog) *showed_dialog = TRUE;
485 		result = wb_control_validation_msg (wbc, gv->style, title, msg);
486 	}
487 	g_free (def_msg);
488 	return result;
489 }
490 
491 static GnmValue *
cb_validate_custom(GnmValueIter const * v_iter,GnmValue const * target)492 cb_validate_custom (GnmValueIter const *v_iter, GnmValue const *target)
493 {
494 	if (value_compare (v_iter->v, target, FALSE) == IS_EQUAL)
495 		return VALUE_TERMINATE;
496 	else
497 		return NULL;
498 }
499 
500 #define BARF(msg)					\
501   do {							\
502 	return validation_barf (wbc, v, msg, showed_dialog);		\
503   } while (0)
504 
505 /**
506  * gnm_validation_eval:
507  * @wbc:
508  * @mstyle:
509  * @sheet:
510  * @pos:
511  * @showed_dialog: (out) (optional):
512  *
513  * Checks the validation in @mstyle, if any.  Set @showed_dialog to %TRUE
514  * if a dialog was showed as a result.
515  **/
516 ValidationStatus
gnm_validation_eval(WorkbookControl * wbc,GnmStyle const * mstyle,Sheet * sheet,GnmCellPos const * pos,gboolean * showed_dialog)517 gnm_validation_eval (WorkbookControl *wbc, GnmStyle const *mstyle,
518 		     Sheet *sheet, GnmCellPos const *pos,
519 		     gboolean *showed_dialog)
520 {
521 	GnmValidation const *v;
522 	GnmCell *cell;
523 	GnmValue *val;
524 	gnm_float x;
525 	int nok, i;
526 	GnmEvalPos ep;
527 
528 	if (showed_dialog) *showed_dialog = FALSE;
529 
530 	v = gnm_style_get_validation (mstyle);
531 	if (v == NULL)
532 		return GNM_VALIDATION_STATUS_VALID;
533 
534 	if (v->type == GNM_VALIDATION_TYPE_ANY)
535 		return GNM_VALIDATION_STATUS_VALID;
536 
537 	cell = sheet_cell_get (sheet, pos->col, pos->row);
538 	if (cell != NULL)
539 		gnm_cell_eval (cell);
540 
541 	if (gnm_cell_is_empty (cell)) {
542 		if (v->allow_blank)
543 			return GNM_VALIDATION_STATUS_VALID;
544 		BARF (g_strdup_printf (_("Cell %s is not permitted to be blank"),
545 				       cell_name (cell)));
546 	}
547 
548 	val = cell->value;
549 	switch (val->v_any.type) {
550 	case VALUE_ERROR:
551 		if (typeinfo[v->type].errors_not_allowed)
552 			BARF (g_strdup_printf (_("Cell %s is not permitted to contain error values"),
553 					       cell_name (cell)));
554 		break;
555 
556 	case VALUE_BOOLEAN:
557 		if (typeinfo[v->type].bool_always_ok)
558 			return GNM_VALIDATION_STATUS_VALID;
559 		break;
560 
561 	case VALUE_STRING:
562 		if (typeinfo[v->type].strings_not_allowed)
563 			BARF (g_strdup_printf (_("Cell %s is not permitted to contain strings"),
564 					       cell_name (cell)));
565 		break;
566 
567 	default:
568 		break;
569 	}
570 
571 	eval_pos_init_cell (&ep, cell);
572 
573 	switch (v->type) {
574 	case GNM_VALIDATION_TYPE_AS_INT:
575 		x = value_get_as_float (val);
576 		if (gnm_fake_floor (x) == gnm_fake_ceil (x))
577 			break;
578 		else
579 			BARF (g_strdup_printf (_("'%s' is not an integer"),
580 					       value_peek_string (val)));
581 
582 	case GNM_VALIDATION_TYPE_AS_NUMBER:
583 		x = value_get_as_float (val);
584 		break;
585 
586 	case GNM_VALIDATION_TYPE_AS_DATE: /* What the hell does this do?  */
587 		x = value_get_as_float (val);
588 		if (x < 0)
589 			BARF (g_strdup_printf (_("'%s' is not a valid date"),
590 					       value_peek_string (val)));
591 		break;
592 
593 
594 	case GNM_VALIDATION_TYPE_AS_TIME: /* What the hell does this do?  */
595 		x = value_get_as_float (val);
596 		break;
597 
598 	case GNM_VALIDATION_TYPE_IN_LIST: {
599 		GnmExprTop const *texpr = v->deps[0].base.texpr;
600 		if (texpr) {
601 			GnmValue *list = gnm_expr_top_eval
602 				(texpr, &ep,
603 				 GNM_EXPR_EVAL_PERMIT_NON_SCALAR | GNM_EXPR_EVAL_PERMIT_EMPTY);
604 			GnmValue *res = value_area_foreach (list, &ep, CELL_ITER_IGNORE_BLANK,
605 				 (GnmValueIterFunc) cb_validate_custom, val);
606 			value_release (list);
607 			if (res == NULL) {
608 				GnmParsePos pp;
609 				char *expr_str = gnm_expr_top_as_string
610 					(texpr,
611 					 parse_pos_init_evalpos (&pp, &ep),
612 					 ep.sheet->convs);
613 				char *msg = g_strdup_printf (_("%s does not contain the new value."), expr_str);
614 				g_free (expr_str);
615 				BARF (msg);
616 			}
617 		}
618 		return GNM_VALIDATION_STATUS_VALID;
619 	}
620 
621 	case GNM_VALIDATION_TYPE_TEXT_LENGTH:
622 		/* XL appears to use a very basic value->string mapping that
623 		 * ignores formatting.
624 		 * eg len (12/13/01) == len (37238) = 5
625 		 * This seems wrong for
626 		 */
627 		x = g_utf8_strlen (value_peek_string (val), -1);
628 		break;
629 
630 	case GNM_VALIDATION_TYPE_CUSTOM: {
631 		gboolean valid;
632 		GnmExprTop const *texpr = v->deps[0].base.texpr;
633 
634 		if (!texpr)
635 			return GNM_VALIDATION_STATUS_VALID;
636 
637 		val = gnm_expr_top_eval (texpr, &ep, GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
638 		valid = value_get_as_bool (val, NULL);
639 		value_release (val);
640 
641 		if (valid)
642 			return GNM_VALIDATION_STATUS_VALID;
643 		else {
644 			GnmParsePos pp;
645 			char *expr_str = gnm_expr_top_as_string
646 				(texpr,
647 				 parse_pos_init_evalpos (&pp, &ep),
648 				 ep.sheet->convs);
649 			char *msg = g_strdup_printf (_("%s is not true."), expr_str);
650 			g_free (expr_str);
651 			BARF (msg);
652 		}
653 	}
654 
655 	default:
656 		g_assert_not_reached ();
657 		return GNM_VALIDATION_STATUS_VALID;
658 	}
659 
660 	if (v->op == GNM_VALIDATION_OP_NONE)
661 		return GNM_VALIDATION_STATUS_VALID;
662 
663 	nok = 0;
664 	for (i = 0; i < opinfo[v->op].nops; i++) {
665 		GnmExprTop const *texpr_i = v->deps[i].base.texpr;
666 		GnmExprTop const *texpr;
667 		GnmValue *cres;
668 
669 		if (!texpr_i) {
670 			nok++;
671 			continue;
672 		}
673 
674 		texpr = gnm_expr_top_new
675 			(gnm_expr_new_binary
676 			 (gnm_expr_new_constant (value_new_float (x)),
677 			  opinfo[v->op].ops[i],
678 			  gnm_expr_copy (texpr_i->expr)));
679 		cres = gnm_expr_top_eval
680 			(texpr, &ep, GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
681 		if (value_get_as_bool (cres, NULL))
682 			nok++;
683 		value_release (cres);
684 		gnm_expr_top_unref (texpr);
685 	}
686 
687 	if (nok < opinfo[v->op].ntrue)
688 		BARF (g_strdup_printf (_("%s is out of permitted range"),
689 				       value_peek_string (val)));
690 
691 	return GNM_VALIDATION_STATUS_VALID;
692 }
693 
694 #undef BARF
695 
696 typedef struct {
697 	WorkbookControl *wbc;
698 	Sheet *sheet;
699 	GnmCellPos const *pos;
700 	gboolean *showed_dialog;
701 	ValidationStatus status;
702 } validation_eval_t;
703 
704 static GnmValue *
validation_eval_range_cb(GnmCellIter const * iter,validation_eval_t * closure)705 validation_eval_range_cb (GnmCellIter const *iter, validation_eval_t *closure)
706 {
707 	ValidationStatus status;
708 	gboolean showed_dialog;
709 	GnmStyle const *mstyle = sheet_style_get
710 		(closure->sheet, iter->pp.eval.col, iter->pp.eval.row);
711 
712 	if (mstyle != NULL) {
713 		status = gnm_validation_eval (closure->wbc, mstyle,
714 					  closure->sheet, &iter->pp.eval,
715 					  &showed_dialog);
716 		if (closure->showed_dialog)
717 			*closure->showed_dialog = *closure->showed_dialog || showed_dialog;
718 
719 		if (status != GNM_VALIDATION_STATUS_VALID) {
720 			closure->status = status;
721 			return VALUE_TERMINATE;
722 		}
723 	}
724 
725 	return NULL;
726 }
727 
728 ValidationStatus
gnm_validation_eval_range(WorkbookControl * wbc,Sheet * sheet,GnmCellPos const * pos,GnmRange const * r,gboolean * showed_dialog)729 gnm_validation_eval_range (WorkbookControl *wbc,
730 			   Sheet *sheet, GnmCellPos const *pos,
731 			   GnmRange const *r,
732 			   gboolean *showed_dialog)
733 {
734 	GnmValue *result;
735 	validation_eval_t closure;
736 	GnmEvalPos ep;
737 	GnmValue *cell_range = value_new_cellrange_r (sheet, r);
738 
739 	closure.wbc = wbc;
740 	closure.sheet = sheet;
741 	closure.pos = pos;
742 	closure.showed_dialog = showed_dialog;
743 	closure.status = GNM_VALIDATION_STATUS_VALID;
744 
745 	eval_pos_init_pos (&ep, sheet, pos);
746 
747 	result = workbook_foreach_cell_in_range (&ep, cell_range, CELL_ITER_ALL,
748 						 (CellIterFunc) validation_eval_range_cb,
749 						 &closure);
750 
751 	value_release (cell_range);
752 
753 	if (result == NULL)
754 		return GNM_VALIDATION_STATUS_VALID;
755 	return closure.status;
756 }
757