auto-format.c 7.31 KB
Newer Older
Morten Welinder's avatar
Morten Welinder committed
1 2 3
/*
 * auto-format.c: Suggest formats for expressions.
 *
4 5 6 7
 * NOTE: If you were looking for the code to automatically put style on a
 * region, you are in the wrong place.  See the files file-autoft.c and
 * dialogs/dialog-autoformat.c instead.
 *
Morten Welinder's avatar
Morten Welinder committed
8 9 10 11
 * Authors:
 *   Morten Welinder <terra@diku.dk>
 */

12 13
#include <gnumeric-config.h>
#include "gnumeric.h"
Morten Welinder's avatar
Morten Welinder committed
14
#include "auto-format.h"
15

Jody Goldberg's avatar
Jody Goldberg committed
16 17 18
#include "func.h"
#include "cell.h"
#include "value.h"
Morten Welinder's avatar
Morten Welinder committed
19
#include "expr.h"
20
#include "expr-impl.h"
Jody Goldberg's avatar
Jody Goldberg committed
21
#include "sheet.h"
22
#include "workbook.h"
Jody Goldberg's avatar
Jody Goldberg committed
23
#include "format.h"
24
#include "formats.h"
Morten Welinder's avatar
Morten Welinder committed
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53

/* ------------------------------------------------------------------------- */
/*
 * An important note about correctness.
 *
 * For some functions it is easy to tell what correct behaviour is;
 * if the evaluation of the percent operator yields anything but x/100
 * that is bad.
 *
 * This function is not that simple.
 *
 * If we fail to suggest a format when one might have been deduced, that
 * is really not a big deal.  So the fact that "=date(2000,1,1)^1" is not
 * recognised as a date bothers no-one.
 *
 * If we occasionally suggest a format where none is reasonable, that is
 * not a problem either.  "=pv(1,2,3,4,5)*today()" has no reasonable
 * format, but we assign one.  Tough.
 *
 * On the other hand, if we suggest a bad format for a function that does
 * have a good format, this is bad.  (Since the user will just select
 * another format, it isn't critical, just bad.)
 *
 * Please resist the temptation of making this ridiculously smart.  For
 * example, avoid too much algebra here and don't look at actual numbers
 * encountered.  Let the evaluator do that.  One reason for this is that
 * if you are entering a range of similar data, you really want the same
 * format.  You don't want a different number of decimals for "22%" and
 * "22.5%".
54 55
 *
 * (The problem here is actually more a physics problem -- what are the
Morten Welinder's avatar
Morten Welinder committed
56
 * units -- than a math problem.)
Morten Welinder's avatar
Morten Welinder committed
57 58 59
 */
/* ------------------------------------------------------------------------- */

60
#define AF_EXPLICIT ((GnmFuncFlags)(GNM_FUNC_AUTO_MASK + 1))
Morten Welinder's avatar
Morten Welinder committed
61

62 63 64
static GnmFuncFlags do_af_suggest_list (GnmExprList *list,
					EvalPos const *epos,
					StyleFormat **explicit);
Morten Welinder's avatar
Morten Welinder committed
65

66
struct cb_af_suggest { GnmFuncFlags typ; StyleFormat **explicit; };
Morten Welinder's avatar
Morten Welinder committed
67 68

static Value *
Morten Welinder's avatar
Morten Welinder committed
69 70
cb_af_suggest (G_GNUC_UNUSED Sheet *sheet,
	       G_GNUC_UNUSED int col, G_GNUC_UNUSED int row,
71
	       Cell *cell, void *_data)
Morten Welinder's avatar
Morten Welinder committed
72 73 74 75 76 77
{
	struct cb_af_suggest *data = _data;

	*(data->explicit) = cell_get_format (cell);
	if (*(data->explicit)) {
		data->typ = AF_EXPLICIT;
78
		return VALUE_TERMINATE;
Morten Welinder's avatar
Morten Welinder committed
79 80 81 82
	}
	return NULL;
}

83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
static gboolean
is_date (GnmFuncFlags typ, StyleFormat *explicit)
{
	switch (typ) {
	case GNM_FUNC_AUTO_DATE: return TRUE;

	default: return FALSE;

	case AF_EXPLICIT: {
		FormatCharacteristics info;
		return (cell_format_classify (explicit, &info) == FMT_DATE);
	}
	}
}

