Example #1
0
/**
 * paste_cell:
 * @target_col:  Column to put the cell into
 * @target_row:  Row to put the cell into.
 * @src:         A #GnmCelCopy with the content to paste
 * @paste_flags: Bit mask that describes the paste options.
 *
 *  Pastes a cell in the spreadsheet.
 */
static void
paste_cell (int target_col, int target_row,
	    GnmCellCopy const *src,
	    const struct paste_cell_data *dat)
{
	Sheet *dst_sheet = dat->pt->sheet;
	int paste_flags = dat->pt->paste_flags;

	if (paste_flags & PASTE_OPER_MASK)
		paste_cell_with_operation (dst_sheet, target_col, target_row,
					   &dat->rinfo, src, paste_flags);
	else {
		GnmCell *dst = sheet_cell_fetch (dst_sheet, target_col, target_row);
		if (NULL != src->texpr && (paste_flags & PASTE_CONTENTS)) {
			GnmExprTop const *relo = gnm_expr_top_relocate (
				src->texpr, &dat->rinfo, FALSE);
			if (paste_flags & PASTE_TRANSPOSE) {
				GnmExprTop const *trelo =
					gnm_expr_top_transpose (relo ? relo : src->texpr);
				if (trelo) {
					if (relo)
						gnm_expr_top_unref (relo);
					relo = trelo;
				}
			} else if (!relo && gnm_expr_top_is_array_corner (src->texpr)) {
				/* We must not share array expressions.  */
				relo = gnm_expr_top_new (gnm_expr_copy (src->texpr->expr));
			}
			gnm_cell_set_expr_and_value (dst, relo ? relo : src->texpr,
						 value_dup (src->val), TRUE);
			if (NULL != relo)
				gnm_expr_top_unref (relo);
		} else {
			GnmValue *newval = NULL;
			GnmValue const *oldval = src->val;

			if (dat->translate_dates && oldval && VALUE_IS_FLOAT (oldval)) {
				GOFormat const *fmt = VALUE_FMT (oldval)
					? VALUE_FMT (oldval)
					: gnm_style_get_format (gnm_cell_get_style (dst));
				if (go_format_is_date (fmt) > 0) {
					gnm_float fnew = go_date_conv_translate
						(value_get_as_float (oldval),
						 dat->cr->date_conv,
						 workbook_date_conv (dst_sheet->workbook));
					newval = value_new_float (fnew);
					value_set_fmt (newval, VALUE_FMT (oldval));
				}
			}

			if (!newval)
				newval = value_dup (src->val);
			gnm_cell_set_value (dst, newval);
		}
	}
}
Example #2
0
static void
paste_cell_with_operation (Sheet *dst_sheet,
			   int target_col, int target_row,
			   GnmExprRelocateInfo const *rinfo,
			   GnmCellCopy const *src,
			   int paste_flags)
{
	GnmCell *dst;
	GnmExprOp op;

	if (src->texpr == NULL &&
	    !VALUE_IS_EMPTY (src->val) &&
	    !VALUE_IS_NUMBER (src->val))
		return;

	dst = sheet_cell_fetch (dst_sheet, target_col, target_row);
	if (!cell_has_expr_or_number_or_blank (dst))
		return;

	op = paste_op_to_expr_op (paste_flags);
	/* FIXME : This does not handle arrays, linked cells, ranges, etc. */
	if ((paste_flags & PASTE_CONTENTS) &&
	    (NULL != src->texpr || gnm_cell_has_expr (dst))) {
		GnmExpr const *old_expr    = contents_as_expr (dst->base.texpr, dst->value);
		GnmExpr const *copied_expr = contents_as_expr (src->texpr, src->val);
		GnmExprTop const *res = gnm_expr_top_new (gnm_expr_new_binary (old_expr, op, copied_expr));
		GnmExprTop const *relo = gnm_expr_top_relocate (res, rinfo, FALSE);
		if (relo) {
			gnm_cell_set_expr (dst, relo);
			gnm_expr_top_unref (relo);
		} else
			gnm_cell_set_expr (dst, res);
		gnm_expr_top_unref (res);
	} else {
		GnmValue  *value;
		GnmEvalPos pos;
		GnmExpr const *expr = gnm_expr_new_binary (
			gnm_expr_new_constant (value_dup (dst->value)),
			op,
			gnm_expr_new_constant (value_dup (src->val)));
		GnmExprTop const *texpr = gnm_expr_top_new (expr);

		eval_pos_init_cell (&pos, dst);
		pos.dep = NULL; /* no dynamic deps */
		value = gnm_expr_top_eval (texpr, &pos,
					   GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
		gnm_expr_top_unref (texpr);
		gnm_cell_set_value (dst, value);
	}
}
Example #3
0
static void
constraint_select_click (GtkTreeSelection *Selection,
			 SolverState * state)
{
	GtkTreeModel *store;
	GtkTreeIter iter;
	GnmSolverConstraint const *c;
	GnmValue const *lhs, *rhs;

	if (gtk_tree_selection_get_selected (Selection, &store, &iter))
		gtk_tree_model_get (store, &iter, 1, &state->constr, -1);
	else
		state->constr = NULL;
	dialog_set_sec_button_sensitivity (NULL, state);

	if (state->constr == NULL)
		return; /* Fail? */
	c = state->constr;

	lhs = gnm_solver_constraint_get_lhs (c);
	if (lhs) {
		GnmExprTop const *texpr =
			gnm_expr_top_new_constant (value_dup (lhs));
		GnmParsePos pp;

		gnm_expr_entry_load_from_expr
			(state->lhs.entry,
			 texpr,
			 parse_pos_init_sheet (&pp, state->sheet));
		gnm_expr_top_unref (texpr);
	} else
		gnm_expr_entry_load_from_text (state->lhs.entry, "");

	rhs = gnm_solver_constraint_get_rhs (c);
	if (rhs && gnm_solver_constraint_has_rhs (c)) {
		GnmExprTop const *texpr =
			gnm_expr_top_new_constant (value_dup (rhs));
		GnmParsePos pp;

		gnm_expr_entry_load_from_expr
			(state->rhs.entry,
			 texpr,
			 parse_pos_init_sheet (&pp, state->sheet));
		gnm_expr_top_unref (texpr);
	} else
		gnm_expr_entry_load_from_text (state->rhs.entry, "");

	gtk_combo_box_set_active (state->type_combo, c->type);
}
Example #4
0
static gboolean
so_list_init (GnmDialogSOList *state, WBCGtk *wbcg, SheetObject *so)
{
	GnmExprTop const *texpr;
	GtkBuilder *gui;

	gui = gnm_gtk_builder_load ("res:ui/so-list.ui", NULL, GO_CMD_CONTEXT (wbcg));
	if (gui == NULL)
                return TRUE;

	state->wbcg   = wbcg;
	state->so     = so;
	state->dialog = go_gtk_builder_get_widget (gui, "SOList");

	gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
					   state->wbcg,
					   GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);

	texpr = sheet_widget_list_base_get_content_link (so);
	state->content_entry = init_entry (state, gui, 1, 4, texpr);
	if (texpr) gnm_expr_top_unref (texpr);

	texpr = sheet_widget_list_base_get_result_link (so);
	state->link_entry = init_entry (state, gui, 1, 0, texpr);
	if (texpr) gnm_expr_top_unref (texpr);

	state->as_index_radio = go_gtk_builder_get_widget (gui, "as-index-radio");
	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (state->as_index_radio),
				      sheet_widget_list_base_result_type_is_index (so));

	g_signal_connect (G_OBJECT (state->dialog), "response",
		G_CALLBACK (cb_so_list_response), state);
	gnm_init_help_button (
		go_gtk_builder_get_widget (gui, "help"),
		GNUMERIC_HELP_LINK_SO_LIST);

	/* a candidate for merging into attach guru */
	gnm_keyed_dialog (state->wbcg, GTK_WINDOW (state->dialog),
		DIALOG_SO_LIST_KEY);
	g_object_set_data_full (G_OBJECT (state->dialog),
		"state", state, g_free);
	go_gtk_nonmodal_dialog (wbcg_toplevel (state->wbcg),
		GTK_WINDOW (state->dialog));
	wbc_gtk_attach_guru (state->wbcg, state->dialog);
	gtk_widget_show_all (GTK_WIDGET (state->dialog));
	g_object_unref (gui);

	return FALSE;
}
Example #5
0
void
validation_unref (GnmValidation const *val)
{
	GnmValidation *v = (GnmValidation *)val;

	g_return_if_fail (v != NULL);

	v->ref_count--;

	if (v->ref_count < 1) {
		int i;

		if (v->title != NULL) {
			go_string_unref (v->title);
			v->title = NULL;
		}
		if (v->msg != NULL) {
			go_string_unref (v->msg);
			v->msg = NULL;
		}
		for (i = 0 ; i < 2 ; i++)
			if (v->texpr[i] != NULL) {
				gnm_expr_top_unref (v->texpr[i]);
				v->texpr[i] = NULL;
			}
		g_free (v);
	}
}
Example #6
0
/**
 * validation_new :
 * @title : will be copied.
 * @msg   : will be copied.
 * @texpr0 : absorb the reference to the expression (optionally %NULL).
 * @texpr1 : absorb the reference to the expression (optionally %NULL).
 *
 * Does _NOT_ require all necessary information to be set here.
 * validation_set_expr can be used to change the expressions after creation,
 * and validation_is_ok can be used to ensure that things are properly setup.
 *
 * Returns a new @GnmValidation object that needs to be unrefed.
 **/
