Update Spanish translation
[gnumeric.git] / src / sheet-filter.c
blob4d4e0e49ef39f78a025394faaaa7a5826266c256
2 /*
3 * sheet-filter.c: support for 'auto-filters'
5 * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
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.
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.
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
22 #include <gnumeric-config.h>
23 #include <libgnumeric.h>
24 #include <sheet-filter.h>
25 #include <sheet-filter-combo.h>
27 #include <workbook.h>
28 #include <sheet.h>
29 #include <sheet-private.h>
30 #include <cell.h>
31 #include <expr.h>
32 #include <value.h>
33 #include <gnm-format.h>
34 #include <ranges.h>
35 #include <number-match.h>
36 #include <gutils.h>
37 #include <sheet-object.h>
38 #include <widgets/gnm-filter-combo-view.h>
39 #include <widgets/gnm-cell-combo-view.h>
40 #include <gsf/gsf-impl-utils.h>
42 #include <glib/gi18n-lib.h>
43 #include <stdlib.h>
44 #include <string.h>
46 static gboolean
47 gnm_filter_op_needs_value (GnmFilterOp op)
49 g_return_val_if_fail (op != GNM_FILTER_UNUSED, FALSE);
51 switch (op & GNM_FILTER_OP_TYPE_MASK) {
52 case GNM_FILTER_OP_TYPE_OP:
53 case GNM_FILTER_OP_TYPE_BUCKETS:
54 case GNM_FILTER_OP_TYPE_MATCH:
55 return TRUE;
56 default:
57 g_assert_not_reached ();
58 case GNM_FILTER_OP_TYPE_BLANKS:
59 case GNM_FILTER_OP_TYPE_AVERAGE:
60 case GNM_FILTER_OP_TYPE_STDDEV:
61 return FALSE;
66 /**
67 * gnm_filter_condition_new_single:
68 * @op: #GnmFilterOp
69 * @v: #GnmValue
71 * Create a new condition with 1 value.
72 * Absorbs the reference to @v.
73 **/
74 GnmFilterCondition *
75 gnm_filter_condition_new_single (GnmFilterOp op, GnmValue *v)
77 GnmFilterCondition *res;
79 g_return_val_if_fail ((v != NULL) == gnm_filter_op_needs_value (op),
80 (value_release (v), NULL));
82 res = g_new0 (GnmFilterCondition, 1);
83 res->op[0] = op; res->op[1] = GNM_FILTER_UNUSED;
84 res->value[0] = v;
85 return res;
88 /**
89 * gnm_filter_condition_new_double:
90 * @op0: #GnmFilterOp
91 * @v0: #GnmValue
92 * @join_with_and:
93 * @op1: #GnmFilterOp
94 * @v1: #GnmValue
96 * Create a new condition with 2 value.
97 * Absorbs the reference to @v0 and @v1.
98 **/
99 GnmFilterCondition *
100 gnm_filter_condition_new_double (GnmFilterOp op0, GnmValue *v0,
101 gboolean join_with_and,
102 GnmFilterOp op1, GnmValue *v1)
104 GnmFilterCondition *res;
106 g_return_val_if_fail ((v0 != NULL) == gnm_filter_op_needs_value (op0),
107 (value_release (v0), value_release (v1), NULL));
108 g_return_val_if_fail ((v1 != NULL) == gnm_filter_op_needs_value (op1),
109 (value_release (v0), value_release (v1), NULL));
111 res = g_new0 (GnmFilterCondition, 1);
112 res->op[0] = op0; res->op[1] = op1;
113 res->is_and = join_with_and;
114 res->value[0] = v0; res->value[1] = v1;
115 return res;
118 GnmFilterCondition *
119 gnm_filter_condition_new_bucket (gboolean top, gboolean absolute,
120 gboolean rel_range, double n)
122 GnmFilterCondition *res = g_new0 (GnmFilterCondition, 1);
123 res->op[0] = GNM_FILTER_OP_TOP_N | (top ? 0 : 1) |
124 (absolute ? 0 : (rel_range ? 2 : 4));
125 res->op[1] = GNM_FILTER_UNUSED;
126 res->count = n;
127 return res;
130 GnmFilterCondition *
131 gnm_filter_condition_dup (GnmFilterCondition const *src)
133 GnmFilterCondition *dst;
135 if (src == NULL)
136 return NULL;
138 dst = g_new0 (GnmFilterCondition, 1);
139 dst->op[0] = src->op[0];
140 dst->op[1] = src->op[1];
141 dst->is_and = src->is_and;
142 dst->count = src->count;
143 dst->value[0] = value_dup (src->value[0]);
144 dst->value[1] = value_dup (src->value[1]);
145 return dst;
148 void
149 gnm_filter_condition_free (GnmFilterCondition *cond)
151 if (cond == NULL)
152 return;
154 value_release (cond->value[0]);
155 value_release (cond->value[1]);
156 g_free (cond);
159 GType
160 gnm_filter_condition_get_type (void)
162 static GType t = 0;
164 if (t == 0) {
165 t = g_boxed_type_register_static ("GnmFilterCondition",
166 (GBoxedCopyFunc)gnm_filter_condition_dup,
167 (GBoxedFreeFunc)gnm_filter_condition_free);
169 return t;
172 /*****************************************************************************/
174 typedef struct {
175 GnmFilterCondition const *cond;
176 GnmValue *val[2];
177 GnmValue *alt_val[2];
178 GORegexp regexp[2];
179 Sheet *target_sheet; /* not necessarilly the src */
180 } FilterExpr;
182 static void
183 filter_expr_init (FilterExpr *fexpr, unsigned i,
184 GnmFilterCondition const *cond,
185 GnmFilter const *filter)
187 GnmValue *tmp = cond->value[i];
189 if (tmp && VALUE_IS_STRING (tmp)) {
190 GnmFilterOp op = cond->op[i];
191 char const *str = value_peek_string (tmp);
192 GODateConventions const *date_conv =
193 sheet_date_conv (filter->sheet);
195 if ((op == GNM_FILTER_OP_EQUAL || op == GNM_FILTER_OP_NOT_EQUAL) &&
196 gnm_regcomp_XL (fexpr->regexp + i, str, GO_REG_ICASE, TRUE, TRUE) == GO_REG_OK) {
197 /* FIXME: Do we want to anchor at the end above? */
198 fexpr->val[i] = NULL;
199 return;
202 fexpr->val[i] = format_match_number (str, NULL, date_conv);
203 if (fexpr->val[i] != NULL)
204 return;
206 fexpr->val[i] = value_dup (tmp);
209 static void
210 filter_expr_release (FilterExpr *fexpr, unsigned i)
212 if (fexpr->val[i] == NULL)
213 go_regfree (fexpr->regexp + i);
214 else
215 value_release (fexpr->val[i]);
218 static char *
219 filter_cell_contents (GnmCell *cell)
221 GOFormat const *format = gnm_cell_get_format (cell);
222 GODateConventions const *date_conv =
223 sheet_date_conv (cell->base.sheet);
224 return format_value (format, cell->value, -1, date_conv);
227 static gboolean
228 filter_expr_eval (GnmFilterOp op, GnmValue const *src, GORegexp const *regexp,
229 GnmCell *cell)
231 GnmValue *target = cell->value;
232 GnmValDiff cmp;
233 GnmValue *fake_val = NULL;
235 if (src == NULL) {
236 char *str = filter_cell_contents (cell);
237 GORegmatch rm;
238 int res = go_regexec (regexp, str, 1, &rm, 0);
239 gboolean whole = (res == GO_REG_OK && rm.rm_so == 0 && str[rm.rm_eo] == 0);
241 g_free (str);
243 switch (res) {
244 case GO_REG_OK:
245 if (whole)
246 return op == GNM_FILTER_OP_EQUAL;
247 /* fall through */
249 case GO_REG_NOMATCH:
250 return op == GNM_FILTER_OP_NOT_EQUAL;
252 default:
253 g_warning ("Unexpected regexec result");
254 return FALSE;
258 if (VALUE_IS_STRING (target) && VALUE_IS_NUMBER (src)) {
259 GODateConventions const *date_conv =
260 sheet_date_conv (cell->base.sheet);
261 char *str = format_value (NULL, src, -1, date_conv);
262 fake_val = value_new_string_nocopy (str);
263 src = fake_val;
266 cmp = value_compare (target, src, FALSE);
267 value_release (fake_val);
269 switch (op) {
270 case GNM_FILTER_OP_EQUAL : return cmp == IS_EQUAL;
271 case GNM_FILTER_OP_NOT_EQUAL : return cmp != IS_EQUAL;
272 case GNM_FILTER_OP_GTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
273 case GNM_FILTER_OP_GT : return cmp == IS_GREATER;
274 case GNM_FILTER_OP_LTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
275 case GNM_FILTER_OP_LT : return cmp == IS_LESS;
276 default:
277 g_warning ("Huh?");
278 return FALSE;
282 static GnmValue *
283 cb_filter_expr (GnmCellIter const *iter, FilterExpr const *fexpr)
285 if (iter->cell != NULL) {
286 unsigned int ui;
288 for (ui = 0; ui < G_N_ELEMENTS (fexpr->cond->op); ui++) {
289 gboolean res;
291 if (fexpr->cond->op[ui] == GNM_FILTER_UNUSED)
292 continue;
294 res = filter_expr_eval (fexpr->cond->op[ui],
295 fexpr->val[ui],
296 fexpr->regexp + ui,
297 iter->cell);
298 if (fexpr->cond->is_and && !res)
299 goto nope; /* AND(...,FALSE,...) */
300 if (res && !fexpr->cond->is_and)
301 return NULL; /* OR(...,TRUE,...) */
304 if (fexpr->cond->is_and)
305 return NULL; /* AND(TRUE,...,TRUE) */
308 nope:
309 colrow_set_visibility (fexpr->target_sheet, FALSE, FALSE,
310 iter->pp.eval.row, iter->pp.eval.row);
311 return NULL;
314 /*****************************************************************************/
316 static GnmValue *
317 cb_filter_non_blanks (GnmCellIter const *iter, Sheet *target_sheet)
319 if (gnm_cell_is_blank (iter->cell))
320 colrow_set_visibility (target_sheet, FALSE, FALSE,
321 iter->pp.eval.row, iter->pp.eval.row);
322 return NULL;
325 static GnmValue *
326 cb_filter_blanks (GnmCellIter const *iter, Sheet *target_sheet)
328 if (!gnm_cell_is_blank (iter->cell))
329 colrow_set_visibility (target_sheet, FALSE, FALSE,
330 iter->pp.eval.row, iter->pp.eval.row);
331 return NULL;
334 /*****************************************************************************/
336 typedef struct {
337 unsigned count;
338 unsigned elements;
339 gboolean find_max;
340 GnmValue const **vals;
341 Sheet *target_sheet;
342 } FilterItems;
344 static GnmValue *
345 cb_filter_find_items (GnmCellIter const *iter, FilterItems *data)
347 GnmValue const *v = iter->cell->value;
348 if (data->elements >= data->count) {
349 unsigned j, i = data->elements;
350 GnmValDiff const cond = data->find_max ? IS_GREATER : IS_LESS;
351 while (i-- > 0)
352 if (value_compare (v, data->vals[i], TRUE) == cond) {
353 for (j = 0; j < i ; j++)
354 data->vals[j] = data->vals[j+1];
355 data->vals[i] = v;
356 break;
358 } else {
359 data->vals [data->elements++] = v;
360 if (data->elements == data->count) {
361 qsort (data->vals, data->elements,
362 sizeof (GnmValue *),
363 data->find_max ? value_cmp : value_cmp_reverse);
366 return NULL;
369 static GnmValue *
370 cb_hide_unwanted_items (GnmCellIter const *iter, FilterItems const *data)
372 if (iter->cell != NULL) {
373 int i = data->elements;
374 GnmValue const *v = iter->cell->value;
376 while (i-- > 0)
377 if (data->vals[i] == v)
378 return NULL;
380 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
381 iter->pp.eval.row, iter->pp.eval.row);
382 return NULL;
385 /*****************************************************************************/
387 typedef struct {
388 gboolean initialized, find_max;
389 gnm_float low, high;
390 Sheet *target_sheet;
391 } FilterPercentage;
393 static GnmValue *
394 cb_filter_find_percentage (GnmCellIter const *iter, FilterPercentage *data)
396 if (VALUE_IS_NUMBER (iter->cell->value)) {
397 gnm_float const v = value_get_as_float (iter->cell->value);
399 if (data->initialized) {
400 if (data->low > v)
401 data->low = v;
402 else if (data->high < v)
403 data->high = v;
404 } else {
405 data->initialized = TRUE;
406 data->low = data->high = v;
409 return NULL;
412 static GnmValue *
413 cb_hide_unwanted_percentage (GnmCellIter const *iter,
414 FilterPercentage const *data)
416 if (iter->cell != NULL && VALUE_IS_NUMBER (iter->cell->value)) {
417 gnm_float const v = value_get_as_float (iter->cell->value);
418 if (data->find_max) {
419 if (v >= data->high)
420 return NULL;
421 } else {
422 if (v <= data->low)
423 return NULL;
426 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
427 iter->pp.eval.row, iter->pp.eval.row);
428 return NULL;
430 /*****************************************************************************/
433 gnm_filter_combo_index (GnmFilterCombo *fcombo)
435 g_return_val_if_fail (GNM_IS_FILTER_COMBO (fcombo), 0);
437 return (sheet_object_get_range (GNM_SO (fcombo))->start.col -
438 fcombo->filter->r.start.col);
443 * gnm_filter_combo_apply:
444 * @fcombo: #GnmFilterCombo
445 * @target_sheet: @Sheet
448 void
449 gnm_filter_combo_apply (GnmFilterCombo *fcombo, Sheet *target_sheet)
451 GnmFilter const *filter;
452 GnmFilterCondition const *cond;
453 int col, start_row, end_row;
454 CellIterFlags iter_flags = CELL_ITER_IGNORE_HIDDEN;
456 g_return_if_fail (GNM_IS_FILTER_COMBO (fcombo));
458 filter = fcombo->filter;
459 cond = fcombo->cond;
460 col = sheet_object_get_range (GNM_SO (fcombo))->start.col;
461 start_row = filter->r.start.row + 1;
462 end_row = filter->r.end.row;
464 if (start_row > end_row ||
465 cond == NULL ||
466 cond->op[0] == GNM_FILTER_UNUSED)
467 return;
470 * For the combo we filter a temporary sheet using the data from
471 * filter->sheet and need to include everything from the source,
472 * because it has a different set of conditions
474 if (target_sheet != filter->sheet)
475 iter_flags = CELL_ITER_ALL;
477 if (0x10 >= (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
478 FilterExpr data;
479 data.cond = cond;
480 data.target_sheet = target_sheet;
481 filter_expr_init (&data, 0, cond, filter);
482 if (cond->op[1] != GNM_FILTER_UNUSED)
483 filter_expr_init (&data, 1, cond, filter);
485 sheet_foreach_cell_in_region (filter->sheet,
486 iter_flags,
487 col, start_row, col, end_row,
488 (CellIterFunc) cb_filter_expr, &data);
490 filter_expr_release (&data, 0);
491 if (cond->op[1] != GNM_FILTER_UNUSED)
492 filter_expr_release (&data, 1);
493 } else if (cond->op[0] == GNM_FILTER_OP_BLANKS)
494 sheet_foreach_cell_in_region (filter->sheet,
495 CELL_ITER_IGNORE_HIDDEN,
496 col, start_row, col, end_row,
497 (CellIterFunc) cb_filter_blanks, target_sheet);
498 else if (cond->op[0] == GNM_FILTER_OP_NON_BLANKS)
499 sheet_foreach_cell_in_region (filter->sheet,
500 CELL_ITER_IGNORE_HIDDEN,
501 col, start_row, col, end_row,
502 (CellIterFunc) cb_filter_non_blanks, target_sheet);
503 else if (0x30 == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
504 if (cond->op[0] & GNM_FILTER_OP_PERCENT_MASK) { /* relative */
505 if (cond->op[0] & GNM_FILTER_OP_REL_N_MASK) {
506 FilterItems data;
507 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
508 data.elements = 0;
509 data.count = 0.5 + cond->count * (end_row - start_row + 1) /100.;
510 if (data.count < 1)
511 data.count = 1;
512 data.vals = g_new (GnmValue const *, data.count);
513 sheet_foreach_cell_in_region (filter->sheet,
514 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
515 col, start_row, col, end_row,
516 (CellIterFunc) cb_filter_find_items, &data);
517 data.target_sheet = target_sheet;
518 sheet_foreach_cell_in_region (filter->sheet,
519 CELL_ITER_IGNORE_HIDDEN,
520 col, start_row, col, end_row,
521 (CellIterFunc) cb_hide_unwanted_items, &data);
522 g_free (data.vals);
523 } else {
524 FilterPercentage data;
525 gnm_float offset;
527 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
528 data.initialized = FALSE;
529 sheet_foreach_cell_in_region (filter->sheet,
530 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
531 col, start_row, col, end_row,
532 (CellIterFunc) cb_filter_find_percentage, &data);
533 offset = (data.high - data.low) * cond->count / 100.;
534 data.high -= offset;
535 data.low += offset;
536 data.target_sheet = target_sheet;
537 sheet_foreach_cell_in_region (filter->sheet,
538 CELL_ITER_IGNORE_HIDDEN,
539 col, start_row, col, end_row,
540 (CellIterFunc) cb_hide_unwanted_percentage, &data);
542 } else { /* absolute */
543 FilterItems data;
544 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
545 data.elements = 0;
546 data.count = cond->count;
547 data.vals = g_new (GnmValue const *, data.count);
549 sheet_foreach_cell_in_region (filter->sheet,
550 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
551 col, start_row, col, end_row,
552 (CellIterFunc) cb_filter_find_items, &data);
553 data.target_sheet = target_sheet;
554 sheet_foreach_cell_in_region (filter->sheet,
555 CELL_ITER_IGNORE_HIDDEN,
556 col, start_row, col, end_row,
557 (CellIterFunc) cb_hide_unwanted_items, &data);
558 g_free (data.vals);
560 } else
561 g_warning ("Invalid operator %d", cond->op[0]);
564 enum {
565 COND_CHANGED,
566 LAST_SIGNAL
569 static guint signals [LAST_SIGNAL] = { 0 };
571 typedef struct {
572 SheetObjectClass parent;
574 void (*cond_changed) (GnmFilterCombo *);
575 } GnmFilterComboClass;
577 static void
578 gnm_filter_combo_finalize (GObject *object)
580 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (object);
581 GObjectClass *parent;
583 gnm_filter_condition_free (fcombo->cond);
584 fcombo->cond = NULL;
586 parent = g_type_class_peek (GNM_SO_TYPE);
587 parent->finalize (object);
590 static void
591 gnm_filter_combo_init (SheetObject *so)
593 /* keep the arrows from wandering with their cells */
594 so->flags &= ~SHEET_OBJECT_MOVE_WITH_CELLS;
596 static SheetObjectView *
597 gnm_filter_combo_view_new (SheetObject *so, SheetObjectViewContainer *container)
599 return gnm_cell_combo_view_new (so,
600 gnm_filter_combo_view_get_type (), container);
602 static void
603 gnm_filter_combo_class_init (GObjectClass *gobject_class)
605 SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
607 /* Object class method overrides */
608 gobject_class->finalize = gnm_filter_combo_finalize;
610 /* SheetObject class method overrides */
611 so_class->new_view = gnm_filter_combo_view_new;
612 so_class->write_xml_sax = NULL;
613 so_class->prep_sax_parser = NULL;
614 so_class->copy = NULL;
616 signals[COND_CHANGED] = g_signal_new ("cond-changed",
617 GNM_FILTER_COMBO_TYPE,
618 G_SIGNAL_RUN_LAST,
619 G_STRUCT_OFFSET (GnmFilterComboClass, cond_changed),
620 NULL, NULL,
621 g_cclosure_marshal_VOID__VOID,
622 G_TYPE_NONE, 0);
625 GSF_CLASS (GnmFilterCombo, gnm_filter_combo,
626 gnm_filter_combo_class_init, gnm_filter_combo_init,
627 GNM_SO_TYPE)
629 /*************************************************************************/
631 static void
632 gnm_filter_add_field (GnmFilter *filter, int i)
634 /* pretend to fill the cell, then clip the X start later */
635 static double const a_offsets[4] = { .0, .0, 1., 1. };
636 int n;
637 GnmRange tmp;
638 SheetObjectAnchor anchor;
639 GnmFilterCombo *fcombo = g_object_new (GNM_FILTER_COMBO_TYPE, NULL);
641 fcombo->filter = filter;
642 tmp.start.row = tmp.end.row = filter->r.start.row;
643 tmp.start.col = tmp.end.col = filter->r.start.col + i;
644 sheet_object_anchor_init (&anchor, &tmp, a_offsets,
645 GOD_ANCHOR_DIR_DOWN_RIGHT, GNM_SO_ANCHOR_TWO_CELLS);
646 sheet_object_set_anchor (GNM_SO (fcombo), &anchor);
647 sheet_object_set_sheet (GNM_SO (fcombo), filter->sheet);
649 g_ptr_array_add (filter->fields, NULL);
650 for (n = filter->fields->len; --n > i ; )
651 g_ptr_array_index (filter->fields, n) =
652 g_ptr_array_index (filter->fields, n - 1);
653 g_ptr_array_index (filter->fields, n) = fcombo;
654 /* We hold a reference to fcombo */
657 void
658 gnm_filter_attach (GnmFilter *filter, Sheet *sheet)
660 int i;
662 g_return_if_fail (filter != NULL);
663 g_return_if_fail (filter->sheet == NULL);
664 g_return_if_fail (IS_SHEET (sheet));
666 gnm_filter_ref (filter);
668 filter->sheet = sheet;
669 sheet->filters = g_slist_prepend (sheet->filters, filter);
670 sheet->priv->filters_changed = TRUE;
672 for (i = 0 ; i < range_width (&(filter->r)); i++)
673 gnm_filter_add_field (filter, i);
678 * gnm_filter_new:
679 * @sheet:
680 * @r:
682 * Init a filter and add it to @sheet
684 GnmFilter *
685 gnm_filter_new (Sheet *sheet, GnmRange const *r)
687 GnmFilter *filter;
689 g_return_val_if_fail (IS_SHEET (sheet), NULL);
690 g_return_val_if_fail (r != NULL, NULL);
692 filter = g_new0 (GnmFilter, 1);
694 filter->is_active = FALSE;
695 filter->r = *r;
696 filter->fields = g_ptr_array_new ();
698 /* This creates the initial ref. */
699 gnm_filter_attach (filter, sheet);
701 return filter;
705 * gnm_filter_dup:
706 * @src: #GnmFilter
707 * @sheet: #Sheet
709 * Duplicate @src into @sheet
711 GnmFilter *
712 gnm_filter_dup (GnmFilter const *src, Sheet *sheet)
714 int i;
715 GnmFilter *dst;
717 g_return_val_if_fail (src != NULL, NULL);
718 g_return_val_if_fail (IS_SHEET (sheet), NULL);
720 dst = g_new0 (GnmFilter, 1);
722 dst->is_active = src->is_active;
723 dst->r = src->r;
724 dst->fields = g_ptr_array_new ();
726 /* This creates the initial ref. */
727 gnm_filter_attach (dst, sheet);
729 for (i = 0 ; i < range_width (&dst->r); i++) {
730 gnm_filter_add_field (dst, i);
731 gnm_filter_set_condition (dst, i,
732 gnm_filter_condition_dup (
733 gnm_filter_get_condition (src, i)),
734 FALSE);
737 return dst;
740 GnmFilter *
741 gnm_filter_ref (GnmFilter *filter)
743 g_return_val_if_fail (filter != NULL, NULL);
744 filter->ref_count++;
745 return filter;
748 void
749 gnm_filter_unref (GnmFilter *filter)
751 g_return_if_fail (filter != NULL);
753 filter->ref_count--;
754 if (filter->ref_count > 0)
755 return;
757 g_ptr_array_free (filter->fields, TRUE);
758 g_free (filter);
761 GType
762 gnm_filter_get_type (void)
764 static GType t = 0;
766 if (t == 0) {
767 t = g_boxed_type_register_static ("GnmFilter",
768 (GBoxedCopyFunc)gnm_filter_ref,
769 (GBoxedFreeFunc)gnm_filter_unref);
771 return t;
774 void
775 gnm_filter_remove (GnmFilter *filter)
777 Sheet *sheet;
778 int i;
780 g_return_if_fail (filter != NULL);
781 g_return_if_fail (filter->sheet != NULL);
783 sheet = filter->sheet;
784 sheet->priv->filters_changed = TRUE;
785 sheet->filters = g_slist_remove (sheet->filters, filter);
786 for (i = filter->r.start.row; ++i <= filter->r.end.row ; ) {
787 ColRowInfo *ri = sheet_row_get (sheet, i);
788 if (ri != NULL) {
789 ri->in_filter = FALSE;
790 colrow_set_visibility (sheet, FALSE, TRUE, i, i);
793 filter->sheet = NULL;
795 for (i = 0 ; i < (int)filter->fields->len ; i++) {
796 SheetObject *so = g_ptr_array_index (filter->fields, i);
797 sheet_object_clear_sheet (so);
798 g_object_unref (so);
800 g_ptr_array_set_size (filter->fields, 0);
804 * gnm_filter_get_condition:
805 * @filter: #GnmFilter
806 * @i: zero-based index
808 * Returns: (transfer none): the @i'th condition of @filter
810 GnmFilterCondition const *
811 gnm_filter_get_condition (GnmFilter const *filter, unsigned i)
813 GnmFilterCombo *fcombo;
815 g_return_val_if_fail (filter != NULL, NULL);
816 g_return_val_if_fail (i < filter->fields->len, NULL);
818 fcombo = g_ptr_array_index (filter->fields, i);
819 return fcombo->cond;
823 * gnm_filter_reapply:
824 * @filter: #GnmFilter
826 * Reapplies @filter after changes.
828 void
829 gnm_filter_reapply (GnmFilter *filter)
831 unsigned i;
833 colrow_set_visibility (filter->sheet, FALSE, TRUE,
834 filter->r.start.row + 1, filter->r.end.row);
835 for (i = 0 ; i < filter->fields->len ; i++)
836 gnm_filter_combo_apply (g_ptr_array_index (filter->fields, i),
837 filter->sheet);
840 static void
841 gnm_filter_update_active (GnmFilter *filter)
843 unsigned i;
844 gboolean old_active = filter->is_active;
846 filter->is_active = FALSE;
847 for (i = 0 ; i < filter->fields->len ; i++) {
848 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, i);
849 if (fcombo->cond != NULL) {
850 filter->is_active = TRUE;
851 break;
855 if (filter->is_active != old_active) {
856 int r;
857 for (r = filter->r.start.row; ++r <= filter->r.end.row ; ) {
858 ColRowInfo *ri = sheet_row_fetch (filter->sheet, r);
859 ri->in_filter = filter->is_active;
866 * gnm_filter_set_condition:
867 * @filter:
868 * @i:
869 * @cond: #GnmFilterCondition
870 * @apply:
872 * Change the @i-th condition of @filter to @cond. If @apply is
873 * %TRUE, @filter is used to set the visibility of the rows in @filter::sheet
875 * Absorbs the reference to @cond.
877 void
878 gnm_filter_set_condition (GnmFilter *filter, unsigned i,
879 GnmFilterCondition *cond,
880 gboolean apply)
882 GnmFilterCombo *fcombo;
883 gboolean existing_cond = FALSE;
885 g_return_if_fail (filter != NULL);
886 g_return_if_fail (i < filter->fields->len);
888 fcombo = g_ptr_array_index (filter->fields, i);
890 if (fcombo->cond != NULL) {
891 existing_cond = TRUE;
892 gnm_filter_condition_free (fcombo->cond);
894 fcombo->cond = cond;
895 g_signal_emit (G_OBJECT (fcombo), signals [COND_CHANGED], 0);
897 if (apply) {
898 /* if there was an existing cond then we need to do
899 * redo the whole filter.
900 * This is because we do not record what elements this
901 * particular field filtered
903 if (existing_cond)
904 gnm_filter_reapply (filter);
905 else
906 /* When adding a new cond all we need to do is
907 * apply that filter */
908 gnm_filter_combo_apply (fcombo, filter->sheet);
911 gnm_filter_update_active (filter);
915 * gnm_filter_overlaps_range:
916 * @filter: #GnmFilter
917 * @r: #GnmRange
919 * Returns: %TRUE if @filter overlaps @r.
921 static gboolean
922 gnm_filter_overlaps_range (GnmFilter const *filter, GnmRange const *r)
924 g_return_val_if_fail (filter != NULL, FALSE);
925 g_return_val_if_fail (r != NULL, FALSE);
927 return range_overlap (&filter->r, r);
930 /*************************************************************************/
933 * gnm_sheet_filter_at_pos:
934 * @sheet: #Sheet
935 * @pos: location to query
937 * Returns: (transfer none) (nullable): #GnmFilter covering @pos.
939 GnmFilter *
940 gnm_sheet_filter_at_pos (Sheet const *sheet, GnmCellPos const *pos)
942 GSList *ptr;
943 GnmRange r;
945 g_return_val_if_fail (IS_SHEET (sheet), NULL);
946 g_return_val_if_fail (NULL != pos, NULL);
948 range_init_cellpos (&r, pos);
949 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
950 if (gnm_filter_overlaps_range (ptr->data, &r))
951 return ptr->data;
953 return NULL;
957 * gnm_sheet_filter_intersect_rows:
958 * @sheet: #Sheet
959 * @from: starting row number
960 * @to: ending row number
962 * Returns: (transfer none) (nullable): the #GnmFilter, if any, that
963 * intersects the rows @from to @to.
965 GnmFilter *
966 gnm_sheet_filter_intersect_rows (Sheet const *sheet, int from, int to)
968 GSList *ptr;
969 GnmRange r;
971 g_return_val_if_fail (IS_SHEET (sheet), NULL);
973 range_init_rows (&r, sheet, from, to);
974 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
975 if (gnm_filter_overlaps_range (ptr->data, &r))
976 return ptr->data;
978 return NULL;
982 * gnm_sheet_filter_can_be_extended:
984 * Returns: (transfer full): #GnmRange
986 GnmRange *
987 gnm_sheet_filter_can_be_extended (G_GNUC_UNUSED Sheet const *sheet,
988 GnmFilter const *f, GnmRange const *r)
990 if (r->start.row < f->r.start.row || r->end.row > f->r.end.row)
991 return NULL;
992 if ((r->end.col > f->r.end.col) ||
993 (r->start.col < f->r.start.col)) {
994 GnmRange *res = g_new (GnmRange, 1);
995 *res = range_union (&f->r, r);
996 return res;
998 return NULL;
1002 /*************************************************************************/
1004 struct cb_remove_col_undo {
1005 unsigned col;
1006 GnmFilterCondition *cond;
1009 static void
1010 cb_remove_col_undo_free (struct cb_remove_col_undo *r)
1012 gnm_filter_condition_free (r->cond);
1013 g_free (r);
1016 static void
1017 cb_remove_col_undo (GnmFilter *filter, struct cb_remove_col_undo *r,
1018 G_GNUC_UNUSED gpointer data)
1020 while (filter->fields->len <= r->col)
1021 gnm_filter_add_field (filter, filter->fields->len);
1022 gnm_filter_set_condition (filter, r->col,
1023 gnm_filter_condition_dup (r->cond),
1024 FALSE);
1027 static void
1028 remove_col (GnmFilter *filter, unsigned col, GOUndo **pundo)
1030 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, col);
1031 if (pundo) {
1032 struct cb_remove_col_undo *r = g_new (struct cb_remove_col_undo, 1);
1033 GOUndo *u;
1035 r->col = col;
1036 r->cond = gnm_filter_condition_dup (fcombo->cond);
1037 u = go_undo_binary_new
1038 (gnm_filter_ref (filter), r,
1039 (GOUndoBinaryFunc)cb_remove_col_undo,
1040 (GFreeFunc)gnm_filter_unref,
1041 (GFreeFunc)cb_remove_col_undo_free);
1042 *pundo = go_undo_combine (*pundo, u);
1044 g_object_unref (fcombo);
1045 g_ptr_array_remove_index (filter->fields, col);
1048 static void
1049 gnm_filter_set_range (GnmFilter *filter, GnmRange *r)
1051 GnmRange old_r = filter->r;
1052 int i;
1053 int start = r->start.col;
1055 filter->r = *r;
1056 for (i = start; i < old_r.start.col; i++)
1057 gnm_filter_add_field (filter, i - start);
1058 for (i = old_r.end.col + 1; i <= r->end.col; i++)
1059 gnm_filter_add_field (filter, i - start);
1063 * gnm_sheet_filter_insdel_colrow:
1064 * @sheet: #Sheet
1065 * @is_cols: %TRUE for columns, %FALSE for rows.
1066 * @is_insert: %TRUE for insert, %FALSE for delete.
1067 * @start: Starting column or row.
1068 * @count: Number of columns or rows.
1069 * @pundo: (out) (optional): location to store undo closures.
1071 * Adjust filters as necessary to handle col/row insertions and deletions
1073 void
1074 gnm_sheet_filter_insdel_colrow (Sheet *sheet,
1075 gboolean is_cols, gboolean is_insert,
1076 int start, int count,
1077 GOUndo **pundo)
1079 GSList *ptr, *filters;
1081 g_return_if_fail (IS_SHEET (sheet));
1083 filters = g_slist_copy (sheet->filters);
1084 for (ptr = filters; ptr != NULL ; ptr = ptr->next) {
1085 GnmFilter *filter = ptr->data;
1086 gboolean kill_filter = FALSE;
1087 gboolean reapply_filter = FALSE;
1088 GnmRange r = filter->r;
1090 if (is_cols) {
1091 if (start > filter->r.end.col) /* a */
1092 continue;
1094 sheet->priv->filters_changed = TRUE;
1096 if (is_insert) {
1097 /* INSERTING COLUMNS */
1098 filter->r.end.col += count;
1099 /* inserting in the middle of a filter adds
1100 * fields. Everything else just moves it */
1101 if (start > filter->r.start.col &&
1102 start <= filter->r.end.col) {
1103 int i;
1104 for (i = 0; i < count; i++)
1105 gnm_filter_add_field (filter,
1106 start - filter->r.start.col + i);
1107 } else
1108 filter->r.start.col += count;
1109 } else {
1110 /* REMOVING COLUMNS */
1111 int start_del = start - filter->r.start.col;
1112 int end_del = start_del + count;
1113 if (start_del <= 0) {
1114 start_del = 0;
1115 if (end_del > 0)
1116 filter->r.start.col = start; /* c */
1117 else
1118 filter->r.start.col -= count; /* b */
1119 filter->r.end.col -= count;
1120 } else {
1121 if ((unsigned)end_del > filter->fields->len) {
1122 end_del = filter->fields->len;
1123 filter->r.end.col = start - 1; /* d */
1124 } else
1125 filter->r.end.col -= count;
1128 if (filter->r.end.col < filter->r.start.col)
1129 kill_filter = TRUE;
1130 else {
1131 while (end_del-- > start_del) {
1132 remove_col (filter, end_del, pundo);
1133 reapply_filter = TRUE;
1137 } else {
1138 if (start > filter->r.end.row)
1139 continue;
1141 sheet->priv->filters_changed = TRUE;
1143 if (is_insert) {
1144 /* INSERTING ROWS */
1145 filter->r.end.row += count;
1146 if (start < filter->r.start.row)
1147 filter->r.start.row += count;
1148 } else {
1149 /* REMOVING ROWS */
1150 if (start <= filter->r.start.row) {
1151 filter->r.end.row -= count;
1152 if (start + count > filter->r.start.row)
1153 /* delete if the dropdowns are wiped */
1154 filter->r.start.row = filter->r.end.row + 1;
1155 else
1156 filter->r.start.row -= count;
1157 } else if (start + count > filter->r.end.row)
1158 filter->r.end.row = start -1;
1159 else
1160 filter->r.end.row -= count;
1162 if (filter->r.end.row < filter->r.start.row)
1163 kill_filter = TRUE;
1167 if (kill_filter) {
1169 * Empty the filter as we need fresh combo boxes
1170 * if we undo.
1172 while (filter->fields->len)
1173 remove_col (filter,
1174 filter->fields->len - 1,
1175 pundo);
1177 /* Restore the filters range */
1178 gnm_filter_remove (filter);
1179 filter->r = r;
1181 if (pundo) {
1182 GOUndo *u = go_undo_binary_new
1183 (gnm_filter_ref (filter),
1184 sheet,
1185 (GOUndoBinaryFunc)gnm_filter_attach,
1186 (GFreeFunc)gnm_filter_unref,
1187 NULL);
1188 *pundo = go_undo_combine (*pundo, u);
1190 gnm_filter_unref (filter);
1191 } else if (reapply_filter) {
1192 GnmRange *range = g_new (GnmRange, 1);
1193 *range = r;
1194 if (pundo) {
1195 GOUndo *u = go_undo_binary_new
1196 (gnm_filter_ref (filter),
1197 range,
1198 (GOUndoBinaryFunc)gnm_filter_set_range,
1199 (GFreeFunc)gnm_filter_unref,
1200 g_free);
1201 *pundo = go_undo_combine (*pundo, u);
1203 gnm_filter_update_active (filter);
1204 gnm_filter_reapply (filter);
1208 g_slist_free (filters);