Make graphs (and images) not resize with cells by default. [#684450]
[gnumeric.git] / src / sheet-filter.c
blob3db6529aa1c7580367117998eb3270519dd12496
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 /*
4 * sheet-filter.c: support for 'auto-filters'
6 * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) version 3.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
21 * USA
23 #include <gnumeric-config.h>
24 #include "libgnumeric.h"
25 #include "sheet-filter.h"
26 #include "sheet-filter-combo.h"
28 #include "workbook.h"
29 #include "sheet.h"
30 #include "sheet-private.h"
31 #include "cell.h"
32 #include "expr.h"
33 #include "value.h"
34 #include "gnm-format.h"
35 #include "ranges.h"
36 #include "number-match.h"
37 #include "gutils.h"
38 #include "sheet-object.h"
39 #include "gnm-filter-combo-view.h"
40 #include "gnm-cell-combo-view.h"
41 #include <gsf/gsf-impl-utils.h>
43 #include <glib/gi18n-lib.h>
44 #include <stdlib.h>
45 #include <string.h>
47 static gboolean
48 gnm_filter_op_needs_value (GnmFilterOp op)
50 switch (op & GNM_FILTER_OP_TYPE_MASK) {
51 case GNM_FILTER_OP_TYPE_OP:
52 case GNM_FILTER_OP_TYPE_BUCKETS:
53 case GNM_FILTER_OP_TYPE_MATCH:
54 return TRUE;
55 default:
56 g_assert_not_reached ();
57 case GNM_FILTER_OP_TYPE_BLANKS:
58 case GNM_FILTER_OP_TYPE_AVERAGE:
59 case GNM_FILTER_OP_TYPE_STDDEV:
60 return FALSE;
65 /**
66 * gnm_filter_condition_new_single :
67 * @op: #GnmFilterOp
68 * @v: #GnmValue
70 * Create a new condition with 1 value.
71 * Absorbs the reference to @v.
72 **/
73 GnmFilterCondition *
74 gnm_filter_condition_new_single (GnmFilterOp op, GnmValue *v)
76 GnmFilterCondition *res;
78 g_return_val_if_fail ((v != NULL) == gnm_filter_op_needs_value (op), NULL);
80 res = g_new0 (GnmFilterCondition, 1);
81 res->op[0] = op; res->op[1] = GNM_FILTER_UNUSED;
82 res->value[0] = v;
83 return res;
86 /**
87 * gnm_filter_condition_new_double :
88 * @op0: #GnmFilterOp
89 * @v0: #GnmValue
90 * @join_with_and:
91 * @op1: #GnmFilterOp
92 * @v1: #GnmValue
94 * Create a new condition with 2 value.
95 * Absorbs the reference to @v0 and @v1.
96 **/
97 GnmFilterCondition *
98 gnm_filter_condition_new_double (GnmFilterOp op0, GnmValue *v0,
99 gboolean join_with_and,
100 GnmFilterOp op1, GnmValue *v1)
102 GnmFilterCondition *res;
104 g_return_val_if_fail ((v0 != NULL) == gnm_filter_op_needs_value (op0), NULL);
105 g_return_val_if_fail ((v1 != NULL) == gnm_filter_op_needs_value (op1), NULL);
107 res = g_new0 (GnmFilterCondition, 1);
108 res->op[0] = op0; res->op[1] = op1;
109 res->is_and = join_with_and;
110 res->value[0] = v0; res->value[1] = v1;
111 return res;
114 GnmFilterCondition *
115 gnm_filter_condition_new_bucket (gboolean top, gboolean absolute,
116 gboolean rel_range, double n)
118 GnmFilterCondition *res = g_new0 (GnmFilterCondition, 1);
119 res->op[0] = GNM_FILTER_OP_TOP_N | (top ? 0 : 1) |
120 (absolute ? 0 : (rel_range ? 2 : 4));
121 res->op[1] = GNM_FILTER_UNUSED;
122 res->count = n;
123 return res;
126 GnmFilterCondition *
127 gnm_filter_condition_dup (GnmFilterCondition const *src)
129 GnmFilterCondition *dst;
131 if (src == NULL)
132 return NULL;
134 dst = g_new0 (GnmFilterCondition, 1);
135 dst->op[0] = src->op[0];
136 dst->op[1] = src->op[1];
137 dst->is_and = src->is_and;
138 dst->count = src->count;
139 dst->value[0] = value_dup (src->value[0]);
140 dst->value[1] = value_dup (src->value[1]);
141 return dst;
144 void
145 gnm_filter_condition_free (GnmFilterCondition *cond)
147 if (cond == NULL)
148 return;
150 value_release (cond->value[0]);
151 value_release (cond->value[1]);
152 g_free (cond);
155 GType
156 gnm_filter_condition_get_type (void)
158 static GType t = 0;
160 if (t == 0) {
161 t = g_boxed_type_register_static ("GnmFilterCondition",
162 (GBoxedCopyFunc)gnm_filter_condition_dup,
163 (GBoxedFreeFunc)gnm_filter_condition_free);
165 return t;
168 /*****************************************************************************/
170 typedef struct {
171 GnmFilterCondition const *cond;
172 GnmValue *val[2];
173 GnmValue *alt_val[2];
174 GORegexp regexp[2];
175 Sheet *target_sheet; /* not necessarilly the src */
176 } FilterExpr;
178 static void
179 filter_expr_init (FilterExpr *fexpr, unsigned i,
180 GnmFilterCondition const *cond,
181 GnmFilter const *filter)
183 GnmValue *tmp = cond->value[i];
185 if (tmp && VALUE_IS_STRING (tmp)) {
186 GnmFilterOp op = cond->op[i];
187 char const *str = value_peek_string (tmp);
188 GODateConventions const *date_conv =
189 workbook_date_conv (filter->sheet->workbook);
191 if ((op == GNM_FILTER_OP_EQUAL || op == GNM_FILTER_OP_NOT_EQUAL) &&
192 gnm_regcomp_XL (fexpr->regexp + i, str, GO_REG_ICASE, TRUE, TRUE) == GO_REG_OK) {
193 /* FIXME: Do we want to anchor at the end above? */
194 fexpr->val[i] = NULL;
195 return;
198 fexpr->val[i] = format_match_number (str, NULL, date_conv);
199 if (fexpr->val[i] != NULL)
200 return;
202 fexpr->val[i] = value_dup (tmp);
205 static void
206 filter_expr_release (FilterExpr *fexpr, unsigned i)
208 if (fexpr->val[i] == NULL)
209 go_regfree (fexpr->regexp + i);
210 else
211 value_release (fexpr->val[i]);
214 static char *
215 filter_cell_contents (GnmCell *cell)
217 GOFormat const *format = gnm_cell_get_format (cell);
218 GODateConventions const *date_conv =
219 workbook_date_conv (cell->base.sheet->workbook);
220 return format_value (format, cell->value, -1, date_conv);
223 static gboolean
224 filter_expr_eval (GnmFilterOp op, GnmValue const *src, GORegexp const *regexp,
225 GnmCell *cell)
227 GnmValue *target = cell->value;
228 GnmValDiff cmp;
229 GnmValue *fake_val = NULL;
231 if (src == NULL) {
232 char *str = filter_cell_contents (cell);
233 GORegmatch rm;
234 int res = go_regexec (regexp, str, 1, &rm, 0);
235 gboolean whole = (rm.rm_so == 0 && str[rm.rm_eo] == 0);
237 g_free (str);
239 switch (res) {
240 case GO_REG_OK:
241 if (whole)
242 return op == GNM_FILTER_OP_EQUAL;
243 /* fall through */
245 case GO_REG_NOMATCH:
246 return op == GNM_FILTER_OP_NOT_EQUAL;
248 default:
249 g_warning ("Unexpected regexec result");
250 return FALSE;
254 if (VALUE_IS_STRING (target) && VALUE_IS_NUMBER (src)) {
255 GODateConventions const *date_conv =
256 workbook_date_conv (cell->base.sheet->workbook);
257 char *str = format_value (NULL, src, -1, date_conv);
258 fake_val = value_new_string_nocopy (str);
259 src = fake_val;
262 cmp = value_compare (target, src, FALSE);
263 value_release (fake_val);
265 switch (op) {
266 case GNM_FILTER_OP_EQUAL : return cmp == IS_EQUAL;
267 case GNM_FILTER_OP_NOT_EQUAL : return cmp != IS_EQUAL;
268 case GNM_FILTER_OP_GTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
269 case GNM_FILTER_OP_GT : return cmp == IS_GREATER;
270 case GNM_FILTER_OP_LTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
271 case GNM_FILTER_OP_LT : return cmp == IS_LESS;
272 default :
273 g_warning ("Huh?");
274 return FALSE;
278 static GnmValue *
279 cb_filter_expr (GnmCellIter const *iter, FilterExpr const *fexpr)
281 if (iter->cell != NULL) {
282 unsigned int ui;
284 for (ui = 0; ui < G_N_ELEMENTS (fexpr->cond->op); ui++) {
285 gboolean res;
287 if (fexpr->cond->op[ui] == GNM_FILTER_UNUSED)
288 continue;
290 res = filter_expr_eval (fexpr->cond->op[ui],
291 fexpr->val[ui],
292 fexpr->regexp + ui,
293 iter->cell);
294 if (fexpr->cond->is_and && !res)
295 goto nope; /* AND(...,FALSE,...) */
296 if (res && !fexpr->cond->is_and)
297 return NULL; /* OR(...,TRUE,...) */
300 if (fexpr->cond->is_and)
301 return NULL; /* AND(TRUE,...,TRUE) */
304 nope:
305 colrow_set_visibility (fexpr->target_sheet, FALSE, FALSE,
306 iter->pp.eval.row, iter->pp.eval.row);
307 return NULL;
310 /*****************************************************************************/
312 static GnmValue *
313 cb_filter_non_blanks (GnmCellIter const *iter, Sheet *target_sheet)
315 if (gnm_cell_is_blank (iter->cell))
316 colrow_set_visibility (target_sheet, FALSE, FALSE,
317 iter->pp.eval.row, iter->pp.eval.row);
318 return NULL;
321 static GnmValue *
322 cb_filter_blanks (GnmCellIter const *iter, Sheet *target_sheet)
324 if (!gnm_cell_is_blank (iter->cell))
325 colrow_set_visibility (target_sheet, FALSE, FALSE,
326 iter->pp.eval.row, iter->pp.eval.row);
327 return NULL;
330 /*****************************************************************************/
332 typedef struct {
333 unsigned count;
334 unsigned elements;
335 gboolean find_max;
336 GnmValue const **vals;
337 Sheet *target_sheet;
338 } FilterItems;
340 static GnmValue *
341 cb_filter_find_items (GnmCellIter const *iter, FilterItems *data)
343 GnmValue const *v = iter->cell->value;
344 if (data->elements >= data->count) {
345 unsigned j, i = data->elements;
346 GnmValDiff const cond = data->find_max ? IS_GREATER : IS_LESS;
347 while (i-- > 0)
348 if (value_compare (v, data->vals[i], TRUE) == cond) {
349 for (j = 0; j < i ; j++)
350 data->vals[j] = data->vals[j+1];
351 data->vals[i] = v;
352 break;
354 } else {
355 data->vals [data->elements++] = v;
356 if (data->elements == data->count) {
357 qsort (data->vals, data->elements,
358 sizeof (GnmValue *),
359 data->find_max ? value_cmp : value_cmp_reverse);
362 return NULL;
365 static GnmValue *
366 cb_hide_unwanted_items (GnmCellIter const *iter, FilterItems const *data)
368 if (iter->cell != NULL) {
369 int i = data->elements;
370 GnmValue const *v = iter->cell->value;
372 while (i-- > 0)
373 if (data->vals[i] == v)
374 return NULL;
376 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
377 iter->pp.eval.row, iter->pp.eval.row);
378 return NULL;
381 /*****************************************************************************/
383 typedef struct {
384 gboolean initialized, find_max;
385 gnm_float low, high;
386 Sheet *target_sheet;
387 } FilterPercentage;
389 static GnmValue *
390 cb_filter_find_percentage (GnmCellIter const *iter, FilterPercentage *data)
392 if (VALUE_IS_NUMBER (iter->cell->value)) {
393 gnm_float const v = value_get_as_float (iter->cell->value);
395 if (data->initialized) {
396 if (data->low > v)
397 data->low = v;
398 else if (data->high < v)
399 data->high = v;
400 } else {
401 data->initialized = TRUE;
402 data->low = data->high = v;
405 return NULL;
408 static GnmValue *
409 cb_hide_unwanted_percentage (GnmCellIter const *iter,
410 FilterPercentage const *data)
412 if (iter->cell != NULL && VALUE_IS_NUMBER (iter->cell->value)) {
413 gnm_float const v = value_get_as_float (iter->cell->value);
414 if (data->find_max) {
415 if (v >= data->high)
416 return NULL;
417 } else {
418 if (v <= data->low)
419 return NULL;
422 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
423 iter->pp.eval.row, iter->pp.eval.row);
424 return NULL;
426 /*****************************************************************************/
429 gnm_filter_combo_index (GnmFilterCombo *fcombo)
431 g_return_val_if_fail (GNM_IS_FILTER_COMBO (fcombo), 0);
433 return (sheet_object_get_range (GNM_SO (fcombo))->start.col -
434 fcombo->filter->r.start.col);
439 * gnm_filter_combo_apply :
440 * @fcombo: #GnmFilterCombo
441 * @target_sheet: @Sheet
444 void
445 gnm_filter_combo_apply (GnmFilterCombo *fcombo, Sheet *target_sheet)
447 GnmFilter const *filter;
448 GnmFilterCondition const *cond;
449 int col, start_row, end_row;
450 CellIterFlags iter_flags = CELL_ITER_IGNORE_HIDDEN;
452 g_return_if_fail (GNM_IS_FILTER_COMBO (fcombo));
454 filter = fcombo->filter;
455 cond = fcombo->cond;
456 col = sheet_object_get_range (GNM_SO (fcombo))->start.col;
457 start_row = filter->r.start.row + 1;
458 end_row = filter->r.end.row;
460 if (start_row > end_row ||
461 cond == NULL ||
462 cond->op[0] == GNM_FILTER_UNUSED)
463 return;
466 * For the combo we filter a temporary sheet using the data from
467 * filter->sheet and need to include everything from the source,
468 * because it has a different set of conditions
470 if (target_sheet != filter->sheet)
471 iter_flags = CELL_ITER_ALL;
473 if (0x10 >= (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
474 FilterExpr data;
475 data.cond = cond;
476 data.target_sheet = target_sheet;
477 filter_expr_init (&data, 0, cond, filter);
478 if (cond->op[1] != GNM_FILTER_UNUSED)
479 filter_expr_init (&data, 1, cond, filter);
481 sheet_foreach_cell_in_range (filter->sheet,
482 iter_flags,
483 col, start_row, col, end_row,
484 (CellIterFunc) cb_filter_expr, &data);
486 filter_expr_release (&data, 0);
487 if (cond->op[1] != GNM_FILTER_UNUSED)
488 filter_expr_release (&data, 1);
489 } else if (cond->op[0] == GNM_FILTER_OP_BLANKS)
490 sheet_foreach_cell_in_range (filter->sheet,
491 CELL_ITER_IGNORE_HIDDEN,
492 col, start_row, col, end_row,
493 (CellIterFunc) cb_filter_blanks, target_sheet);
494 else if (cond->op[0] == GNM_FILTER_OP_NON_BLANKS)
495 sheet_foreach_cell_in_range (filter->sheet,
496 CELL_ITER_IGNORE_HIDDEN,
497 col, start_row, col, end_row,
498 (CellIterFunc) cb_filter_non_blanks, target_sheet);
499 else if (0x30 == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
500 if (cond->op[0] & GNM_FILTER_OP_PERCENT_MASK) { /* relative */
501 if (cond->op[0] & GNM_FILTER_OP_REL_N_MASK) {
502 FilterItems data;
503 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
504 data.elements = 0;
505 data.count = 0.5 + cond->count * (end_row - start_row + 1) /100.;
506 if (data.count < 1)
507 data.count = 1;
508 data.vals = g_alloca (sizeof (GnmValue *) * data.count);
509 sheet_foreach_cell_in_range (filter->sheet,
510 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
511 col, start_row, col, end_row,
512 (CellIterFunc) cb_filter_find_items, &data);
513 data.target_sheet = target_sheet;
514 sheet_foreach_cell_in_range (filter->sheet,
515 CELL_ITER_IGNORE_HIDDEN,
516 col, start_row, col, end_row,
517 (CellIterFunc) cb_hide_unwanted_items, &data);
518 } else {
519 FilterPercentage data;
520 gnm_float offset;
522 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
523 data.initialized = FALSE;
524 sheet_foreach_cell_in_range (filter->sheet,
525 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
526 col, start_row, col, end_row,
527 (CellIterFunc) cb_filter_find_percentage, &data);
528 offset = (data.high - data.low) * cond->count / 100.;
529 data.high -= offset;
530 data.low += offset;
531 data.target_sheet = target_sheet;
532 sheet_foreach_cell_in_range (filter->sheet,
533 CELL_ITER_IGNORE_HIDDEN,
534 col, start_row, col, end_row,
535 (CellIterFunc) cb_hide_unwanted_percentage, &data);
537 } else { /* absolute */
538 FilterItems data;
539 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
540 data.elements = 0;
541 data.count = cond->count;
542 data.vals = g_alloca (sizeof (GnmValue *) * data.count);
543 sheet_foreach_cell_in_range (filter->sheet,
544 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
545 col, start_row, col, end_row,
546 (CellIterFunc) cb_filter_find_items, &data);
547 data.target_sheet = target_sheet;
548 sheet_foreach_cell_in_range (filter->sheet,
549 CELL_ITER_IGNORE_HIDDEN,
550 col, start_row, col, end_row,
551 (CellIterFunc) cb_hide_unwanted_items, &data);
553 } else
554 g_warning ("Invalid operator %d", cond->op[0]);
557 enum {
558 COND_CHANGED,
559 LAST_SIGNAL
562 static guint signals [LAST_SIGNAL] = { 0 };
564 typedef struct {
565 SheetObjectClass parent;
567 void (*cond_changed) (GnmFilterCombo *);
568 } GnmFilterComboClass;
570 static void
571 gnm_filter_combo_finalize (GObject *object)
573 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (object);
574 GObjectClass *parent;
576 gnm_filter_condition_free (fcombo->cond);
577 fcombo->cond = NULL;
579 parent = g_type_class_peek (GNM_SO_TYPE);
580 parent->finalize (object);
583 static void
584 gnm_filter_combo_init (SheetObject *so)
586 /* keep the arrows from wandering with their cells */
587 so->flags &= ~SHEET_OBJECT_MOVE_WITH_CELLS;
589 static SheetObjectView *
590 gnm_filter_combo_view_new (SheetObject *so, SheetObjectViewContainer *container)
592 return gnm_cell_combo_view_new (so,
593 gnm_filter_combo_view_get_type (), container);
595 static void
596 gnm_filter_combo_class_init (GObjectClass *gobject_class)
598 SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
600 /* Object class method overrides */
601 gobject_class->finalize = gnm_filter_combo_finalize;
603 /* SheetObject class method overrides */
604 so_class->new_view = gnm_filter_combo_view_new;
605 so_class->write_xml_sax = NULL;
606 so_class->prep_sax_parser = NULL;
607 so_class->copy = NULL;
609 signals[COND_CHANGED] = g_signal_new ("cond-changed",
610 GNM_FILTER_COMBO_TYPE,
611 G_SIGNAL_RUN_LAST,
612 G_STRUCT_OFFSET (GnmFilterComboClass, cond_changed),
613 NULL, NULL,
614 g_cclosure_marshal_VOID__VOID,
615 G_TYPE_NONE, 0);
618 GSF_CLASS (GnmFilterCombo, gnm_filter_combo,
619 gnm_filter_combo_class_init, gnm_filter_combo_init,
620 GNM_SO_TYPE)
622 /*************************************************************************/
624 static void
625 gnm_filter_add_field (GnmFilter *filter, int i)
627 /* pretend to fill the cell, then clip the X start later */
628 static double const a_offsets[4] = { .0, .0, 1., 1. };
629 int n;
630 GnmRange tmp;
631 SheetObjectAnchor anchor;
632 GnmFilterCombo *fcombo = g_object_new (GNM_FILTER_COMBO_TYPE, NULL);
634 fcombo->filter = filter;
635 tmp.start.row = tmp.end.row = filter->r.start.row;
636 tmp.start.col = tmp.end.col = filter->r.start.col + i;
637 sheet_object_anchor_init (&anchor, &tmp, a_offsets,
638 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
639 sheet_object_set_anchor (GNM_SO (fcombo), &anchor);
640 sheet_object_set_sheet (GNM_SO (fcombo), filter->sheet);
642 g_ptr_array_add (filter->fields, NULL);
643 for (n = filter->fields->len; --n > i ; )
644 g_ptr_array_index (filter->fields, n) =
645 g_ptr_array_index (filter->fields, n - 1);
646 g_ptr_array_index (filter->fields, n) = fcombo;
647 /* We hold a reference to fcombo */
650 void
651 gnm_filter_attach (GnmFilter *filter, Sheet *sheet)
653 int i;
655 g_return_if_fail (filter != NULL);
656 g_return_if_fail (filter->sheet == NULL);
657 g_return_if_fail (IS_SHEET (sheet));
659 gnm_filter_ref (filter);
661 filter->sheet = sheet;
662 sheet->filters = g_slist_prepend (sheet->filters, filter);
663 sheet->priv->filters_changed = TRUE;
665 for (i = 0 ; i < range_width (&(filter->r)); i++)
666 gnm_filter_add_field (filter, i);
671 * gnm_filter_new :
672 * @sheet:
673 * @r:
675 * Init a filter and add it to @sheet
677 GnmFilter *
678 gnm_filter_new (Sheet *sheet, GnmRange const *r)
680 GnmFilter *filter;
682 g_return_val_if_fail (IS_SHEET (sheet), NULL);
683 g_return_val_if_fail (r != NULL, NULL);
685 filter = g_new0 (GnmFilter, 1);
687 filter->is_active = FALSE;
688 filter->r = *r;
689 filter->fields = g_ptr_array_new ();
691 /* This creates the initial ref. */
692 gnm_filter_attach (filter, sheet);
694 return filter;
698 * gnm_filter_dup :
699 * @src: #GnmFilter
700 * @sheet: #Sheet
702 * Duplicate @src into @sheet
704 GnmFilter *
705 gnm_filter_dup (GnmFilter const *src, Sheet *sheet)
707 int i;
708 GnmFilter *dst;
710 g_return_val_if_fail (src != NULL, NULL);
711 g_return_val_if_fail (IS_SHEET (sheet), NULL);
713 dst = g_new0 (GnmFilter, 1);
715 dst->is_active = src->is_active;
716 dst->r = src->r;
717 dst->fields = g_ptr_array_new ();
719 /* This creates the initial ref. */
720 gnm_filter_attach (dst, sheet);
722 for (i = 0 ; i < range_width (&dst->r); i++) {
723 gnm_filter_add_field (dst, i);
724 gnm_filter_set_condition (dst, i,
725 gnm_filter_condition_dup (
726 gnm_filter_get_condition (src, i)),
727 FALSE);
730 return dst;
733 GnmFilter *
734 gnm_filter_ref (GnmFilter *filter)
736 g_return_val_if_fail (filter != NULL, NULL);
737 filter->ref_count++;
738 return filter;
741 void
742 gnm_filter_unref (GnmFilter *filter)
744 g_return_if_fail (filter != NULL);
746 filter->ref_count--;
747 if (filter->ref_count > 0)
748 return;
750 g_ptr_array_free (filter->fields, TRUE);
751 g_free (filter);
754 GType
755 gnm_filter_get_type (void)
757 static GType t = 0;
759 if (t == 0) {
760 t = g_boxed_type_register_static ("GnmFilter",
761 (GBoxedCopyFunc)gnm_filter_ref,
762 (GBoxedFreeFunc)gnm_filter_unref);
764 return t;
767 void
768 gnm_filter_remove (GnmFilter *filter)
770 Sheet *sheet;
771 int i;
773 g_return_if_fail (filter != NULL);
774 g_return_if_fail (filter->sheet != NULL);
776 sheet = filter->sheet;
777 sheet->priv->filters_changed = TRUE;
778 sheet->filters = g_slist_remove (sheet->filters, filter);
779 for (i = filter->r.start.row; ++i <= filter->r.end.row ; ) {
780 ColRowInfo *ri = sheet_row_get (sheet, i);
781 if (ri != NULL) {
782 ri->in_filter = FALSE;
783 colrow_set_visibility (sheet, FALSE, TRUE, i, i);
786 filter->sheet = NULL;
788 for (i = 0 ; i < (int)filter->fields->len ; i++) {
789 SheetObject *so = g_ptr_array_index (filter->fields, i);
790 sheet_object_clear_sheet (so);
791 g_object_unref (so);
793 g_ptr_array_set_size (filter->fields, 0);
797 * gnm_filter_get_condition :
798 * @filter:
799 * @i:
802 GnmFilterCondition const *
803 gnm_filter_get_condition (GnmFilter const *filter, unsigned i)
805 GnmFilterCombo *fcombo;
807 g_return_val_if_fail (filter != NULL, NULL);
808 g_return_val_if_fail (i < filter->fields->len, NULL);
810 fcombo = g_ptr_array_index (filter->fields, i);
811 return fcombo->cond;
814 void
815 gnm_filter_reapply (GnmFilter *filter)
817 unsigned i;
819 colrow_set_visibility (filter->sheet, FALSE, TRUE,
820 filter->r.start.row + 1, filter->r.end.row);
821 for (i = 0 ; i < filter->fields->len ; i++)
822 gnm_filter_combo_apply (g_ptr_array_index (filter->fields, i),
823 filter->sheet);
826 static void
827 gnm_filter_update_active (GnmFilter *filter)
829 unsigned i;
830 gboolean old_active = filter->is_active;
832 filter->is_active = FALSE;
833 for (i = 0 ; i < filter->fields->len ; i++) {
834 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, i);
835 if (fcombo->cond != NULL) {
836 filter->is_active = TRUE;
837 break;
841 if (filter->is_active != old_active) {
842 int r;
843 for (r = filter->r.start.row; ++r <= filter->r.end.row ; ) {
844 ColRowInfo *ri = sheet_row_fetch (filter->sheet, r);
845 ri->in_filter = filter->is_active;
852 * gnm_filter_set_condition :
853 * @filter:
854 * @i:
855 * @cond: #GnmFilterCondition
856 * @apply:
858 * Change the @i-th condition of @filter to @cond. If @apply is
859 * TRUE @filter is used to set the visibility of the rows in @filter::sheet
861 * Absorbs the reference to @cond.
863 void
864 gnm_filter_set_condition (GnmFilter *filter, unsigned i,
865 GnmFilterCondition *cond,
866 gboolean apply)
868 GnmFilterCombo *fcombo;
869 gboolean existing_cond = FALSE;
871 g_return_if_fail (filter != NULL);
872 g_return_if_fail (i < filter->fields->len);
874 fcombo = g_ptr_array_index (filter->fields, i);
876 if (fcombo->cond != NULL) {
877 existing_cond = TRUE;
878 gnm_filter_condition_free (fcombo->cond);
880 fcombo->cond = cond;
881 g_signal_emit (G_OBJECT (fcombo), signals [COND_CHANGED], 0);
883 if (apply) {
884 /* if there was an existing cond then we need to do
885 * redo the whole filter.
886 * This is because we do not record what elements this
887 * particular field filtered
889 if (existing_cond)
890 gnm_filter_reapply (filter);
891 else
892 /* When adding a new cond all we need to do is
893 * apply that filter */
894 gnm_filter_combo_apply (fcombo, filter->sheet);
897 gnm_filter_update_active (filter);
901 * gnm_filter_overlaps_range :
902 * @filter: #GnmFilter
903 * @r: #GnmRange
905 * Returns: %TRUE if @filter overlaps @r.
907 static gboolean
908 gnm_filter_overlaps_range (GnmFilter const *filter, GnmRange const *r)
910 g_return_val_if_fail (filter != NULL, FALSE);
911 g_return_val_if_fail (r != NULL, FALSE);
913 return range_overlap (&filter->r, r);
916 /*************************************************************************/
919 * gnm_sheet_filter_at_pos :
920 * @sheet: #Sheet
922 * Returns : #GnmRange
924 GnmFilter *
925 gnm_sheet_filter_at_pos (Sheet const *sheet, GnmCellPos const *pos)
927 GSList *ptr;
928 GnmRange r;
930 g_return_val_if_fail (IS_SHEET (sheet), NULL);
931 g_return_val_if_fail (NULL != pos, NULL);
933 range_init_cellpos (&r, pos);
934 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
935 if (gnm_filter_overlaps_range (ptr->data, &r))
936 return ptr->data;
938 return NULL;
942 * gnm_sheet_filter_intersect_rows:
943 * @sheet:
944 * @from: starting row number
945 * @to: ending row number
947 * Returns: the filter, if any, that intersect the rows @from to @to
949 GnmFilter *
950 gnm_sheet_filter_intersect_rows (Sheet const *sheet, int from, int to)
952 GSList *ptr;
953 GnmRange r;
955 g_return_val_if_fail (IS_SHEET (sheet), NULL);
957 range_init_rows (&r, sheet, from, to);
958 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
959 if (gnm_filter_overlaps_range (ptr->data, &r))
960 return ptr->data;
962 return NULL;
965 GnmRange *
966 gnm_sheet_filter_can_be_extended (G_GNUC_UNUSED Sheet const *sheet,
967 GnmFilter const *f, GnmRange const *r)
969 if (r->start.row < f->r.start.row || r->end.row > f->r.end.row)
970 return NULL;
971 if ((r->end.col > f->r.end.col) ||
972 (r->start.col < f->r.start.col)) {
973 GnmRange *res = g_new (GnmRange, 1);
974 *res = range_union (&f->r, r);
975 return res;
977 return NULL;
981 /*************************************************************************/
983 struct cb_remove_col_undo {
984 unsigned col;
985 GnmFilterCondition *cond;
988 static void
989 cb_remove_col_undo_free (struct cb_remove_col_undo *r)
991 gnm_filter_condition_free (r->cond);
992 g_free (r);
995 static void
996 cb_remove_col_undo (GnmFilter *filter, struct cb_remove_col_undo *r,
997 G_GNUC_UNUSED gpointer data)
999 while (filter->fields->len <= r->col)
1000 gnm_filter_add_field (filter, filter->fields->len);
1001 gnm_filter_set_condition (filter, r->col,
1002 gnm_filter_condition_dup (r->cond),
1003 FALSE);
1006 static void
1007 remove_col (GnmFilter *filter, unsigned col, GOUndo **pundo)
1009 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, col);
1010 if (pundo) {
1011 struct cb_remove_col_undo *r = g_new (struct cb_remove_col_undo, 1);
1012 GOUndo *u;
1014 r->col = col;
1015 r->cond = gnm_filter_condition_dup (fcombo->cond);
1016 u = go_undo_binary_new
1017 (gnm_filter_ref (filter), r,
1018 (GOUndoBinaryFunc)cb_remove_col_undo,
1019 (GFreeFunc)gnm_filter_unref,
1020 (GFreeFunc)cb_remove_col_undo_free);
1021 *pundo = go_undo_combine (*pundo, u);
1023 g_object_unref (fcombo);
1024 g_ptr_array_remove_index (filter->fields, col);
1027 static void
1028 gnm_filter_set_range (GnmFilter *filter, GnmRange *r)
1030 GnmRange old_r = filter->r;
1031 int i;
1032 int start = r->start.col;
1034 filter->r = *r;
1035 for (i = start; i < old_r.start.col; i++)
1036 gnm_filter_add_field (filter, i - start);
1037 for (i = old_r.end.col + 1; i <= r->end.col; i++)
1038 gnm_filter_add_field (filter, i - start);
1042 * gnm_sheet_filter_insdel_colrow :
1043 * @sheet:
1044 * @is_cols:
1045 * @is_insert:
1046 * @start:
1047 * @count:
1048 * @pundo: location to store undo closures.
1050 * Adjust filters as necessary to handle col/row insertions and deletions
1052 void
1053 gnm_sheet_filter_insdel_colrow (Sheet *sheet,
1054 gboolean is_cols, gboolean is_insert,
1055 int start, int count,
1056 GOUndo **pundo)
1058 GSList *ptr, *filters;
1060 g_return_if_fail (IS_SHEET (sheet));
1062 filters = g_slist_copy (sheet->filters);
1063 for (ptr = filters; ptr != NULL ; ptr = ptr->next) {
1064 GnmFilter *filter = ptr->data;
1065 gboolean kill_filter = FALSE;
1066 gboolean reapply_filter = FALSE;
1067 GnmRange r = filter->r;
1069 if (is_cols) {
1070 if (start > filter->r.end.col) /* a */
1071 continue;
1073 sheet->priv->filters_changed = TRUE;
1075 if (is_insert) {
1076 /* INSERTING COLUMNS */
1077 filter->r.end.col += count;
1078 /* inserting in the middle of a filter adds
1079 * fields. Everything else just moves it */
1080 if (start > filter->r.start.col &&
1081 start <= filter->r.end.col) {
1082 int i;
1083 for (i = 0; i < count; i++)
1084 gnm_filter_add_field (filter,
1085 start - filter->r.start.col + i);
1086 } else
1087 filter->r.start.col += count;
1088 } else {
1089 /* REMOVING COLUMNS */
1090 int start_del = start - filter->r.start.col;
1091 int end_del = start_del + count;
1092 if (start_del <= 0) {
1093 start_del = 0;
1094 if (end_del > 0)
1095 filter->r.start.col = start; /* c */
1096 else
1097 filter->r.start.col -= count; /* b */
1098 filter->r.end.col -= count;
1099 } else {
1100 if ((unsigned)end_del > filter->fields->len) {
1101 end_del = filter->fields->len;
1102 filter->r.end.col = start - 1; /* d */
1103 } else
1104 filter->r.end.col -= count;
1107 if (filter->r.end.col < filter->r.start.col)
1108 kill_filter = TRUE;
1109 else {
1110 while (end_del-- > start_del) {
1111 remove_col (filter, end_del, pundo);
1112 reapply_filter = TRUE;
1116 } else {
1117 if (start > filter->r.end.row)
1118 continue;
1120 sheet->priv->filters_changed = TRUE;
1122 if (is_insert) {
1123 /* INSERTING ROWS */
1124 filter->r.end.row += count;
1125 if (start < filter->r.start.row)
1126 filter->r.start.row += count;
1127 } else {
1128 /* REMOVING ROWS */
1129 if (start <= filter->r.start.row) {
1130 filter->r.end.row -= count;
1131 if (start + count > filter->r.start.row)
1132 /* delete if the dropdowns are wiped */
1133 filter->r.start.row = filter->r.end.row + 1;
1134 else
1135 filter->r.start.row -= count;
1136 } else if (start + count > filter->r.end.row)
1137 filter->r.end.row = start -1;
1138 else
1139 filter->r.end.row -= count;
1141 if (filter->r.end.row < filter->r.start.row)
1142 kill_filter = TRUE;
1146 if (kill_filter) {
1148 * Empty the filter as we need fresh combo boxes
1149 * if we undo.
1151 while (filter->fields->len)
1152 remove_col (filter,
1153 filter->fields->len - 1,
1154 pundo);
1156 /* Restore the filters range */
1157 gnm_filter_remove (filter);
1158 filter->r = r;
1160 if (pundo) {
1161 GOUndo *u = go_undo_binary_new
1162 (gnm_filter_ref (filter),
1163 sheet,
1164 (GOUndoBinaryFunc)gnm_filter_attach,
1165 (GFreeFunc)gnm_filter_unref,
1166 NULL);
1167 *pundo = go_undo_combine (*pundo, u);
1169 gnm_filter_unref (filter);
1170 } else if (reapply_filter) {
1171 GnmRange *range = g_new (GnmRange, 1);
1172 *range = r;
1173 if (pundo) {
1174 GOUndo *u = go_undo_binary_new
1175 (gnm_filter_ref (filter),
1176 range,
1177 (GOUndoBinaryFunc)gnm_filter_set_range,
1178 (GFreeFunc)gnm_filter_unref,
1179 g_free);
1180 *pundo = go_undo_combine (*pundo, u);
1182 gnm_filter_update_active (filter);
1183 gnm_filter_reapply (filter);
1187 g_slist_free (filters);