GnmValidation *
validation_new (ValidationStyle style,
		ValidationType  type,
		ValidationOp    op,
		char const *title, char const *msg,
		GnmExprTop const *texpr0, GnmExprTop const *texpr1,
		gboolean allow_blank, gboolean use_dropdown)
{
	GnmValidation *v;
	int nops, i;

	g_return_val_if_fail (type >= 0, NULL);
	g_return_val_if_fail (type < G_N_ELEMENTS (typeinfo), NULL);
	g_return_val_if_fail (op >= VALIDATION_OP_NONE, NULL);
	g_return_val_if_fail (op < (int)G_N_ELEMENTS (opinfo), NULL);

	switch (type) {
	case VALIDATION_TYPE_CUSTOM:
	case VALIDATION_TYPE_IN_LIST:
		nops = 1;
		if (op != VALIDATION_OP_NONE) {
			/*
			 * This can happen if an .xls file was saved
			 * as a .gnumeric.
			 */
			op = VALIDATION_OP_NONE;
		}
		break;
	case VALIDATION_TYPE_ANY:
		nops = 0;
		break;
	default:
		nops = (op == VALIDATION_OP_NONE) ? 0 : opinfo[op].nops;
	}

	v = g_new0 (GnmValidation, 1);
	v->ref_count = 1;
	v->title = title && title[0] ? go_string_new (title) : NULL;
	v->msg = msg && msg[0] ? go_string_new (msg) : NULL;
	v->texpr[0] = texpr0;
	v->texpr[1] = texpr1;
	v->style = style;
	v->type = type;
	v->op = op;
	v->allow_blank = (allow_blank != FALSE);
	v->use_dropdown = (use_dropdown != FALSE);

	/* Clear excess expressions.  */
	for (i = nops; i < 2; i++)
		if (v->texpr[i]) {
			gnm_expr_top_unref (v->texpr[i]);
			v->texpr[i] = NULL;
		}

	return v;
}
Example #7
0
/**
 * validation_set_expr :
 * @v : #GnmValidation
 * @texpr : #GnmExprTop
 * @indx : 0 or 1
 *
 * Assign an expression to a validation.  validation_is_ok can be used to
 * verify that @v has all of the requisit information.
 **/
void
validation_set_expr (GnmValidation *v,
		     GnmExprTop const *texpr, unsigned indx)
{
	g_return_if_fail (indx <= 1);

	if (NULL != texpr)
		gnm_expr_top_ref (texpr);
	if (NULL != v->texpr[indx])
		gnm_expr_top_unref (v->texpr[indx]);
	v->texpr[indx] = texpr;
}
Example #8
0
static void
gnm_cell_copy_free (GnmCellCopy *cc)
{
	if (cc->texpr) {
		gnm_expr_top_unref (cc->texpr);
		cc->texpr = NULL;
	}
	value_release (cc->val);
	cc->val = NULL;

	CHUNK_FREE (cell_copy_pool, cc);
}
Example #9
0
void
gnm_scenario_item_set_range (GnmScenarioItem *sci, const GnmSheetRange *sr)
{
	if (sr) {
		GnmValue *v = value_new_cellrange_r
			(sr->sheet != sci->dep.sheet ? sr->sheet : NULL,
			 &sr->range);
		GnmExprTop const *texpr = gnm_expr_top_new_constant (v);
		dependent_managed_set_expr (&sci->dep, texpr);
		gnm_expr_top_unref (texpr);
	} else
		dependent_managed_set_expr (&sci->dep, NULL);
}
Example #10
0
static void
cb_invalidate_cellcopy (GnmCellCopy *cc, gconstpointer ignore,
			GnmExprRelocateInfo *rinfo)
{
	GnmExprTop const *texpr;
	if (NULL != cc->texpr) {
		texpr = gnm_expr_top_relocate (cc->texpr, rinfo, FALSE);
		if (NULL != texpr) {
			gnm_expr_top_unref (cc->texpr);
			cc->texpr = texpr;
		}
	}
}
Example #11
0
/**
 * expr_name_set_expr :
 * @nexpr : the named expression
 * @new_expr : the new content
 * @rwinfo : optional.
 *
 * Unrefs the current content of @nexpr and absorbs a ref to @new_expr.
 **/