98
static GnmFuncFlags
99
do_af_suggest (GnmExpr const *expr, const EvalPos *epos, StyleFormat **explicit)
Morten Welinder's avatar
Morten Welinder committed
100
{
101
	switch (expr->any.oper) {
102 103 104 105 106 107
	case GNM_EXPR_OP_EQUAL:
	case GNM_EXPR_OP_GT:
	case GNM_EXPR_OP_LT:
	case GNM_EXPR_OP_GTE:
	case GNM_EXPR_OP_LTE:
	case GNM_EXPR_OP_NOT_EQUAL:
108
		return GNM_FUNC_AUTO_UNITLESS;  /* Close enough.  */
Morten Welinder's avatar
Morten Welinder committed
109

110
	case GNM_EXPR_OP_MULT:
Morten Welinder's avatar
Morten Welinder committed
111
		/* Fall through.  This isn't quite right, but good enough.  */
112
	case GNM_EXPR_OP_ADD: {
Morten Welinder's avatar
Morten Welinder committed
113
		/* Return the first interesting type we see.  */
114
		GnmFuncFlags typ;
Morten Welinder's avatar
Morten Welinder committed
115

116
		typ = do_af_suggest (expr->binary.value_a, epos, explicit);
117
		if (typ != GNM_FUNC_AUTO_UNKNOWN && typ != GNM_FUNC_AUTO_UNITLESS)
Morten Welinder's avatar
Morten Welinder committed
118 119
			return typ;

120
		return do_af_suggest (expr->binary.value_b, epos, explicit);
Morten Welinder's avatar
Morten Welinder committed
121 122
	}

123
	case GNM_EXPR_OP_SUB: {
124
		GnmFuncFlags typ1, typ2;
125
		StyleFormat *explicit1 = NULL, *explicit2 = NULL;
126

127 128
		typ1 = do_af_suggest (expr->binary.value_a, epos, &explicit1);
		typ2 = do_af_suggest (expr->binary.value_b, epos, &explicit2);
129

130
		if (is_date (typ1, explicit1) && is_date (typ2, explicit2))
131
			return GNM_FUNC_AUTO_UNITLESS;
132
		else if (typ1 != GNM_FUNC_AUTO_UNKNOWN && typ1 != GNM_FUNC_AUTO_UNITLESS) {
133 134 135 136 137
			*explicit = explicit1;
			return typ1;
		} else {
			*explicit = explicit2;
			return typ2;
138
		}
139 140
	}

141
	case GNM_EXPR_OP_DIV:
Morten Welinder's avatar
Morten Welinder committed
142
		/* Check the left-hand side only.  */
143
		return do_af_suggest (expr->binary.value_a, epos, explicit);
Morten Welinder's avatar
Morten Welinder committed
144

145
	case GNM_EXPR_OP_FUNCALL: {
146 147
		GnmFuncFlags typ =
			(expr->func.func->flags & GNM_FUNC_AUTO_MASK);
Morten Welinder's avatar
Morten Welinder committed
148 149

		switch (typ) {
150
		case GNM_FUNC_AUTO_FIRST:
151
			return do_af_suggest_list (expr->func.arg_list,
152
						   epos, explicit);
Morten Welinder's avatar
Morten Welinder committed
153

154
		case GNM_FUNC_AUTO_SECOND: {
155
			GnmExprList *l;
156
			l = expr->func.arg_list;
157
			if (l) l = l->next;
158
			return do_af_suggest_list (l, epos, explicit);
159 160
		}

Morten Welinder's avatar
Morten Welinder committed
161 162 163 164 165
		default:
			return typ;
		}
	}

166
	case GNM_EXPR_OP_CONSTANT: {
167
		const Value *v = expr->constant.value;
Morten Welinder's avatar
Morten Welinder committed
168 169 170 171 172

		switch (v->type) {
		case VALUE_STRING:
		case VALUE_ERROR:
		case VALUE_ARRAY:
173
			return GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
174 175 176 177 178

		case VALUE_CELLRANGE: {
			struct cb_af_suggest closure;

			/* If we don't have a sheet, we cannot look up vars. */
179
			if (epos->sheet == NULL)
180
				return GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
181

182
			closure.typ = GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
183
			closure.explicit = explicit;
184 185 186
			workbook_foreach_cell_in_range (epos, v,
				CELL_ITER_IGNORE_BLANK,
				&cb_af_suggest, &closure);
Morten Welinder's avatar
Morten Welinder committed
187 188 189 190
			return closure.typ;
		}

		default:
191
			return GNM_FUNC_AUTO_UNITLESS;
Morten Welinder's avatar
Morten Welinder committed
192 193 194
		}
	}

195
	case GNM_EXPR_OP_CELLREF: {
196 197 198 199
		Sheet const *sheet;
		CellRef const *ref;
		Cell const *cell;
		CellPos pos;
Morten Welinder's avatar
Morten Welinder committed
200

201
		ref = &expr->cellref.ref;
202
		sheet = eval_sheet (ref->sheet, epos->sheet);
Morten Welinder's avatar
Morten Welinder committed
203 204
		/* If we don't have a sheet, we cannot look up vars.  */
		if (sheet == NULL)
205
			return GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
206

207 208
		cellref_get_abs_pos (ref, &epos->eval, &pos);
		cell = sheet_cell_get (sheet, pos.col, pos.row);
Morten Welinder's avatar
Morten Welinder committed
209
		if (cell == NULL)
210
			return GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
211 212

		*explicit = cell_get_format (cell);
213
		return *explicit ? AF_EXPLICIT : GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
214 215
	}

216 217
	case GNM_EXPR_OP_UNARY_NEG:
	case GNM_EXPR_OP_UNARY_PLUS:
218
		return do_af_suggest (expr->unary.value, epos, explicit);
Morten Welinder's avatar
Morten Welinder committed
219

220
	case GNM_EXPR_OP_PERCENTAGE:
221
		return GNM_FUNC_AUTO_PERCENT;
Morten Welinder's avatar
Morten Welinder committed
222

223 224 225 226
	case GNM_EXPR_OP_EXP:
	case GNM_EXPR_OP_CAT:
	case GNM_EXPR_OP_NAME:
	case GNM_EXPR_OP_ARRAY:
Morten Welinder's avatar
Morten Welinder committed
227
	default:
228
		return GNM_FUNC_AUTO_UNKNOWN;
Morten Welinder's avatar
Morten Welinder committed
229 230 231
	}
}

