1 
2 /*
3  * sheet-filter.c: support for 'auto-filters'
4  *
5  * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) version 3.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
20  * USA
21  */
22 #include <gnumeric-config.h>
23 #include <libgnumeric.h>
24 #include <sheet-filter.h>
25 #include <sheet-filter-combo.h>
26 
27 #include <workbook.h>
28 #include <sheet.h>
29 #include <sheet-private.h>
30 #include <sheet-view.h>
31 #include <sheet-control.h>
32 #include <cell.h>
33 #include <expr.h>
34 #include <value.h>
35 #include <gnm-format.h>
36 #include <ranges.h>
37 #include <number-match.h>
38 #include <gutils.h>
39 #include <sheet-object.h>
40 #include <widgets/gnm-filter-combo-view.h>
41 #include <widgets/gnm-cell-combo-view.h>
42 #include <gsf/gsf-impl-utils.h>
43 
44 #include <glib/gi18n-lib.h>
45 #include <stdlib.h>
46 #include <string.h>
47 
48 static gboolean
gnm_filter_op_needs_value(GnmFilterOp op)49 gnm_filter_op_needs_value (GnmFilterOp op)
50 {
51 	g_return_val_if_fail (op != GNM_FILTER_UNUSED, FALSE);
52 
53 	switch (op & GNM_FILTER_OP_TYPE_MASK) {
54 	case GNM_FILTER_OP_TYPE_OP:
55 	case GNM_FILTER_OP_TYPE_BUCKETS:
56 	case GNM_FILTER_OP_TYPE_MATCH:
57 		return TRUE;
58 	default:
59 		g_assert_not_reached ();
60 	case GNM_FILTER_OP_TYPE_BLANKS:
61 	case GNM_FILTER_OP_TYPE_AVERAGE:
62 	case GNM_FILTER_OP_TYPE_STDDEV:
63 		return FALSE;
64 	}
65 }
66 
67 
68 /**
69  * gnm_filter_condition_new_single:
70  * @op: #GnmFilterOp
71  * @v: #GnmValue
72  *
73  * Create a new condition with 1 value.
74  * Absorbs the reference to @v.
75  **/
76 GnmFilterCondition *
gnm_filter_condition_new_single(GnmFilterOp op,GnmValue * v)77 gnm_filter_condition_new_single (GnmFilterOp op, GnmValue *v)
78 {
79 	GnmFilterCondition *res;
80 
81 	g_return_val_if_fail ((v != NULL) == gnm_filter_op_needs_value (op),
82 			      (value_release (v), NULL));
83 
84 	res = g_new0 (GnmFilterCondition, 1);
85 	res->op[0] = op;	res->op[1] = GNM_FILTER_UNUSED;
86 	res->value[0] = v;
87 	return res;
88 }
89 
90 /**
91  * gnm_filter_condition_new_double:
92  * @op0: #GnmFilterOp
93  * @v0: #GnmValue
94  * @join_with_and:
95  * @op1: #GnmFilterOp
96  * @v1: #GnmValue
97  *
98  * Create a new condition with 2 value.
99  * Absorbs the reference to @v0 and @v1.
100  **/
101 GnmFilterCondition *
gnm_filter_condition_new_double(GnmFilterOp op0,GnmValue * v0,gboolean join_with_and,GnmFilterOp op1,GnmValue * v1)102 gnm_filter_condition_new_double (GnmFilterOp op0, GnmValue *v0,
103 				 gboolean join_with_and,
104 				 GnmFilterOp op1, GnmValue *v1)
105 {
106 	GnmFilterCondition *res;
107 
108 	g_return_val_if_fail ((v0 != NULL) == gnm_filter_op_needs_value (op0),
109 			      (value_release (v0), value_release (v1), NULL));
110 	g_return_val_if_fail ((v1 != NULL) == gnm_filter_op_needs_value (op1),
111 			      (value_release (v0), value_release (v1), NULL));
112 
113 	res = g_new0 (GnmFilterCondition, 1);
114 	res->op[0] = op0;	res->op[1] = op1;
115 	res->is_and = join_with_and;
116 	res->value[0] = v0;	res->value[1] = v1;
117 	return res;
118 }
119 
120 GnmFilterCondition *
gnm_filter_condition_new_bucket(gboolean top,gboolean absolute,gboolean rel_range,double n)121 gnm_filter_condition_new_bucket (gboolean top, gboolean absolute,
122 				 gboolean rel_range, double n)
123 {
124 	GnmFilterCondition *res = g_new0 (GnmFilterCondition, 1);
125 	res->op[0] = GNM_FILTER_OP_TOP_N | (top ? 0 : 1) |
126 		(absolute ? 0 : (rel_range ? 2 : 4));
127 	res->op[1] = GNM_FILTER_UNUSED;
128 	res->count = n;
129 	return res;
130 }
131 
132 GnmFilterCondition *
gnm_filter_condition_dup(GnmFilterCondition const * src)133 gnm_filter_condition_dup (GnmFilterCondition const *src)
134 {
135 	GnmFilterCondition *dst;
136 
137 	if (src == NULL)
138 		return NULL;
139 
140 	dst = g_new0 (GnmFilterCondition, 1);
141 	dst->op[0]    = src->op[0];
142 	dst->op[1]    = src->op[1];
143 	dst->is_and   = src->is_and;
144 	dst->count    = src->count;
145 	dst->value[0] = value_dup (src->value[0]);
146 	dst->value[1] = value_dup (src->value[1]);
147 	return dst;
148 }
149 
150 void
gnm_filter_condition_free(GnmFilterCondition * cond)151 gnm_filter_condition_free (GnmFilterCondition *cond)
152 {
153 	if (cond == NULL)
154 		return;
155 
156 	value_release (cond->value[0]);
157 	value_release (cond->value[1]);
158 	g_free (cond);
159 }
160 
161 GType
gnm_filter_condition_get_type(void)162 gnm_filter_condition_get_type (void)
163 {
164 	static GType t = 0;
165 
166 	if (t == 0) {
167 		t = g_boxed_type_register_static ("GnmFilterCondition",
168 			 (GBoxedCopyFunc)gnm_filter_condition_dup,
169 			 (GBoxedFreeFunc)gnm_filter_condition_free);
170 	}
171 	return t;
172 }
173 
174 /*****************************************************************************/
175 
176 typedef struct  {
177 	GnmFilterCondition const *cond;
178 	GnmValue		 *val[2];
179 	GnmValue		 *alt_val[2];
180 	GORegexp		  regexp[2];
181 	Sheet			 *target_sheet; /* not necessarilly the src */
182 } FilterExpr;
183 
184 static void
filter_expr_init(FilterExpr * fexpr,unsigned i,GnmFilterCondition const * cond,GnmFilter const * filter)185 filter_expr_init (FilterExpr *fexpr, unsigned i,
186 		  GnmFilterCondition const *cond,
187 		  GnmFilter const *filter)
188 {
189 	GnmValue *tmp = cond->value[i];
190 
191 	if (tmp && VALUE_IS_STRING (tmp)) {
192 		GnmFilterOp op = cond->op[i];
193 		char const *str = value_peek_string (tmp);
194 		GODateConventions const *date_conv =
195 			sheet_date_conv (filter->sheet);
196 
197 		if ((op == GNM_FILTER_OP_EQUAL || op == GNM_FILTER_OP_NOT_EQUAL) &&
198 		    gnm_regcomp_XL (fexpr->regexp + i, str, GO_REG_ICASE, TRUE, TRUE) == GO_REG_OK) {
199 			/* FIXME: Do we want to anchor at the end above?  */
200 			fexpr->val[i] = NULL;
201 			return;
202 		}
203 
204 		fexpr->val[i] = format_match_number (str, NULL, date_conv);
205 		if (fexpr->val[i] != NULL)
206 			return;
207 	}
208 	fexpr->val[i] = value_dup (tmp);
209 }
210 
211 static void
filter_expr_release(FilterExpr * fexpr,unsigned i)212 filter_expr_release (FilterExpr *fexpr, unsigned i)
213 {
214 	if (fexpr->val[i] == NULL)
215 		go_regfree (fexpr->regexp + i);
216 	else
217 		value_release (fexpr->val[i]);
218 }
219 
220 static char *
filter_cell_contents(GnmCell * cell)221 filter_cell_contents (GnmCell *cell)
222 {
223 	GOFormat const *format = gnm_cell_get_format (cell);
224 	GODateConventions const *date_conv =
225 		sheet_date_conv (cell->base.sheet);
226 	return format_value (format, cell->value, -1, date_conv);
227 }
228 
229 static gboolean
filter_expr_eval(GnmFilterOp op,GnmValue const * src,GORegexp const * regexp,GnmCell * cell)230 filter_expr_eval (GnmFilterOp op, GnmValue const *src, GORegexp const *regexp,
231 		  GnmCell *cell)
232 {
233 	GnmValue *target = cell->value;
234 	GnmValDiff cmp;
235 	GnmValue *fake_val = NULL;
236 
237 	if (src == NULL) {
238 		char *str = filter_cell_contents (cell);
239 		GORegmatch rm;
240 		int res = go_regexec (regexp, str, 1, &rm, 0);
241 		gboolean whole = (res == GO_REG_OK && rm.rm_so == 0 && str[rm.rm_eo] == 0);
242 
243 		g_free (str);
244 
245 		switch (res) {
246 		case GO_REG_OK:
247 			if (whole)
248 				return op == GNM_FILTER_OP_EQUAL;
249 			/* fall through */
250 
251 		case GO_REG_NOMATCH:
252 			return op == GNM_FILTER_OP_NOT_EQUAL;
253 
254 		default:
255 			g_warning ("Unexpected regexec result");
256 			return FALSE;
257 		}
258 	}
259 
260 	if (VALUE_IS_STRING (target) && VALUE_IS_NUMBER (src)) {
261 		GODateConventions const *date_conv =
262 			sheet_date_conv (cell->base.sheet);
263 		char *str = format_value (NULL, src, -1, date_conv);
264 		fake_val = value_new_string_nocopy (str);
265 		src = fake_val;
266 	}
267 
268 	cmp = value_compare (target, src, FALSE);
269 	value_release (fake_val);
270 
271 	switch (op) {
272 	case GNM_FILTER_OP_EQUAL     : return cmp == IS_EQUAL;
273 	case GNM_FILTER_OP_NOT_EQUAL : return cmp != IS_EQUAL;
274 	case GNM_FILTER_OP_GTE	: if (cmp == IS_EQUAL) return TRUE; /* fall */
275 	case GNM_FILTER_OP_GT	: return cmp == IS_GREATER;
276 	case GNM_FILTER_OP_LTE	: if (cmp == IS_EQUAL) return TRUE; /* fall */
277 	case GNM_FILTER_OP_LT	: return cmp == IS_LESS;
278 	default:
279 		g_warning ("Huh?");
280 		return FALSE;
281 	}
282 }
283 
284 static GnmValue *
cb_filter_expr(GnmCellIter const * iter,FilterExpr const * fexpr)285 cb_filter_expr (GnmCellIter const *iter, FilterExpr const *fexpr)
286 {
287 	if (iter->cell != NULL) {
288 		unsigned int ui;
289 
290 		for (ui = 0; ui < G_N_ELEMENTS (fexpr->cond->op); ui++) {
291 			gboolean res;
292 
293 			if (fexpr->cond->op[ui] == GNM_FILTER_UNUSED)
294 				continue;
295 
296 			res = filter_expr_eval (fexpr->cond->op[ui],
297 						fexpr->val[ui],
298 						fexpr->regexp + ui,
299 						iter->cell);
300 			if (fexpr->cond->is_and && !res)
301 				goto nope;   /* AND(...,FALSE,...) */
302 			if (res && !fexpr->cond->is_and)
303 				return NULL;   /* OR(...,TRUE,...) */
304 		}
305 
306 		if (fexpr->cond->is_and)
307 			return NULL;  /* AND(TRUE,...,TRUE) */
308 	}
309 
310  nope:
311 	colrow_set_visibility (fexpr->target_sheet, FALSE, FALSE,
312 		iter->pp.eval.row, iter->pp.eval.row);
313 	return NULL;
314 }
315 
316 /*****************************************************************************/
317 
318 static GnmValue *
cb_filter_non_blanks(GnmCellIter const * iter,Sheet * target_sheet)319 cb_filter_non_blanks (GnmCellIter const *iter, Sheet *target_sheet)
320 {
321 	if (gnm_cell_is_blank (iter->cell))
322 		colrow_set_visibility (target_sheet, FALSE, FALSE,
323 			iter->pp.eval.row, iter->pp.eval.row);
324 	return NULL;
325 }
326 
327 static GnmValue *
cb_filter_blanks(GnmCellIter const * iter,Sheet * target_sheet)328 cb_filter_blanks (GnmCellIter const *iter, Sheet *target_sheet)
329 {
330 	if (!gnm_cell_is_blank (iter->cell))
331 		colrow_set_visibility (target_sheet, FALSE, FALSE,
332 			iter->pp.eval.row, iter->pp.eval.row);
333 	return NULL;
334 }
335 
336 /*****************************************************************************/
337 
338 typedef struct {
339 	unsigned count;
340 	unsigned elements;
341 	gboolean find_max;
342 	GnmValue const **vals;
343 	Sheet	*target_sheet;
344 } FilterItems;
345 
346 static GnmValue *
cb_filter_find_items(GnmCellIter const * iter,FilterItems * data)347 cb_filter_find_items (GnmCellIter const *iter, FilterItems *data)
348 {
349 	GnmValue const *v = iter->cell->value;
350 	if (data->elements >= data->count) {
351 		unsigned j, i = data->elements;
352 		GnmValDiff const cond = data->find_max ? IS_GREATER : IS_LESS;
353 		while (i-- > 0)
354 			if (value_compare (v, data->vals[i], TRUE) == cond) {
355 				for (j = 0; j < i ; j++)
356 					data->vals[j] = data->vals[j+1];
357 				data->vals[i] = v;
358 				break;
359 			}
360 	} else {
361 		data->vals [data->elements++] = v;
362 		if (data->elements == data->count) {
363 			qsort (data->vals, data->elements,
364 			       sizeof (GnmValue *),
365 			       data->find_max ? value_cmp : value_cmp_reverse);
366 		}
367 	}
368 	return NULL;
369 }
370 
371 static GnmValue *
cb_hide_unwanted_items(GnmCellIter const * iter,FilterItems const * data)372 cb_hide_unwanted_items (GnmCellIter const *iter, FilterItems const *data)
373 {
374 	if (iter->cell != NULL) {
375 		int i = data->elements;
376 		GnmValue const *v = iter->cell->value;
377 
378 		while (i-- > 0)
379 			if (data->vals[i] == v)
380 				return NULL;
381 	}
382 	colrow_set_visibility (data->target_sheet, FALSE, FALSE,
383 		iter->pp.eval.row, iter->pp.eval.row);
384 	return NULL;
385 }
386 
387 /*****************************************************************************/
388 
389 typedef struct {
390 	gboolean	 initialized, find_max;
391 	gnm_float	 low, high;
392 	Sheet		*target_sheet;
393 } FilterPercentage;
394 
395 static GnmValue *
cb_filter_find_percentage(GnmCellIter const * iter,FilterPercentage * data)396 cb_filter_find_percentage (GnmCellIter const *iter, FilterPercentage *data)
397 {
398 	if (VALUE_IS_NUMBER (iter->cell->value)) {
399 		gnm_float const v = value_get_as_float (iter->cell->value);
400 
401 		if (data->initialized) {
402 			if (data->low > v)
403 				data->low = v;
404 			else if (data->high < v)
405 				data->high = v;
406 		} else {
407 			data->initialized = TRUE;
408 			data->low = data->high = v;
409 		}
410 	}
411 	return NULL;
412 }
413 
414 static GnmValue *
cb_hide_unwanted_percentage(GnmCellIter const * iter,FilterPercentage const * data)415 cb_hide_unwanted_percentage (GnmCellIter const *iter,
416 			     FilterPercentage const *data)
417 {
418 	if (iter->cell != NULL && VALUE_IS_NUMBER (iter->cell->value)) {
419 		gnm_float const v = value_get_as_float (iter->cell->value);
420 		if (data->find_max) {
421 			if (v >= data->high)
422 				return NULL;
423 		} else {
424 			if (v <= data->low)
425 				return NULL;
426 		}
427 	}
428 	colrow_set_visibility (data->target_sheet, FALSE, FALSE,
429 		iter->pp.eval.row, iter->pp.eval.row);
430 	return NULL;
431 }
432 /*****************************************************************************/
433 
434 int
gnm_filter_combo_index(GnmFilterCombo * fcombo)435 gnm_filter_combo_index (GnmFilterCombo *fcombo)
436 {
437 	g_return_val_if_fail (GNM_IS_FILTER_COMBO (fcombo), 0);
438 
439 	return (sheet_object_get_range (GNM_SO (fcombo))->start.col -
440 		fcombo->filter->r.start.col);
441 }
442 
443 
444 /**
445  * gnm_filter_combo_apply:
446  * @fcombo: #GnmFilterCombo
447  * @target_sheet: @Sheet
448  *
449  **/
450 void
gnm_filter_combo_apply(GnmFilterCombo * fcombo,Sheet * target_sheet)451 gnm_filter_combo_apply (GnmFilterCombo *fcombo, Sheet *target_sheet)
452 {
453 	GnmFilter const *filter;
454 	GnmFilterCondition const *cond;
455 	int col, start_row, end_row;
456 	CellIterFlags iter_flags = CELL_ITER_IGNORE_HIDDEN;
457 
458 	g_return_if_fail (GNM_IS_FILTER_COMBO (fcombo));
459 
460 	filter = fcombo->filter;
461 	cond = fcombo->cond;
462 	col = sheet_object_get_range (GNM_SO (fcombo))->start.col;
463 	start_row = filter->r.start.row + 1;
464 	end_row = filter->r.end.row;
465 
466 	if (start_row > end_row ||
467 	    cond == NULL ||
468 	    cond->op[0] == GNM_FILTER_UNUSED)
469 		return;
470 
471 	/*
472 	 * For the combo we filter a temporary sheet using the data from
473 	 * filter->sheet and need to include everything from the source,
474 	 * because it has a different set of conditions
475 	 */
476 	if (target_sheet != filter->sheet)
477 		iter_flags = CELL_ITER_ALL;
478 
479 	if (0x10 >= (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
480 		FilterExpr data;
481 		data.cond = cond;
482 		data.target_sheet = target_sheet;
483 		filter_expr_init (&data, 0, cond, filter);
484 		if (cond->op[1] != GNM_FILTER_UNUSED)
485 			filter_expr_init (&data, 1, cond, filter);
486 
487 		sheet_foreach_cell_in_region (filter->sheet,
488 			iter_flags,
489 			col, start_row, col, end_row,
490 			(CellIterFunc) cb_filter_expr, &data);
491 
492 		filter_expr_release (&data, 0);
493 		if (cond->op[1] != GNM_FILTER_UNUSED)
494 			filter_expr_release (&data, 1);
495 	} else if (cond->op[0] == GNM_FILTER_OP_BLANKS)
496 		sheet_foreach_cell_in_region (filter->sheet,
497 			CELL_ITER_IGNORE_HIDDEN,
498 			col, start_row, col, end_row,
499 			(CellIterFunc) cb_filter_blanks, target_sheet);
500 	else if (cond->op[0] == GNM_FILTER_OP_NON_BLANKS)
501 		sheet_foreach_cell_in_region (filter->sheet,
502 			CELL_ITER_IGNORE_HIDDEN,
503 			col, start_row, col, end_row,
504 			(CellIterFunc) cb_filter_non_blanks, target_sheet);
505 	else if (0x30 == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
506 		if (cond->op[0] & GNM_FILTER_OP_PERCENT_MASK) { /* relative */
507 			if (cond->op[0] & GNM_FILTER_OP_REL_N_MASK) {
508 				FilterItems data;
509 				data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
510 				data.elements    = 0;
511 				data.count  = 0.5 + cond->count * (end_row - start_row + 1) /100.;
512 				if (data.count < 1)
513 					data.count = 1;
514 				data.vals   = g_new (GnmValue const *, data.count);
515 				sheet_foreach_cell_in_region (filter->sheet,
516 							     CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
517 							     col, start_row, col, end_row,
518 							     (CellIterFunc) cb_filter_find_items, &data);
519 				data.target_sheet = target_sheet;
520 				sheet_foreach_cell_in_region (filter->sheet,
521 							     CELL_ITER_IGNORE_HIDDEN,
522 							     col, start_row, col, end_row,
523 							     (CellIterFunc) cb_hide_unwanted_items, &data);
524 				g_free (data.vals);
525 			} else {
526 				FilterPercentage data;
527 				gnm_float	 offset;
528 
529 				data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
530 				data.initialized = FALSE;
531 				sheet_foreach_cell_in_region (filter->sheet,
532 							     CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
533 							     col, start_row, col, end_row,
534 							     (CellIterFunc) cb_filter_find_percentage, &data);
535 				offset = (data.high - data.low) * cond->count / 100.;
536 				data.high -= offset;
537 				data.low  += offset;
538 				data.target_sheet = target_sheet;
539 				sheet_foreach_cell_in_region (filter->sheet,
540 							     CELL_ITER_IGNORE_HIDDEN,
541 							     col, start_row, col, end_row,
542 							     (CellIterFunc) cb_hide_unwanted_percentage, &data);
543 			}
544 		} else { /* absolute */
545 			FilterItems data;
546 			data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
547 			data.elements    = 0;
548 			data.count  = cond->count;
549 			data.vals   = g_new (GnmValue const *, data.count);
550 
551 			sheet_foreach_cell_in_region (filter->sheet,
552 				CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
553 				col, start_row, col, end_row,
554 				(CellIterFunc) cb_filter_find_items, &data);
555 			data.target_sheet = target_sheet;
556 			sheet_foreach_cell_in_region (filter->sheet,
557 				CELL_ITER_IGNORE_HIDDEN,
558 				col, start_row, col, end_row,
559 				(CellIterFunc) cb_hide_unwanted_items, &data);
560 			g_free (data.vals);
561 		}
562 	} else
563 		g_warning ("Invalid operator %d", cond->op[0]);
564 }
565 
566 enum {
567 	COND_CHANGED,
568 	LAST_SIGNAL
569 };
570 
571 static guint signals [LAST_SIGNAL] = { 0 };
572 
573 typedef struct {
574 	SheetObjectClass parent;
575 
576 	void (*cond_changed) (GnmFilterCombo *);
577 } GnmFilterComboClass;
578 
579 static void
gnm_filter_combo_finalize(GObject * object)580 gnm_filter_combo_finalize (GObject *object)
581 {
582 	GnmFilterCombo *fcombo = GNM_FILTER_COMBO (object);
583 	GObjectClass *parent;
584 
585 	gnm_filter_condition_free (fcombo->cond);
586 	fcombo->cond = NULL;
587 
588 	parent = g_type_class_peek (GNM_SO_TYPE);
589 	parent->finalize (object);
590 }
591 
592 static void
gnm_filter_combo_init(SheetObject * so)593 gnm_filter_combo_init (SheetObject *so)
594 {
595 	/* keep the arrows from wandering with their cells */
596 	so->flags &= ~SHEET_OBJECT_MOVE_WITH_CELLS;
597 }
598 static SheetObjectView *
gnm_filter_combo_view_new(SheetObject * so,SheetObjectViewContainer * container)599 gnm_filter_combo_view_new (SheetObject *so, SheetObjectViewContainer *container)
600 {
601 	return gnm_cell_combo_view_new (so,
602 		gnm_filter_combo_view_get_type (), container);
603 }
604 static void
gnm_filter_combo_class_init(GObjectClass * gobject_class)605 gnm_filter_combo_class_init (GObjectClass *gobject_class)
606 {
607 	SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
608 
609 	/* Object class method overrides */
610 	gobject_class->finalize = gnm_filter_combo_finalize;
611 
612 	/* SheetObject class method overrides */
613 	so_class->new_view	= gnm_filter_combo_view_new;
614 	so_class->write_xml_sax = NULL;
615 	so_class->prep_sax_parser = NULL;
616 	so_class->copy          = NULL;
617 
618 	signals[COND_CHANGED] = g_signal_new ("cond-changed",
619 		 GNM_FILTER_COMBO_TYPE,
620 		 G_SIGNAL_RUN_LAST,
621 		 G_STRUCT_OFFSET (GnmFilterComboClass, cond_changed),
622 		 NULL, NULL,
623 		 g_cclosure_marshal_VOID__VOID,
624 		 G_TYPE_NONE, 0);
625 }
626 
GSF_CLASS(GnmFilterCombo,gnm_filter_combo,gnm_filter_combo_class_init,gnm_filter_combo_init,GNM_SO_TYPE)627 GSF_CLASS (GnmFilterCombo, gnm_filter_combo,
628 	   gnm_filter_combo_class_init, gnm_filter_combo_init,
629 	   GNM_SO_TYPE)
630 
631 /*************************************************************************/
632 
633 static void
634 gnm_filter_add_field (GnmFilter *filter, int i)
635 {
636 	/* pretend to fill the cell, then clip the X start later */
637 	static double const a_offsets[4] = { .0, .0, 1., 1. };
638 	GnmRange tmp;
639 	SheetObjectAnchor anchor;
640 	GnmFilterCombo *fcombo = g_object_new (GNM_FILTER_COMBO_TYPE, NULL);
641 
642 	fcombo->filter = filter;
643 	tmp.start.row = tmp.end.row = filter->r.start.row;
644 	tmp.start.col = tmp.end.col = filter->r.start.col + i;
645 	sheet_object_anchor_init (&anchor, &tmp, a_offsets,
646 		GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
647 	sheet_object_set_anchor (GNM_SO (fcombo), &anchor);
648 	sheet_object_set_sheet (GNM_SO (fcombo), filter->sheet);
649 
650 #ifdef HAVE_G_PTR_ARRAY_INSERT
651 	g_ptr_array_insert (filter->fields, i, fcombo);
652 #else
653 	go_ptr_array_insert (filter->fields, fcombo, i);
654 #endif
655 	/* We hold a reference to fcombo */
656 }
657 
658 void
gnm_filter_attach(GnmFilter * filter,Sheet * sheet)659 gnm_filter_attach (GnmFilter *filter, Sheet *sheet)
660 {
661 	int i;
662 
663 	g_return_if_fail (filter != NULL);
664 	g_return_if_fail (filter->sheet == NULL);
665 	g_return_if_fail (IS_SHEET (sheet));
666 
667 	gnm_filter_ref (filter);
668 
669 	filter->sheet = sheet;
670 	sheet->filters = g_slist_prepend (sheet->filters, filter);
671 	sheet->priv->filters_changed = TRUE;
672 
673 	for (i = 0 ; i < range_width (&(filter->r)); i++)
674 		gnm_filter_add_field (filter, i);
675 }
676 
677 
678 /**
679  * gnm_filter_new:
680  * @sheet: #Sheet for which to create the filter.
681  * @r: #GnmRange that the filter covers.
682  * @attach: whether to attach the filter.
683  *
684  * Returns: (transfer full): A new filter.
685  **/
686 GnmFilter *
gnm_filter_new(Sheet * sheet,GnmRange const * r,gboolean attach)687 gnm_filter_new (Sheet *sheet, GnmRange const *r, gboolean attach)
688 {
689 	GnmFilter	*filter;
690 
691 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
692 	g_return_val_if_fail (r != NULL, NULL);
693 
694 	filter = g_new0 (GnmFilter, 1);
695 
696 	filter->is_active = FALSE;
697 	filter->r = *r;
698 	filter->fields = g_ptr_array_new ();
699 
700 	if (attach) {
701 		/* This creates the initial ref.  */
702 		gnm_filter_attach (filter, sheet);
703 	} else {
704 		gnm_filter_ref (filter);
705 	}
706 
707 	return filter;
708 }
709 
710 /**
711  * gnm_filter_dup:
712  * @src: #GnmFilter
713  * @sheet: #Sheet
714  *
715  * Duplicate @src into @sheet
716  **/
717 GnmFilter *
gnm_filter_dup(GnmFilter const * src,Sheet * sheet)718 gnm_filter_dup (GnmFilter const *src, Sheet *sheet)
719 {
720 	int i;
721 	GnmFilter *dst;
722 
723 	g_return_val_if_fail (src != NULL, NULL);
724 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
725 
726 	dst = g_new0 (GnmFilter, 1);
727 
728 	dst->is_active = src->is_active;
729 	dst->r = src->r;
730 	dst->fields = g_ptr_array_new ();
731 
732 	/* This creates the initial ref.  */
733 	gnm_filter_attach (dst, sheet);
734 
735 	for (i = 0 ; i < range_width (&dst->r); i++) {
736 		gnm_filter_add_field (dst, i);
737 		gnm_filter_set_condition (dst, i,
738 			gnm_filter_condition_dup (
739 				gnm_filter_get_condition (src, i)),
740 			FALSE);
741 	}
742 
743 	return dst;
744 }
745 
746 GnmFilter *
gnm_filter_ref(GnmFilter * filter)747 gnm_filter_ref (GnmFilter *filter)
748 {
749 	g_return_val_if_fail (filter != NULL, NULL);
750 	filter->ref_count++;
751 	return filter;
752 }
753 
754 void
gnm_filter_unref(GnmFilter * filter)755 gnm_filter_unref (GnmFilter *filter)
756 {
757 	g_return_if_fail (filter != NULL);
758 
759 	filter->ref_count--;
760 	if (filter->ref_count > 0)
761 		return;
762 
763 	g_ptr_array_free (filter->fields, TRUE);
764 	g_free (filter);
765 }
766 
767 GType
gnm_filter_get_type(void)768 gnm_filter_get_type (void)
769 {
770 	static GType t = 0;
771 
772 	if (t == 0) {
773 		t = g_boxed_type_register_static ("GnmFilter",
774 			 (GBoxedCopyFunc)gnm_filter_ref,
775 			 (GBoxedFreeFunc)gnm_filter_unref);
776 	}
777 	return t;
778 }
779 
780 void
gnm_filter_remove(GnmFilter * filter)781 gnm_filter_remove (GnmFilter *filter)
782 {
783 	Sheet *sheet;
784 	int i;
785 
786 	g_return_if_fail (filter != NULL);
787 	g_return_if_fail (filter->sheet != NULL);
788 
789 	sheet = filter->sheet;
790 	sheet->priv->filters_changed = TRUE;
791 	sheet->filters = g_slist_remove (sheet->filters, filter);
792 	for (i = filter->r.start.row; ++i <= filter->r.end.row ; ) {
793 		ColRowInfo *ri = sheet_row_get (sheet, i);
794 		if (ri != NULL) {
795 			ri->in_filter = FALSE;
796 			colrow_set_visibility (sheet, FALSE, TRUE, i, i);
797 		}
798 	}
799 	filter->sheet = NULL;
800 
801 	SHEET_FOREACH_CONTROL
802 		(sheet, view, sc, sc_freeze_object_view (sc, TRUE););
803 
804 	for (i = filter->fields->len; i-- > 0; ) {
805 		SheetObject *so = g_ptr_array_index (filter->fields, i);
806 		sheet_object_clear_sheet (so);
807 		g_object_unref (so);
808 	}
809 	g_ptr_array_set_size (filter->fields, 0);
810 
811 	SHEET_FOREACH_CONTROL
812 		(sheet, view, sc, sc_freeze_object_view (sc, FALSE););
813 }
814 
815 /**
816  * gnm_filter_get_condition:
817  * @filter: #GnmFilter
818  * @i: zero-based index
819  *
820  * Returns: (transfer none): the @i'th condition of @filter
821  **/
822 GnmFilterCondition const *
gnm_filter_get_condition(GnmFilter const * filter,unsigned i)823 gnm_filter_get_condition (GnmFilter const *filter, unsigned i)
824 {
825 	GnmFilterCombo *fcombo;
826 
827 	g_return_val_if_fail (filter != NULL, NULL);
828 	g_return_val_if_fail (i < filter->fields->len, NULL);
829 
830 	fcombo = g_ptr_array_index (filter->fields, i);
831 	return fcombo->cond;
832 }
833 
834 /**
835  * gnm_filter_reapply:
836  * @filter: #GnmFilter
837  *
838  * Reapplies @filter after changes.
839  **/
840 void
gnm_filter_reapply(GnmFilter * filter)841 gnm_filter_reapply (GnmFilter *filter)
842 {
843 	unsigned i;
844 
845 	colrow_set_visibility (filter->sheet, FALSE, TRUE,
846 			       filter->r.start.row + 1, filter->r.end.row);
847 	for (i = 0 ; i < filter->fields->len ; i++)
848 		gnm_filter_combo_apply (g_ptr_array_index (filter->fields, i),
849 					filter->sheet);
850 }
851 
852 static void
gnm_filter_update_active(GnmFilter * filter)853 gnm_filter_update_active (GnmFilter *filter)
854 {
855 	unsigned i;
856 	gboolean old_active = filter->is_active;
857 
858 	filter->is_active = FALSE;
859 	for (i = 0 ; i < filter->fields->len ; i++) {
860 		GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, i);
861 		if (fcombo->cond != NULL) {
862 			filter->is_active = TRUE;
863 			break;
864 		}
865 	}
866 
867 	if (filter->is_active != old_active) {
868 		int r;
869 		for (r = filter->r.start.row; ++r <= filter->r.end.row ; ) {
870 			ColRowInfo *ri = sheet_row_fetch (filter->sheet, r);
871 			ri->in_filter = filter->is_active;
872 		}
873 	}
874 }
875 
876 
877 /**
878  * gnm_filter_set_condition:
879  * @filter:
880  * @i:
881  * @cond: #GnmFilterCondition
882  * @apply:
883  *
884  * Change the @i-th condition of @filter to @cond.  If @apply is
885  * %TRUE, @filter is used to set the visibility of the rows in @filter::sheet
886  *
887  * Absorbs the reference to @cond.
888  **/
889 void
gnm_filter_set_condition(GnmFilter * filter,unsigned i,GnmFilterCondition * cond,gboolean apply)890 gnm_filter_set_condition (GnmFilter *filter, unsigned i,
891 			  GnmFilterCondition *cond,
892 			  gboolean apply)
893 {
894 	GnmFilterCombo *fcombo;
895 	gboolean existing_cond = FALSE;
896 
897 	g_return_if_fail (filter != NULL);
898 	g_return_if_fail (i < filter->fields->len);
899 
900 	fcombo = g_ptr_array_index (filter->fields, i);
901 
902 	if (fcombo->cond != NULL) {
903 		existing_cond = TRUE;
904 		gnm_filter_condition_free (fcombo->cond);
905 	}
906 	fcombo->cond = cond;
907 	g_signal_emit (G_OBJECT (fcombo), signals [COND_CHANGED], 0);
908 
909 	if (apply) {
910 		/* if there was an existing cond then we need to do
911 		 * redo the whole filter.
912 		 * This is because we do not record what elements this
913 		 * particular field filtered
914 		 */
915 		if (existing_cond)
916 			gnm_filter_reapply (filter);
917 		else
918 			/* When adding a new cond all we need to do is
919 			 * apply that filter */
920 			gnm_filter_combo_apply (fcombo, filter->sheet);
921 	}
922 
923 	gnm_filter_update_active (filter);
924 }
925 
926 /**
927  * gnm_filter_overlaps_range:
928  * @filter: #GnmFilter
929  * @r: #GnmRange
930  *
931  * Returns: %TRUE if @filter overlaps @r.
932  **/
933 static gboolean
gnm_filter_overlaps_range(GnmFilter const * filter,GnmRange const * r)934 gnm_filter_overlaps_range (GnmFilter const *filter, GnmRange const *r)
935 {
936 	g_return_val_if_fail (filter != NULL, FALSE);
937 	g_return_val_if_fail (r != NULL, FALSE);
938 
939 	return range_overlap (&filter->r, r);
940 }
941 
942 /*************************************************************************/
943 
944 /**
945  * gnm_sheet_filter_at_pos:
946  * @sheet: #Sheet
947  * @pos: location to query
948  *
949  * Returns: (transfer none) (nullable): #GnmFilter covering @pos.
950  **/
951 GnmFilter *
gnm_sheet_filter_at_pos(Sheet const * sheet,GnmCellPos const * pos)952 gnm_sheet_filter_at_pos (Sheet const *sheet, GnmCellPos const *pos)
953 {
954 	GSList *ptr;
955 	GnmRange r;
956 
957 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
958 	g_return_val_if_fail (NULL != pos, NULL);
959 
960 	range_init_cellpos (&r, pos);
961 	for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
962 		if (gnm_filter_overlaps_range (ptr->data, &r))
963 			return ptr->data;
964 
965 	return NULL;
966 }
967 
968 /**
969  * gnm_sheet_filter_intersect_rows:
970  * @sheet: #Sheet
971  * @from: starting row number
972  * @to: ending row number
973  *
974  * Returns: (transfer none) (nullable): the #GnmFilter, if any, that
975  * intersects the rows @from to @to.
976  **/
977 GnmFilter *
gnm_sheet_filter_intersect_rows(Sheet const * sheet,int from,int to)978 gnm_sheet_filter_intersect_rows (Sheet const *sheet, int from, int to)
979 {
980 	GSList *ptr;
981 	GnmRange r;
982 
983 	g_return_val_if_fail (IS_SHEET (sheet), NULL);
984 
985 	range_init_rows (&r, sheet, from, to);
986 	for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
987 		if (gnm_filter_overlaps_range (ptr->data, &r))
988 			return ptr->data;
989 
990 	return NULL;
991 }
992 
993 /**
994  * gnm_sheet_filter_can_be_extended:
995  *
996  * Returns: (transfer full): #GnmRange
997  */
998 GnmRange *
gnm_sheet_filter_can_be_extended(G_GNUC_UNUSED Sheet const * sheet,GnmFilter const * f,GnmRange const * r)999 gnm_sheet_filter_can_be_extended (G_GNUC_UNUSED Sheet const *sheet,
1000 				  GnmFilter const *f, GnmRange const *r)
1001 {
1002 	if (r->start.row < f->r.start.row || r->end.row > f->r.end.row)
1003 		return NULL;
1004 	if ((r->end.col > f->r.end.col) ||
1005 	    (r->start.col < f->r.start.col)) {
1006 		GnmRange *res = g_new (GnmRange, 1);
1007 		*res = range_union (&f->r, r);
1008 		return res;
1009 	}
1010 	return NULL;
1011 }
1012 
1013 
1014 /*************************************************************************/
1015 
1016 struct cb_remove_col_undo {
1017 	unsigned col;
1018 	GnmFilterCondition *cond;
1019 };
1020 
1021 static void
cb_remove_col_undo_free(struct cb_remove_col_undo * r)1022 cb_remove_col_undo_free (struct cb_remove_col_undo *r)
1023 {
1024 	gnm_filter_condition_free (r->cond);
1025 	g_free (r);
1026 }
1027 
1028 static void
cb_remove_col_undo(GnmFilter * filter,struct cb_remove_col_undo * r,G_GNUC_UNUSED gpointer data)1029 cb_remove_col_undo (GnmFilter *filter, struct cb_remove_col_undo *r,
1030 		    G_GNUC_UNUSED gpointer data)
1031 {
1032 	while (filter->fields->len <= r->col)
1033 		gnm_filter_add_field (filter, filter->fields->len);
1034 	gnm_filter_set_condition (filter, r->col,
1035 				  gnm_filter_condition_dup (r->cond),
1036 				  FALSE);
1037 }
1038 
1039 static void
remove_col(GnmFilter * filter,unsigned col,GOUndo ** pundo)1040 remove_col (GnmFilter *filter, unsigned col, GOUndo **pundo)
1041 {
1042 	GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, col);
1043 	if (pundo) {
1044 		struct cb_remove_col_undo *r = g_new (struct cb_remove_col_undo, 1);
1045 		GOUndo *u;
1046 
1047 		r->col = col;
1048 		r->cond = gnm_filter_condition_dup (fcombo->cond);
1049 		u = go_undo_binary_new
1050 			(gnm_filter_ref (filter), r,
1051 			 (GOUndoBinaryFunc)cb_remove_col_undo,
1052 			 (GFreeFunc)gnm_filter_unref,
1053 			 (GFreeFunc)cb_remove_col_undo_free);
1054 		*pundo = go_undo_combine (*pundo, u);
1055 	}
1056 	g_object_unref (fcombo);
1057 	g_ptr_array_remove_index (filter->fields, col);
1058 }
1059 
1060 static void
gnm_filter_set_range(GnmFilter * filter,GnmRange * r)1061 gnm_filter_set_range (GnmFilter *filter, GnmRange *r)
1062 {
1063 	GnmRange old_r = filter->r;
1064 	int i;
1065 	int start = r->start.col;
1066 
1067 	filter->r = *r;
1068 	for (i = start; i < old_r.start.col; i++)
1069 		gnm_filter_add_field (filter, i - start);
1070 	for (i = old_r.end.col + 1; i <= r->end.col; i++)
1071 		gnm_filter_add_field (filter, i - start);
1072 }
1073 
1074 /**
1075  * gnm_sheet_filter_insdel_colrow:
1076  * @sheet: #Sheet
1077  * @is_cols: %TRUE for columns, %FALSE for rows.
1078  * @is_insert: %TRUE for insert, %FALSE for delete.
1079  * @start: Starting column or row.
1080  * @count: Number of columns or rows.
1081  * @pundo: (out) (optional): location to store undo closures.
1082  *
1083  * Adjust filters as necessary to handle col/row insertions and deletions
1084  **/
1085 void
gnm_sheet_filter_insdel_colrow(Sheet * sheet,gboolean is_cols,gboolean is_insert,int start,int count,GOUndo ** pundo)1086 gnm_sheet_filter_insdel_colrow (Sheet *sheet,
1087 				gboolean is_cols, gboolean is_insert,
1088 				int start, int count,
1089 				GOUndo **pundo)
1090 {
1091 	GSList *ptr, *filters;
1092 
1093 	g_return_if_fail (IS_SHEET (sheet));
1094 
1095 	filters = g_slist_copy (sheet->filters);
1096 	for (ptr = filters; ptr != NULL ; ptr = ptr->next) {
1097 		GnmFilter *filter = ptr->data;
1098 		gboolean kill_filter = FALSE;
1099 		gboolean reapply_filter = FALSE;
1100 		GnmRange r = filter->r;
1101 
1102 		if (is_cols) {
1103 			if (start > filter->r.end.col)	/* a */
1104 				continue;
1105 
1106 			sheet->priv->filters_changed = TRUE;
1107 
1108 			if (is_insert) {
1109 				/* INSERTING COLUMNS */
1110 				filter->r.end.col =
1111 					MIN (gnm_sheet_get_last_col (sheet),
1112 					     filter->r.end.col + count);
1113 				/* inserting in the middle of a filter adds
1114 				 * fields.  Everything else just moves it */
1115 				if (start > filter->r.start.col &&
1116 				    start <= filter->r.end.col) {
1117 					int i;
1118 					for (i = 0; i < count; i++)
1119 						gnm_filter_add_field (filter,
1120 							start - filter->r.start.col + i);
1121 				} else
1122 					filter->r.start.col += count;
1123 			} else {
1124 				/* REMOVING COLUMNS */
1125 				int start_del = start - filter->r.start.col;
1126 				int end_del   = start_del + count;
1127 				if (start_del <= 0) {
1128 					start_del = 0;
1129 					if (end_del > 0)
1130 						filter->r.start.col = start;	/* c */
1131 					else
1132 						filter->r.start.col -= count;	/* b */
1133 					filter->r.end.col -= count;
1134 				} else {
1135 					if ((unsigned)end_del > filter->fields->len) {
1136 						end_del = filter->fields->len;
1137 						filter->r.end.col = start - 1;	/* d */
1138 					} else
1139 						filter->r.end.col -= count;
1140 				}
1141 
1142 				if (filter->r.end.col < filter->r.start.col)
1143 					kill_filter = TRUE;
1144 				else {
1145 					while (end_del-- > start_del) {
1146 						remove_col (filter, end_del, pundo);
1147 						reapply_filter = TRUE;
1148 					}
1149 				}
1150 			}
1151 		} else {
1152 			if (start > filter->r.end.row)
1153 				continue;
1154 
1155 			sheet->priv->filters_changed = TRUE;
1156 
1157 			if (is_insert) {
1158 				/* INSERTING ROWS */
1159 				filter->r.end.row =
1160 					MIN (gnm_sheet_get_last_row (sheet),
1161 					     filter->r.end.row + count);
1162 				if (start < filter->r.start.row)
1163 					filter->r.start.row += count;
1164 			} else {
1165 				/* REMOVING ROWS */
1166 				if (start <= filter->r.start.row) {
1167 					filter->r.end.row -= count;
1168 					if (start + count > filter->r.start.row)
1169 						/* delete if the dropdowns are wiped */
1170 						filter->r.start.row = filter->r.end.row + 1;
1171 					else
1172 						filter->r.start.row -= count;
1173 				} else if (start + count > filter->r.end.row)
1174 					filter->r.end.row = start -1;
1175 				else
1176 					filter->r.end.row -= count;
1177 
1178 				if (filter->r.end.row < filter->r.start.row)
1179 					kill_filter = TRUE;
1180 			}
1181 		}
1182 
1183 		if (kill_filter) {
1184 			/*
1185 			 * Empty the filter as we need fresh combo boxes
1186 			 * if we undo.
1187 			 */
1188 			while (filter->fields->len)
1189 				remove_col (filter,
1190 					    filter->fields->len - 1,
1191 					    pundo);
1192 
1193 			/* Restore the filters range */
1194 			gnm_filter_remove (filter);
1195 			filter->r = r;
1196 
1197 			if (pundo) {
1198 				GOUndo *u = go_undo_binary_new
1199 					(gnm_filter_ref (filter),
1200 					 sheet,
1201 					 (GOUndoBinaryFunc)gnm_filter_attach,
1202 					 (GFreeFunc)gnm_filter_unref,
1203 					 NULL);
1204 				*pundo = go_undo_combine (*pundo, u);
1205 			}
1206 			gnm_filter_unref (filter);
1207 		} else if (reapply_filter) {
1208 			GnmRange *range = g_new (GnmRange, 1);
1209 			*range = r;
1210 			if (pundo) {
1211 				GOUndo *u = go_undo_binary_new
1212 					(gnm_filter_ref (filter),
1213 					 range,
1214 					 (GOUndoBinaryFunc)gnm_filter_set_range,
1215 					 (GFreeFunc)gnm_filter_unref,
1216 					 g_free);
1217 				*pundo = go_undo_combine (*pundo, u);
1218 			}
1219 			gnm_filter_update_active (filter);
1220 			gnm_filter_reapply (filter);
1221 		}
1222 	}
1223 
1224 	g_slist_free (filters);
1225 }
1226