void
expr_name_set_expr (GnmNamedExpr *nexpr, GnmExprTop const *texpr)
{
	GSList *good = NULL;

	g_return_if_fail (nexpr != NULL);

	if (texpr == nexpr->texpr)
		return;
	if (nexpr->texpr != NULL) {
		GSList *deps = NULL, *junk = NULL;

		deps = expr_name_unlink_deps (nexpr);
		expr_name_handle_references (nexpr, FALSE);
		gnm_expr_top_unref (nexpr->texpr);

		/*
		 * We do not want to relink deps for sheets that are going
		 * away.  This speeds up exit for workbooks with lots of
		 * names defined.
		 */
		while (deps) {
			GSList *next = deps->next;
			GnmDependent *dep = deps->data;

			if (dep->sheet && dep->sheet->being_invalidated)
				deps->next = junk, junk = deps;
			else
				deps->next = good, good = deps;

			deps = next;
		}

		g_slist_free (junk);
	}
	nexpr->texpr = texpr;
	dependents_link (good);
	g_slist_free (good);

	if (texpr != NULL)
		expr_name_handle_references (nexpr, TRUE);

	expr_name_queue_deps (nexpr);
}
Example #12
0
static void
add_cell (Sheet *sheet, const psiconv_sheet_cell psi_cell,
	  const psiconv_formula_list psi_formulas, const GnmStyle * default_style)
{
	GnmCell *cell;
	GnmValue *val;
	GnmExprTop const *expr = NULL;

	cell = sheet_cell_fetch (sheet, psi_cell->column, psi_cell->row);
	if (!cell)
		return;

	val = value_new_from_psi_cell (psi_cell);

	if (psi_cell->calculated)
		expr = expr_new_from_formula (psi_cell, psi_formulas);

	if (expr != NULL) {
		/* TODO : is there a notion of parse format ?
		 * How does it store a user entered date ?
		 */
		if (val != NULL)
			gnm_cell_set_expr_and_value (cell, expr, val, TRUE);
		else
			gnm_cell_set_expr (cell, expr);
	} else if (val != NULL) {
		/* TODO : is there a notion of parse format ?
		 * How does it store a user entered date ?
		 */
		gnm_cell_set_value (cell, val);
	} else {
		/* TODO : send this warning to iocontext with details of
		 * which sheet and cell.
		 */
		g_warning ("Cell with no value or expression ?");
	}
	if (expr)
		gnm_expr_top_unref (expr);

	/* TODO: Perhaps this must be moved above set_format */
	set_style(sheet,psi_cell->row,psi_cell->column,psi_cell->layout,
	          default_style);
}
Example #13
0
/**
 * global_range_list_parse:
 * @sheet: Sheet where the range specification is relatively parsed to
 * @str  : a range or list of ranges to parse (ex: "A1", "A1:B1,C2,Sheet2!D2:D4")
 *
 * Parses a list of ranges, relative to the @sheet and returns a list with the
 * results.
 *
 * Returns a GSList containing Values of type VALUE_CELLRANGE, or NULL on failure
 **/
GSList *
global_range_list_parse (Sheet *sheet, char const *str)
{
	GnmParsePos  pp;
	GnmExprTop const *texpr;
	GSList   *ranges = NULL;
	GnmValue	 *v;

	g_return_val_if_fail (IS_SHEET (sheet), NULL);
	g_return_val_if_fail (str != NULL, NULL);

	texpr = gnm_expr_parse_str (str,
		 parse_pos_init_sheet (&pp, sheet),
		 GNM_EXPR_PARSE_FORCE_EXPLICIT_SHEET_REFERENCES |
		 GNM_EXPR_PARSE_PERMIT_MULTIPLE_EXPRESSIONS |
		 GNM_EXPR_PARSE_UNKNOWN_NAMES_ARE_STRINGS,
		 NULL, NULL);

	if (texpr != NULL)  {
		if (GNM_EXPR_GET_OPER (texpr->expr) == GNM_EXPR_OP_SET) {
			GnmExpr const *expr = texpr->expr;
			int i;
			for (i = 0; i < expr->set.argc; i++) {
				v = gnm_expr_get_range (expr->set.argv[i]);
				if (v == NULL) {
					range_list_destroy (ranges);
					ranges = NULL;
					break;
				} else
					ranges = g_slist_prepend (ranges, v);
			}
		} else {
			v = gnm_expr_top_get_range (texpr);
			if (v != NULL)
				ranges = g_slist_prepend (ranges, v);
		}
		gnm_expr_top_unref (texpr);
	}

	return g_slist_reverse (ranges);
}
Example #14
0
/* NOTE : Make sure to set up any merged regions in the target range BEFORE
 * this is called.
 */