232
static GnmFuncFlags
233
do_af_suggest_list (GnmExprList *list, const EvalPos *epos, StyleFormat **explicit)
Morten Welinder's avatar
Morten Welinder committed
234
{
235 236
	GnmFuncFlags typ = GNM_FUNC_AUTO_UNKNOWN;
	while (list && (typ == GNM_FUNC_AUTO_UNKNOWN || typ == GNM_FUNC_AUTO_UNITLESS)) {
237
		typ = do_af_suggest (list->data, epos, explicit);
Morten Welinder's avatar
Morten Welinder committed
238 239 240 241 242 243 244
		list = list->next;
	}
	return typ;
}

/* ------------------------------------------------------------------------- */

Jody Goldberg's avatar
Jody Goldberg committed
245
StyleFormat *
246
auto_style_format_suggest (GnmExpr const *expr, EvalPos const *epos)
Morten Welinder's avatar
Morten Welinder committed
247
{
248
	StyleFormat *explicit = NULL;
Morten Welinder's avatar
Morten Welinder committed
249 250

	g_return_val_if_fail (expr != NULL, NULL);
251
	g_return_val_if_fail (epos != NULL, NULL);
Morten Welinder's avatar
Morten Welinder committed
252

253
	switch (do_af_suggest (expr, epos, &explicit)) {
Morten Welinder's avatar
Morten Welinder committed
254 255 256
	case AF_EXPLICIT:
		break;

257
	case GNM_FUNC_AUTO_DATE: /* FIXME: any better idea?  */
258
		explicit = style_format_default_date ();
Morten Welinder's avatar
Morten Welinder committed
259 260
		break;

261
	case GNM_FUNC_AUTO_TIME: /* FIXME: any better idea?  */
262
		explicit = style_format_default_time ();
Morten Welinder's avatar
Morten Welinder committed
263 264
		break;

265
	case GNM_FUNC_AUTO_PERCENT: /* FIXME: any better idea?  */
266
		explicit = style_format_default_percentage ();
Morten Welinder's avatar
Morten Welinder committed
267 268
		break;

269
	case GNM_FUNC_AUTO_MONETARY: /* FIXME: any better idea?  */
270
		explicit = style_format_default_money ();
Morten Welinder's avatar
Morten Welinder committed
271 272
		break;

273 274
	case GNM_FUNC_AUTO_FIRST:
	case GNM_FUNC_AUTO_SECOND:
Morten Welinder's avatar
Morten Welinder committed
275 276 277 278 279 280
		g_assert_not_reached ();

	default:
		explicit = NULL;
	}

281 282 283
	if (explicit)
		style_format_ref (explicit);

Morten Welinder's avatar
Morten Welinder committed
284 285
	return explicit;
}