static void
paste_link (GnmPasteTarget const *pt, int top, int left,
	    GnmCellRegion const *cr)
{
	GnmCellPos pos;
	GnmCellRef source_cell_ref;
	int x, y;

	/* Not possible to link to arbitrary (non gnumeric) sources yet. */
	/* TODO : eventually support interprocess gnumeric links */
	if (cr->origin_sheet == NULL)
		return;

	/* TODO : support relative links ? */
	source_cell_ref.col_relative = 0;
	source_cell_ref.row_relative = 0;
	source_cell_ref.sheet = (cr->origin_sheet != pt->sheet)
		? cr->origin_sheet : NULL;
	pos.col = left;
	for (x = 0 ; x < cr->cols ; x++, pos.col++) {
		source_cell_ref.col = cr->base.col + x;
		pos.row = top;
		for (y = 0 ; y < cr->rows ; y++, pos.row++) {
			GnmExprTop const *texpr;
			GnmCell *cell =
				sheet_cell_fetch (pt->sheet, pos.col, pos.row);

			/* This could easily be made smarter */
			if (!gnm_cell_is_merged (cell) &&
			    gnm_sheet_merge_contains_pos (pt->sheet, &pos))
					continue;
			source_cell_ref.row = cr->base.row + y;
			texpr = gnm_expr_top_new (gnm_expr_new_cellref (&source_cell_ref));
			gnm_cell_set_expr (cell, texpr);
			gnm_expr_top_unref (texpr);
		}
	}
}
Example #15
0
static gboolean
sylk_rtd_c_parse (SylkReader *state, char *str)
{
	GnmValue *val = NULL;
	GnmExprTop const *texpr = NULL;
	gboolean is_array = FALSE;
	int r = -1, c = -1, tmp;
	char *next;

	for (; *str != '\0' ; str = next) {
		next = sylk_next_token (str);
		switch (*str) {
		case 'X': if (sylk_parse_int (str+1, &tmp)) state->pp.eval.col = tmp - 1; break;
		case 'Y': if (sylk_parse_int (str+1, &tmp)) state->pp.eval.row = tmp - 1; break;

		case 'K': /* ;K value: Value of the cell. */
			if (val != NULL) {
				sylk_read_warning (state, _("Multiple values in the same cell"));
				value_release (val);
				val = NULL;
			}
			val = sylk_parse_value (state, str+1);
			break;

		case 'E':
			if (texpr != NULL) {
				sylk_read_warning (state, _("Multiple expressions in the same cell"));
				gnm_expr_top_unref (texpr);
			}
			texpr = sylk_parse_expr (state, str+1);
			break;
		case 'M' : /* ;M exp: Expression stored with UL corner of matrix (;R ;C defines
		  the lower right corner).  If the ;M field is supported, the
		  ;K record is ignored.  Note that no ;E field is written. */
			if (texpr != NULL) {
				sylk_read_warning (state, _("Multiple expressions in the same cell"));
				gnm_expr_top_unref (texpr);
			}
			texpr = sylk_parse_expr (state, str+1);
			is_array = TRUE;
			break;

		case 'I' : /* ;I: Inside a matrix or table (at row ;R, col ;C) C record for UL
		      corner must precede this record.  Note that any ;K field is
		      ignored if the ;I field is supported.  No ;E field is written
		      out. */
			is_array = TRUE;
			break;
		case 'C' : sylk_parse_int (str+1, &c); break;
		case 'R' : sylk_parse_int (str+1, &r); break;

		case 'A' : /* ;Aauthor:^[ :text 1) till end of line 2) excel extension */
			sylk_parse_comment (state, str+1);
			break;

		case 'G' : /* ;G: Defines shared value (may not have an ;E for this record). */
		case 'D' : /* ;D: Defines shared expression. */
		case 'S' : /* ;S: Shared expression/value given at row ;R, col ;C.  C record
		  for ;R, ;C must precede this one.  Note that no ;E or ;K
		  fields are written here (not allowed on Excel macro sheets). */

		case 'N' : /* ;N: Cell NOT protected/locked (if ;N present in ;ID record). */
		case 'P' : /* ;P: Cell protected/locked (if ;N not present in ;ID record).
		  Note if this occurs for any cell, we protect the entire
		  sheet. */

		case 'H' : /* ;H: Cell hidden. */

		case 'T' : /* ;Tref,ref: UL corner of table (;R ;C defines the lower left
		  corner).  Note that the defined rectangle is the INSIDE of
		  the table only.  Formulas and input cells are above and to
		  the left of this rectangle.  The row and column input cells
		  are given in by the two refs (possibly only one).  Note that
		  Excel's input refs are single cells only. */

		default:
			break;
		}
	}

	if (val != NULL || texpr != NULL) {
		GnmCell *cell = sheet_cell_fetch (state->pp.sheet,
			state->pp.eval.col, state->pp.eval.row);

		if (is_array) {
			if (texpr) {
				GnmRange rg;
				rg.start = state->pp.eval;
				rg.end.col = c - 1;
				rg.end.row = r - 1;

				gnm_cell_set_array (state->pp.sheet,
						    &rg,
						    texpr);
				gnm_expr_top_unref (texpr);
			}
			if (NULL != val)
				gnm_cell_assign_value (cell, val);
		} else if (NULL != texpr) {
			if (NULL != val)
				gnm_cell_set_expr_and_value (cell, texpr, val, TRUE);
			else
				gnm_cell_set_expr (cell, texpr);
			gnm_expr_top_unref (texpr);
		} else if (NULL != val)
			gnm_cell_set_value (cell, val);
	}

	return TRUE;
}
Example #16
0
/**
 * expr_name_add:
 * @pp:
 * @name:
 * @texpr: if texpr == NULL then create a placeholder with value #NAME?
 * @error_msg:
 * @link_to_container:
 *
 * Absorbs the reference to @texpr.
 * If @error_msg is non NULL it may hold a pointer to a translated descriptive
 * string.  NOTE : caller is responsible for freeing the error message.
 *
 * The reference semantics of the new expression are
 * 1) new names with @link_to_container TRUE are referenced by the container.
 *    The caller DOES NOT OWN a reference to the result, and needs to add their
 *    own.
 * 2) if @link_to_container is FALSE the caller DOES OWN a reference, and
 *    can free the result by unrefing the name.
 **/
GnmNamedExpr *
expr_name_add (GnmParsePos const *pp, char const *name,
	       GnmExprTop const *texpr, char **error_msg,
	       gboolean link_to_container,
	       GnmNamedExpr *stub)
{
	GnmNamedExpr *nexpr = NULL;
	GnmNamedExprCollection *scope = NULL;

	g_return_val_if_fail (pp != NULL, NULL);
	g_return_val_if_fail (pp->sheet != NULL || pp->wb != NULL, NULL);
	g_return_val_if_fail (name != NULL, NULL);
	g_return_val_if_fail (stub == NULL || stub->is_placeholder, NULL);

	if (texpr != NULL && expr_name_check_for_loop (name, texpr)) {
		gnm_expr_top_unref (texpr);
		if (error_msg)
			*error_msg = g_strdup_printf (_("'%s' has a circular reference"), name);
		return NULL;
	}

	scope = (pp->sheet != NULL) ? pp->sheet->names : pp->wb->names;
	/* see if there was a place holder */
	nexpr = g_hash_table_lookup (scope->placeholders, name);
	if (nexpr != NULL) {
		if (texpr == NULL) {
			/* there was already a placeholder for this */
			expr_name_ref (nexpr);
			return nexpr;
		}

		/* convert the placeholder into a real name */
		g_hash_table_steal (scope->placeholders, name);
		nexpr->is_placeholder = FALSE;
	} else {
		nexpr = g_hash_table_lookup (scope->names, name);
		/* If this is a permanent name, we may be adding it */
		/* on opening of a file, although */
		/* the name is already in place. */
		if (nexpr != NULL) {
			if (nexpr->is_permanent)
				link_to_container = FALSE;
			else {
				if (error_msg != NULL)
					*error_msg = (pp->sheet != NULL)
						? g_strdup_printf (_("'%s' is already defined in sheet"), name)
						: g_strdup_printf (_("'%s' is already defined in workbook"), name);

				gnm_expr_top_unref (texpr);
				return NULL;
			}
		}
	}

	if (error_msg)
		*error_msg = NULL;

	if (nexpr == NULL) {
		if (stub != NULL) {
			nexpr = stub;
			stub->is_placeholder = FALSE;
			go_string_unref (stub->name);
			stub->name = go_string_new (name);
		} else {
			nexpr = expr_name_new (name);
			nexpr->is_placeholder = (texpr == NULL);
		}
	}
	parse_pos_init (&nexpr->pos,
		pp->wb, pp->sheet, pp->eval.col, pp->eval.row);
	if (texpr == NULL)
		texpr = gnm_expr_top_new_constant
			(value_new_error_NAME (NULL));
	expr_name_set_expr (nexpr, texpr);
	if (link_to_container)
		gnm_named_expr_collection_insert (scope, nexpr);

	return nexpr;
}
Example #17
0
/**
 * wbcg_edit_finish:
 * @wbcg: #WBCGtk
 * @result: what should we do with the content
 * @showed_dialog: If non-NULL will indicate if a dialog was displayed.
 *
 * Return: TRUE if editing completed successfully, or we were no editing.
 **/
gboolean
wbcg_edit_finish (WBCGtk *wbcg, WBCEditResult result,
		  gboolean *showed_dialog)
{
	Sheet *sheet;
	SheetView *sv;
	WorkbookControl *wbc;
	WorkbookView	*wbv;

	g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE);

	wbc = GNM_WBC (wbcg);
	wbv = wb_control_view (wbc);

	wbcg_focus_cur_scg (wbcg);

	gnm_expr_entry_close_tips (wbcg_get_entry_logical (wbcg));

	if (showed_dialog != NULL)
		*showed_dialog = FALSE;

	/* Remove the range selection cursor if it exists */
	if (NULL != wbcg->rangesel)
		scg_rangesel_stop (wbcg->rangesel, result == WBC_EDIT_REJECT);

	if (!wbcg_is_editing (wbcg)) {
		/* We may have a guru up even if we are not editing. remove it.
		 * Do NOT remove until later it if we are editing, it is possible
		 * that we may want to continue editing.
		 */
		if (wbcg->edit_line.guru != NULL) {
			GtkWidget *w = wbcg->edit_line.guru;
			wbc_gtk_detach_guru (wbcg);
			gtk_widget_destroy (w);
		}

		return TRUE;
	}

	g_return_val_if_fail (IS_SHEET (wbcg->editing_sheet), TRUE);

	sheet = wbcg->editing_sheet;
	sv = sheet_get_view (sheet, wbv);

	/* Save the results before changing focus */
	if (result != WBC_EDIT_REJECT) {
		ValidationStatus valid = GNM_VALIDATION_STATUS_VALID;
		char *free_txt = NULL;
		char const *txt;
		GnmStyle const *mstyle;
		char const *expr_txt = NULL;
		GOFormat const *fmt;
		GnmValue *value;
		GOUndo *u = NULL;
		GSList	*selection = selection_get_ranges (sv, FALSE);
		GnmParsePos    pp;
		GnmExprTop const *texpr = NULL;

		parse_pos_init_editpos (&pp, sv);

		/* Array only works on single range.  */
		if (result == WBC_EDIT_ACCEPT_ARRAY &&
		    (selection == NULL || selection->next != NULL))
			result = WBC_EDIT_ACCEPT_RANGE;

		/******* Check whether we would split a range ********/

		switch (result) {
		case (WBC_EDIT_ACCEPT_RANGE):
		case (WBC_EDIT_ACCEPT_ARRAY): {
			if (sheet_ranges_split_region (sheet, selection,
						       GO_CMD_CONTEXT (wbc), _("Set Text"))) {
				range_fragment_free (selection);
				if (showed_dialog != NULL)
					*showed_dialog = TRUE;
				return FALSE;
			}

			if (result == WBC_EDIT_ACCEPT_ARRAY &&
			    sheet_range_contains_merges_or_arrays
			    	(sheet, selection->data,
				 GO_CMD_CONTEXT (wbc), _("Set Text"),
				 TRUE, FALSE)) {
				range_fragment_free (selection);
				if (showed_dialog != NULL)
					*showed_dialog = TRUE;
				return FALSE;
			}

			break;
		}
		case (WBC_EDIT_ACCEPT_WO_AC):
		case (WBC_EDIT_ACCEPT): {
			GnmCell const *cell = sheet_cell_get
				(sheet, sv->edit_pos.col, sv->edit_pos.row);
			if (gnm_cell_is_nonsingleton_array (cell)) {
				gnm_cmd_context_error_splits_array (GO_CMD_CONTEXT (wbc),
								    _("Set Text"), NULL);
				if (showed_dialog != NULL)
					*showed_dialog = TRUE;
				range_fragment_free (selection);
				return FALSE;
			}
			break;
		}
		case (WBC_EDIT_REJECT):
		default:
			/* We should not be able to get here! */
			break;
		}


		/******* Check whether the range is locked ********/

		switch (result) {
		case (WBC_EDIT_ACCEPT_RANGE):
		case (WBC_EDIT_ACCEPT_ARRAY): {
			if (cmd_selection_is_locked_effective (sheet, selection, wbc,
							       _("Set Text"))) {
				range_fragment_free (selection);
				if (showed_dialog != NULL)
					*showed_dialog = TRUE;
				return FALSE;
			}
			break;
		}
		case (WBC_EDIT_ACCEPT_WO_AC):
		case (WBC_EDIT_ACCEPT): {
			GnmRange r;
			r.end = r.start = pp.eval;

			if (cmd_cell_range_is_locked_effective (sheet, &r, wbc,
							       _("Set Text"))) {
				range_fragment_free (selection);
				if (showed_dialog != NULL)
					*showed_dialog = TRUE;
				return FALSE;
			}
			break;
		}
		case (WBC_EDIT_REJECT):
		default:
			/* We should not be able to get here! */
			break;
		}
		/*****************************************************/

		txt = wbcg_edit_get_display_text (wbcg);
		mstyle = sheet_style_get (sheet, sv->edit_pos.col, sv->edit_pos.row);
		fmt = gnm_cell_get_format (sheet_cell_fetch (sheet, sv->edit_pos.col,
							     sv->edit_pos.row));

		value = format_match (txt, fmt,
				      workbook_date_conv (sheet->workbook));
		if (value == NULL)
			expr_txt = gnm_expr_char_start_p (txt);
		else
			value_release (value);

		/* NOTE : do not modify gnm_expr_char_start_p to exclude "-"
		 * it _can_ start an expression, which is required for rangesel
		 * it just isn't an expression. */
		if (expr_txt != NULL && *expr_txt != '\0' && strcmp (expr_txt, "-")) {
			GnmExprTop const *texpr_test = NULL;
			GnmParseError  perr;

			parse_error_init (&perr);
			texpr_test = gnm_expr_parse_str (expr_txt,
							 &pp, GNM_EXPR_PARSE_DEFAULT, NULL, &perr);
			/* Try adding a single extra closing paren to see if it helps */
			if (texpr_test == NULL && perr.err != NULL &&
			    perr.err->code == PERR_MISSING_PAREN_CLOSE) {
				GnmParseError tmp_err;
				char *tmp = g_strconcat (txt, ")", NULL);
				parse_error_init (&tmp_err);
				texpr_test = gnm_expr_parse_str (gnm_expr_char_start_p (tmp),
								 &pp, GNM_EXPR_PARSE_DEFAULT,
								 NULL, &tmp_err);
				parse_error_free (&tmp_err);

				if (texpr_test != NULL) {
					txt = free_txt = tmp;
					expr_txt = gnm_expr_char_start_p (txt);
				} else
					g_free (tmp);
			}

			if (texpr_test == NULL && perr.err != NULL) {
				ValidationStatus reedit;

				/* set focus _before_ selection.  gtk2 seems to
				 * screw with selection in gtk_entry_grab_focus
				 * (no longer required now that we clear
				 * gtk-entry-select-on-focus) */
				gtk_window_set_focus (wbcg_toplevel (wbcg),
						      (GtkWidget *) wbcg_get_entry (wbcg));

				if (perr.begin_char != 0 || perr.end_char != 0) {
					int offset = expr_txt - txt;
					gtk_editable_select_region (GTK_EDITABLE (wbcg_get_entry (wbcg)),
								    offset + perr.begin_char,
								    offset + perr.end_char);
				} else
					gtk_editable_set_position (
								   GTK_EDITABLE (wbcg_get_entry (wbcg)), -1);

				reedit = wb_control_validation_msg (GNM_WBC (wbcg),
								    GNM_VALIDATION_STYLE_PARSE_ERROR, NULL,
								    perr.err->message);
				if (showed_dialog != NULL)
					*showed_dialog = TRUE;

				parse_error_free (&perr);
				if (reedit == GNM_VALIDATION_STATUS_INVALID_EDIT) {
					range_fragment_free (selection);
					return FALSE;
				}
				/* restore focus to sheet , or we'll leave edit
				 * mode only to jump right back in the new
				 * cell because it looks like someone just
				 * focused on the edit line (eg hit F2) */
				wbcg_focus_cur_scg (wbcg);
			}
			if (texpr_test != NULL)
				gnm_expr_top_unref (texpr_test);
		}

		/* We only enter an array formula if the text is a formula */
		if (result == WBC_EDIT_ACCEPT_ARRAY && !expr_txt)
			result = WBC_EDIT_ACCEPT_RANGE;

		if (result == WBC_EDIT_ACCEPT_ARRAY) {
			GnmParsePos pp_array;
			GnmRange *r = selection->data;

			parse_pos_init (&pp_array, sheet->workbook, sheet, r->start.col, r->start.row);

			if ((texpr = gnm_expr_parse_str
			     (expr_txt, &pp_array, GNM_EXPR_PARSE_DEFAULT,
			      sheet_get_conventions (sheet), NULL)) == NULL)
				result = WBC_EDIT_ACCEPT_RANGE;
		}

		/* We need to save the information that we will temporarily overwrite */
		/* We then assign the information. No need to worry about formatting */
		/* Finally we can check the validation! */

		switch (result) {
		case (WBC_EDIT_ACCEPT_RANGE): {
			GSList	*l;

			for (l = selection; l != NULL; l = l->next) {
				GnmRange *r = l->data;
				u = go_undo_combine (u,  clipboard_copy_range_undo (sheet, r));
			}
			for (l = selection; l != NULL; l = l->next) {
				GnmRange *r = l->data;
				/* We do this separately since there may be overlap between ranges */
				sheet_range_set_text (&pp, r, txt);
				valid =	gnm_validation_eval_range (wbc, sheet, &sv->edit_pos, r,
							       showed_dialog);
				if (valid != GNM_VALIDATION_STATUS_VALID)
					break;
			}
			break;
		}
		case (WBC_EDIT_ACCEPT_ARRAY): {
			GnmRange *r = selection->data;

			u = go_undo_combine (u,  clipboard_copy_range_undo (sheet, r));
			if (texpr) {
				gnm_expr_top_ref (texpr);
				gnm_cell_set_array_formula (sheet,
							    r->start.col, r->start.row,
							    r->end.col, r->end.row,
							    texpr);
				sheet_region_queue_recalc (sheet, r);
			}
			valid =	gnm_validation_eval_range (wbc, sheet, &sv->edit_pos, r,
						       showed_dialog);
			break;
		}
		case (WBC_EDIT_ACCEPT_WO_AC):
		case (WBC_EDIT_ACCEPT): {
			GnmRange r;
			GnmCell *cell;

			range_init_cellpos (&r, &sv->edit_pos);
			u = clipboard_copy_range_undo (sheet, &r);

			cell = sheet_cell_fetch (sheet,
						 sv->edit_pos.col,
						 sv->edit_pos.row);
			sheet_cell_set_text (cell, txt, wbcg->edit_line.markup);
			valid = gnm_validation_eval (wbc, mstyle, sheet, &sv->edit_pos, showed_dialog);
			break;
		}
		case (WBC_EDIT_REJECT):
		default:
			/* We should not be able to get here! */
			break;
		}

		range_fragment_free (selection);

		/* We need to rebuild the original info first. */

		go_undo_undo (u);
		g_object_unref (u);

		/* Now we can respond to our validation information */

		if (valid != GNM_VALIDATION_STATUS_VALID) {
			result = WBC_EDIT_REJECT;
			if (valid == GNM_VALIDATION_STATUS_INVALID_EDIT) {
				gtk_window_set_focus (wbcg_toplevel (wbcg),
					(GtkWidget *) wbcg_get_entry (wbcg));
				g_free (free_txt);
				if (texpr != NULL)
					gnm_expr_top_unref (texpr);
				return FALSE;
			}
		} else {
			if (result == WBC_EDIT_ACCEPT_ARRAY) {
				cmd_area_set_array_expr (wbc, sv, texpr);

			} else {
				PangoAttrList *res_markup = wbcg->edit_line.markup
					? pango_attr_list_copy (wbcg->edit_line.markup)
					: NULL;
				if (result == WBC_EDIT_ACCEPT)
					cmd_set_text (wbc, sheet, &sv->edit_pos, txt, res_markup, TRUE);
				else if (result == WBC_EDIT_ACCEPT_WO_AC)
					cmd_set_text (wbc, sheet, &sv->edit_pos, txt, res_markup, FALSE);
				else
					cmd_area_set_text (wbc, sv, txt, res_markup);
				if (res_markup)
					pango_attr_list_unref (res_markup);
			}
		}
		if (texpr != NULL)
			gnm_expr_top_unref (texpr);
		g_free (free_txt);
	} else {
		if (sv == wb_control_cur_sheet_view (wbc)) {
			/* Redraw the cell contents in case there was a span */
			GnmRange tmp; tmp.start = tmp.end = sv->edit_pos;
			sheet_range_bounding_box (sv->sheet, &tmp);
			sv_redraw_range (wb_control_cur_sheet_view (wbc), &tmp);
		}

		/* Reload the entry widget with the original contents */
		wb_view_edit_line_set (wbv, wbc);
	}

	/* Stop editing */
	wbcg->editing = FALSE;
	wbcg->editing_sheet = NULL;
	wbcg->editing_cell = NULL;

	if (wbcg->edit_line.guru != NULL) {
		GtkWidget *w = wbcg->edit_line.guru;
		wbc_gtk_detach_guru (wbcg);
		gtk_widget_destroy (w);
	}

	if (wbcg->edit_line.signal_insert) {
		g_signal_handler_disconnect (wbcg_get_entry (wbcg),
					     wbcg->edit_line.signal_insert);
		wbcg->edit_line.signal_insert = 0;
	}
	if (wbcg->edit_line.signal_delete) {
		g_signal_handler_disconnect (wbcg_get_entry (wbcg),
					     wbcg->edit_line.signal_delete);
		wbcg->edit_line.signal_delete = 0;
	}
	if (wbcg->edit_line.signal_cursor_pos) {
		g_signal_handler_disconnect (wbcg_get_entry (wbcg),
					     wbcg->edit_line.signal_cursor_pos);
		wbcg->edit_line.signal_cursor_pos = 0;
	}
	if (wbcg->edit_line.signal_selection_bound) {
		g_signal_handler_disconnect (wbcg_get_entry (wbcg),
					     wbcg->edit_line.signal_selection_bound);
		wbcg->edit_line.signal_selection_bound = 0;
	}

	if (wbcg->edit_line.cell_attrs != NULL) {
		pango_attr_list_unref (wbcg->edit_line.cell_attrs);
		wbcg->edit_line.cell_attrs = NULL;
	}

	if (wbcg->edit_line.markup) {
		pango_attr_list_unref (wbcg->edit_line.markup);
		wbcg->edit_line.markup = NULL;
	}

	if (wbcg->edit_line.full_content != NULL) {
		pango_attr_list_unref (wbcg->edit_line.full_content);
		wbcg->edit_line.full_content = NULL;
	}

	if (wbcg->edit_line.cur_fmt) {
		pango_attr_list_unref (wbcg->edit_line.cur_fmt);
		wbcg->edit_line.cur_fmt = NULL;
	}

	/* set pos to 0, to ensure that if we start editing by clicking on the
	 * editline at the last position, we'll get the right style feedback */
	gtk_editable_set_position ((GtkEditable *) wbcg_get_entry (wbcg), 0);

	wb_control_update_action_sensitivity (wbc);

	if (!sheet->workbook->during_destruction) {
		/* restore focus to original sheet in case things were being selected
		 * on a different page.  Do no go through the view, rangesel is
		 * specific to the control.  */
		wb_control_sheet_focus (wbc, sheet);
		/* Only the edit sheet has an edit cursor */
		scg_edit_stop (wbcg_cur_scg (wbcg));
	}
	wbcg_auto_complete_destroy (wbcg);
	wb_control_style_feedback (wbc, NULL);	/* in case markup messed with things */

	return TRUE;
}
Example #18
0
/**
 * gnm_validation_eval:
 * @wbc:
 * @mstyle:
 * @sheet:
 *
 * validation set in the GnmStyle if applicable.
 **/
ValidationStatus
gnm_validation_eval (WorkbookControl *wbc, GnmStyle const *mstyle,
		 Sheet *sheet, GnmCellPos const *pos, gboolean *showed_dialog)
{
	GnmValidation const *v;
	GnmCell *cell;
	GnmValue *val;
	gnm_float x;
	int nok, i;
	GnmEvalPos ep;

	if (showed_dialog) *showed_dialog = FALSE;

	v = gnm_style_get_validation (mstyle);
	if (v == NULL)
		return GNM_VALIDATION_STATUS_VALID;

	if (v->type == GNM_VALIDATION_TYPE_ANY)
		return GNM_VALIDATION_STATUS_VALID;

	cell = sheet_cell_get (sheet, pos->col, pos->row);
	if (cell != NULL)
		gnm_cell_eval (cell);

	if (gnm_cell_is_empty (cell)) {
		if (v->allow_blank)
			return GNM_VALIDATION_STATUS_VALID;
		BARF (g_strdup_printf (_("Cell %s is not permitted to be blank"),
				       cell_name (cell)));
	}

	val = cell->value;
	switch (val->type) {
	case VALUE_ERROR:
		if (typeinfo[v->type].errors_not_allowed)
			BARF (g_strdup_printf (_("Cell %s is not permitted to contain error values"),
					       cell_name (cell)));
		break;

	case VALUE_BOOLEAN:
		if (typeinfo[v->type].bool_always_ok)
			return GNM_VALIDATION_STATUS_VALID;
		break;

	case VALUE_STRING:
		if (typeinfo[v->type].strings_not_allowed)
			BARF (g_strdup_printf (_("Cell %s is not permitted to contain strings"),
					       cell_name (cell)));
		break;

	default:
		break;
	}

	eval_pos_init_cell (&ep, cell);

	switch (v->type) {
	case GNM_VALIDATION_TYPE_AS_INT:
		x = value_get_as_float (val);
		if (gnm_fake_floor (x) == gnm_fake_ceil (x))
			break;
		else
			BARF (g_strdup_printf (_("'%s' is not an integer"),
					       value_peek_string (val)));

	case GNM_VALIDATION_TYPE_AS_NUMBER:
		x = value_get_as_float (val);
		break;

	case GNM_VALIDATION_TYPE_AS_DATE: /* What the hell does this do?  */
		x = value_get_as_float (val);
		if (x < 0)
			BARF (g_strdup_printf (_("'%s' is not a valid date"),
					       value_peek_string (val)));
		break;


	case GNM_VALIDATION_TYPE_AS_TIME: /* What the hell does this do?  */
		x = value_get_as_float (val);
		break;

	case GNM_VALIDATION_TYPE_IN_LIST: {
		GnmExprTop const *texpr = v->deps[0].texpr;
		if (texpr) {
			GnmValue *list = gnm_expr_top_eval
				(texpr, &ep,
				 GNM_EXPR_EVAL_PERMIT_NON_SCALAR | GNM_EXPR_EVAL_PERMIT_EMPTY);
			GnmValue *res = value_area_foreach (list, &ep, CELL_ITER_IGNORE_BLANK,
				 (GnmValueIterFunc) cb_validate_custom, val);
			value_release (list);
			if (res == NULL) {
				GnmParsePos pp;
				char *expr_str = gnm_expr_top_as_string
					(texpr,
					 parse_pos_init_evalpos (&pp, &ep),
					 ep.sheet->convs);
				char *msg = g_strdup_printf (_("%s does not contain the new value."), expr_str);
				g_free (expr_str);
				BARF (msg);
			}
		}
		return GNM_VALIDATION_STATUS_VALID;
	}

	case GNM_VALIDATION_TYPE_TEXT_LENGTH:
		/* XL appears to use a very basic value->string mapping that
		 * ignores formatting.
		 * eg len (12/13/01) == len (37238) = 5
		 * This seems wrong for
		 */
		x = g_utf8_strlen (value_peek_string (val), -1);
		break;

	case GNM_VALIDATION_TYPE_CUSTOM: {
		gboolean valid;
		GnmExprTop const *texpr = v->deps[0].texpr;

		if (!texpr)
			return GNM_VALIDATION_STATUS_VALID;

		val = gnm_expr_top_eval (texpr, &ep, GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
		valid = value_get_as_bool (val, NULL);
		value_release (val);

		if (valid)
			return GNM_VALIDATION_STATUS_VALID;
		else {
			GnmParsePos pp;
			char *expr_str = gnm_expr_top_as_string
				(texpr,
				 parse_pos_init_evalpos (&pp, &ep),
				 ep.sheet->convs);
			char *msg = g_strdup_printf (_("%s is not true."), expr_str);
			g_free (expr_str);
			BARF (msg);
		}
	}

	default:
		g_assert_not_reached ();
		return GNM_VALIDATION_STATUS_VALID;
	}

	if (v->op == GNM_VALIDATION_OP_NONE)
		return GNM_VALIDATION_STATUS_VALID;

	nok = 0;
	for (i = 0; i < opinfo[v->op].nops; i++) {
		GnmExprTop const *texpr_i = v->deps[i].texpr;
		GnmExprTop const *texpr;
		GnmValue *cres;

		if (!texpr_i) {
			nok++;
			continue;
		}

		texpr = gnm_expr_top_new
			(gnm_expr_new_binary
			 (gnm_expr_new_constant (value_new_float (x)),
			  opinfo[v->op].ops[i],
			  gnm_expr_copy (texpr_i->expr)));
		cres = gnm_expr_top_eval
			(texpr, &ep, GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
		if (value_get_as_bool (cres, NULL))
			nok++;
		value_release (cres);
		gnm_expr_top_unref (texpr);
	}

	if (nok < opinfo[v->op].ntrue)
		BARF (g_strdup_printf (_("%s is out of permitted range"),
				       value_peek_string (val)));

	return GNM_VALIDATION_STATUS_VALID;
}
Example #19
0
/**
 * gnm_validation_new :
 * @title: will be copied.
 * @msg: will be copied.
 * @texpr0: absorb the reference to the expression (optionally %NULL).
 * @texpr1: absorb the reference to the expression (optionally %NULL).
 *
 * Does _NOT_ require all necessary information to be set here.
 * gnm_validation_set_expr can be used to change the expressions after creation,
 * and gnm_validation_is_ok can be used to ensure that things are properly setup.
 *
 * Returns a new @GnmValidation object that needs to be unrefed.
 **/
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)
{
	GnmValidation *v;
	int nops;

	g_return_val_if_fail (type >= 0, NULL);
	g_return_val_if_fail (type < G_N_ELEMENTS (typeinfo), NULL);
	g_return_val_if_fail (op >= GNM_VALIDATION_OP_NONE, NULL);
	g_return_val_if_fail (op < (int)G_N_ELEMENTS (opinfo), NULL);
	g_return_val_if_fail (IS_SHEET (sheet), NULL);

	switch (type) {
	case GNM_VALIDATION_TYPE_CUSTOM:
	case GNM_VALIDATION_TYPE_IN_LIST:
		nops = 1;
		if (op != GNM_VALIDATION_OP_NONE) {
			/*
			 * This can happen if an .xls file was saved
			 * as a .gnumeric.
			 */
			op = GNM_VALIDATION_OP_NONE;
		}
		break;
	case GNM_VALIDATION_TYPE_ANY:
		nops = 0;
		break;
	default:
		nops = (op == GNM_VALIDATION_OP_NONE) ? 0 : opinfo[op].nops;
	}

	v = g_new0 (GnmValidation, 1);
	v->ref_count = 1;
	v->title = title && title[0] ? go_string_new (title) : NULL;
	v->msg = msg && msg[0] ? go_string_new (msg) : NULL;

	dependent_managed_init (&v->deps[0], sheet);
	if (texpr0) {
		if (nops > 0)
			dependent_managed_set_expr (&v->deps[0], texpr0);
		gnm_expr_top_unref (texpr0);
	}

	dependent_managed_init (&v->deps[1], sheet);
	if (texpr1) {
		if (nops > 1)
			dependent_managed_set_expr (&v->deps[1], texpr1);
		gnm_expr_top_unref (texpr1);
	}

	v->style = style;
	v->type = type;
	v->op = op;
	v->allow_blank = (allow_blank != FALSE);
	v->use_dropdown = (use_dropdown != FALSE);

	return v;
}