GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / sheet-filter.c
blob71d8fca12b6f23eedc982b316e908dd205583066
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 g_return_val_if_fail (op != GNM_FILTER_UNUSED, FALSE);
52 switch (op & GNM_FILTER_OP_TYPE_MASK) {
53 case GNM_FILTER_OP_TYPE_OP:
54 case GNM_FILTER_OP_TYPE_BUCKETS:
55 case GNM_FILTER_OP_TYPE_MATCH:
56 return TRUE;
57 default:
58 g_assert_not_reached ();
59 case GNM_FILTER_OP_TYPE_BLANKS:
60 case GNM_FILTER_OP_TYPE_AVERAGE:
61 case GNM_FILTER_OP_TYPE_STDDEV:
62 return FALSE;
67 /**
68 * gnm_filter_condition_new_single:
69 * @op: #GnmFilterOp
70 * @v: #GnmValue
72 * Create a new condition with 1 value.
73 * Absorbs the reference to @v.
74 **/
75 GnmFilterCondition *
76 gnm_filter_condition_new_single (GnmFilterOp op, GnmValue *v)
78 GnmFilterCondition *res;
80 g_return_val_if_fail ((v != NULL) == gnm_filter_op_needs_value (op),
81 (value_release (v), NULL));
83 res = g_new0 (GnmFilterCondition, 1);
84 res->op[0] = op; res->op[1] = GNM_FILTER_UNUSED;
85 res->value[0] = v;
86 return res;
89 /**
90 * gnm_filter_condition_new_double:
91 * @op0: #GnmFilterOp
92 * @v0: #GnmValue
93 * @join_with_and:
94 * @op1: #GnmFilterOp
95 * @v1: #GnmValue
97 * Create a new condition with 2 value.
98 * Absorbs the reference to @v0 and @v1.
99 **/
100 GnmFilterCondition *
101 gnm_filter_condition_new_double (GnmFilterOp op0, GnmValue *v0,
102 gboolean join_with_and,
103 GnmFilterOp op1, GnmValue *v1)
105 GnmFilterCondition *res;
107 g_return_val_if_fail ((v0 != NULL) == gnm_filter_op_needs_value (op0),
108 (value_release (v0), value_release (v1), NULL));
109 g_return_val_if_fail ((v1 != NULL) == gnm_filter_op_needs_value (op1),
110 (value_release (v0), value_release (v1), NULL));
112 res = g_new0 (GnmFilterCondition, 1);
113 res->op[0] = op0; res->op[1] = op1;
114 res->is_and = join_with_and;
115 res->value[0] = v0; res->value[1] = v1;
116 return res;
119 GnmFilterCondition *
120 gnm_filter_condition_new_bucket (gboolean top, gboolean absolute,
121 gboolean rel_range, double n)
123 GnmFilterCondition *res = g_new0 (GnmFilterCondition, 1);
124 res->op[0] = GNM_FILTER_OP_TOP_N | (top ? 0 : 1) |
125 (absolute ? 0 : (rel_range ? 2 : 4));
126 res->op[1] = GNM_FILTER_UNUSED;
127 res->count = n;
128 return res;
131 GnmFilterCondition *
132 gnm_filter_condition_dup (GnmFilterCondition const *src)
134 GnmFilterCondition *dst;
136 if (src == NULL)
137 return NULL;
139 dst = g_new0 (GnmFilterCondition, 1);
140 dst->op[0] = src->op[0];
141 dst->op[1] = src->op[1];
142 dst->is_and = src->is_and;
143 dst->count = src->count;
144 dst->value[0] = value_dup (src->value[0]);
145 dst->value[1] = value_dup (src->value[1]);
146 return dst;
149 void
150 gnm_filter_condition_free (GnmFilterCondition *cond)
152 if (cond == NULL)
153 return;
155 value_release (cond->value[0]);
156 value_release (cond->value[1]);
157 g_free (cond);
160 GType
161 gnm_filter_condition_get_type (void)
163 static GType t = 0;
165 if (t == 0) {
166 t = g_boxed_type_register_static ("GnmFilterCondition",
167 (GBoxedCopyFunc)gnm_filter_condition_dup,
168 (GBoxedFreeFunc)gnm_filter_condition_free);
170 return t;
173 /*****************************************************************************/
175 typedef struct {
176 GnmFilterCondition const *cond;
177 GnmValue *val[2];
178 GnmValue *alt_val[2];
179 GORegexp regexp[2];
180 Sheet *target_sheet; /* not necessarilly the src */
181 } FilterExpr;
183 static void
184 filter_expr_init (FilterExpr *fexpr, unsigned i,
185 GnmFilterCondition const *cond,
186 GnmFilter const *filter)
188 GnmValue *tmp = cond->value[i];
190 if (tmp && VALUE_IS_STRING (tmp)) {
191 GnmFilterOp op = cond->op[i];
192 char const *str = value_peek_string (tmp);
193 GODateConventions const *date_conv =
194 sheet_date_conv (filter->sheet);
196 if ((op == GNM_FILTER_OP_EQUAL || op == GNM_FILTER_OP_NOT_EQUAL) &&
197 gnm_regcomp_XL (fexpr->regexp + i, str, GO_REG_ICASE, TRUE, TRUE) == GO_REG_OK) {
198 /* FIXME: Do we want to anchor at the end above? */
199 fexpr->val[i] = NULL;
200 return;
203 fexpr->val[i] = format_match_number (str, NULL, date_conv);
204 if (fexpr->val[i] != NULL)
205 return;
207 fexpr->val[i] = value_dup (tmp);
210 static void
211 filter_expr_release (FilterExpr *fexpr, unsigned i)
213 if (fexpr->val[i] == NULL)
214 go_regfree (fexpr->regexp + i);
215 else
216 value_release (fexpr->val[i]);
219 static char *
220 filter_cell_contents (GnmCell *cell)
222 GOFormat const *format = gnm_cell_get_format (cell);
223 GODateConventions const *date_conv =
224 sheet_date_conv (cell->base.sheet);
225 return format_value (format, cell->value, -1, date_conv);
228 static gboolean
229 filter_expr_eval (GnmFilterOp op, GnmValue const *src, GORegexp const *regexp,
230 GnmCell *cell)
232 GnmValue *target = cell->value;
233 GnmValDiff cmp;
234 GnmValue *fake_val = NULL;
236 if (src == NULL) {
237 char *str = filter_cell_contents (cell);
238 GORegmatch rm;
239 int res = go_regexec (regexp, str, 1, &rm, 0);
240 gboolean whole = (res == GO_REG_OK && rm.rm_so == 0 && str[rm.rm_eo] == 0);
242 g_free (str);
244 switch (res) {
245 case GO_REG_OK:
246 if (whole)
247 return op == GNM_FILTER_OP_EQUAL;
248 /* fall through */
250 case GO_REG_NOMATCH:
251 return op == GNM_FILTER_OP_NOT_EQUAL;
253 default:
254 g_warning ("Unexpected regexec result");
255 return FALSE;
259 if (VALUE_IS_STRING (target) && VALUE_IS_NUMBER (src)) {
260 GODateConventions const *date_conv =
261 sheet_date_conv (cell->base.sheet);
262 char *str = format_value (NULL, src, -1, date_conv);
263 fake_val = value_new_string_nocopy (str);
264 src = fake_val;
267 cmp = value_compare (target, src, FALSE);
268 value_release (fake_val);
270 switch (op) {
271 case GNM_FILTER_OP_EQUAL : return cmp == IS_EQUAL;
272 case GNM_FILTER_OP_NOT_EQUAL : return cmp != IS_EQUAL;
273 case GNM_FILTER_OP_GTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
274 case GNM_FILTER_OP_GT : return cmp == IS_GREATER;
275 case GNM_FILTER_OP_LTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
276 case GNM_FILTER_OP_LT : return cmp == IS_LESS;
277 default:
278 g_warning ("Huh?");
279 return FALSE;
283 static GnmValue *
284 cb_filter_expr (GnmCellIter const *iter, FilterExpr const *fexpr)
286 if (iter->cell != NULL) {
287 unsigned int ui;
289 for (ui = 0; ui < G_N_ELEMENTS (fexpr->cond->op); ui++) {
290 gboolean res;
292 if (fexpr->cond->op[ui] == GNM_FILTER_UNUSED)
293 continue;
295 res = filter_expr_eval (fexpr->cond->op[ui],
296 fexpr->val[ui],
297 fexpr->regexp + ui,
298 iter->cell);
299 if (fexpr->cond->is_and && !res)
300 goto nope; /* AND(...,FALSE,...) */
301 if (res && !fexpr->cond->is_and)
302 return NULL; /* OR(...,TRUE,...) */
305 if (fexpr->cond->is_and)
306 return NULL; /* AND(TRUE,...,TRUE) */
309 nope:
310 colrow_set_visibility (fexpr->target_sheet, FALSE, FALSE,
311 iter->pp.eval.row, iter->pp.eval.row);
312 return NULL;
315 /*****************************************************************************/
317 static GnmValue *
318 cb_filter_non_blanks (GnmCellIter const *iter, Sheet *target_sheet)
320 if (gnm_cell_is_blank (iter->cell))
321 colrow_set_visibility (target_sheet, FALSE, FALSE,
322 iter->pp.eval.row, iter->pp.eval.row);
323 return NULL;
326 static GnmValue *
327 cb_filter_blanks (GnmCellIter const *iter, Sheet *target_sheet)
329 if (!gnm_cell_is_blank (iter->cell))
330 colrow_set_visibility (target_sheet, FALSE, FALSE,
331 iter->pp.eval.row, iter->pp.eval.row);
332 return NULL;
335 /*****************************************************************************/
337 typedef struct {
338 unsigned count;
339 unsigned elements;
340 gboolean find_max;
341 GnmValue const **vals;
342 Sheet *target_sheet;
343 } FilterItems;
345 static GnmValue *
346 cb_filter_find_items (GnmCellIter const *iter, FilterItems *data)
348 GnmValue const *v = iter->cell->value;
349 if (data->elements >= data->count) {
350 unsigned j, i = data->elements;
351 GnmValDiff const cond = data->find_max ? IS_GREATER : IS_LESS;
352 while (i-- > 0)
353 if (value_compare (v, data->vals[i], TRUE) == cond) {
354 for (j = 0; j < i ; j++)
355 data->vals[j] = data->vals[j+1];
356 data->vals[i] = v;
357 break;
359 } else {
360 data->vals [data->elements++] = v;
361 if (data->elements == data->count) {
362 qsort (data->vals, data->elements,
363 sizeof (GnmValue *),
364 data->find_max ? value_cmp : value_cmp_reverse);
367 return NULL;
370 static GnmValue *
371 cb_hide_unwanted_items (GnmCellIter const *iter, FilterItems const *data)
373 if (iter->cell != NULL) {
374 int i = data->elements;
375 GnmValue const *v = iter->cell->value;
377 while (i-- > 0)
378 if (data->vals[i] == v)
379 return NULL;
381 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
382 iter->pp.eval.row, iter->pp.eval.row);
383 return NULL;
386 /*****************************************************************************/
388 typedef struct {
389 gboolean initialized, find_max;
390 gnm_float low, high;
391 Sheet *target_sheet;
392 } FilterPercentage;
394 static GnmValue *
395 cb_filter_find_percentage (GnmCellIter const *iter, FilterPercentage *data)
397 if (VALUE_IS_NUMBER (iter->cell->value)) {
398 gnm_float const v = value_get_as_float (iter->cell->value);
400 if (data->initialized) {
401 if (data->low > v)
402 data->low = v;
403 else if (data->high < v)
404 data->high = v;
405 } else {
406 data->initialized = TRUE;
407 data->low = data->high = v;
410 return NULL;
413 static GnmValue *
414 cb_hide_unwanted_percentage (GnmCellIter const *iter,
415 FilterPercentage const *data)
417 if (iter->cell != NULL && VALUE_IS_NUMBER (iter->cell->value)) {
418 gnm_float const v = value_get_as_float (iter->cell->value);
419 if (data->find_max) {
420 if (v >= data->high)
421 return NULL;
422 } else {
423 if (v <= data->low)
424 return NULL;
427 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
428 iter->pp.eval.row, iter->pp.eval.row);
429 return NULL;
431 /*****************************************************************************/
434 gnm_filter_combo_index (GnmFilterCombo *fcombo)
436 g_return_val_if_fail (GNM_IS_FILTER_COMBO (fcombo), 0);
438 return (sheet_object_get_range (GNM_SO (fcombo))->start.col -
439 fcombo->filter->r.start.col);
444 * gnm_filter_combo_apply:
445 * @fcombo: #GnmFilterCombo
446 * @target_sheet: @Sheet
449 void
450 gnm_filter_combo_apply (GnmFilterCombo *fcombo, Sheet *target_sheet)
452 GnmFilter const *filter;
453 GnmFilterCondition const *cond;
454 int col, start_row, end_row;
455 CellIterFlags iter_flags = CELL_ITER_IGNORE_HIDDEN;
457 g_return_if_fail (GNM_IS_FILTER_COMBO (fcombo));
459 filter = fcombo->filter;
460 cond = fcombo->cond;
461 col = sheet_object_get_range (GNM_SO (fcombo))->start.col;
462 start_row = filter->r.start.row + 1;
463 end_row = filter->r.end.row;
465 if (start_row > end_row ||
466 cond == NULL ||
467 cond->op[0] == GNM_FILTER_UNUSED)
468 return;
471 * For the combo we filter a temporary sheet using the data from
472 * filter->sheet and need to include everything from the source,
473 * because it has a different set of conditions
475 if (target_sheet != filter->sheet)
476 iter_flags = CELL_ITER_ALL;
478 if (0x10 >= (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
479 FilterExpr data;
480 data.cond = cond;
481 data.target_sheet = target_sheet;
482 filter_expr_init (&data, 0, cond, filter);
483 if (cond->op[1] != GNM_FILTER_UNUSED)
484 filter_expr_init (&data, 1, cond, filter);
486 sheet_foreach_cell_in_region (filter->sheet,
487 iter_flags,
488 col, start_row, col, end_row,
489 (CellIterFunc) cb_filter_expr, &data);
491 filter_expr_release (&data, 0);
492 if (cond->op[1] != GNM_FILTER_UNUSED)
493 filter_expr_release (&data, 1);
494 } else if (cond->op[0] == GNM_FILTER_OP_BLANKS)
495 sheet_foreach_cell_in_region (filter->sheet,
496 CELL_ITER_IGNORE_HIDDEN,
497 col, start_row, col, end_row,
498 (CellIterFunc) cb_filter_blanks, target_sheet);
499 else if (cond->op[0] == GNM_FILTER_OP_NON_BLANKS)
500 sheet_foreach_cell_in_region (filter->sheet,
501 CELL_ITER_IGNORE_HIDDEN,
502 col, start_row, col, end_row,
503 (CellIterFunc) cb_filter_non_blanks, target_sheet);
504 else if (0x30 == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
505 if (cond->op[0] & GNM_FILTER_OP_PERCENT_MASK) { /* relative */
506 if (cond->op[0] & GNM_FILTER_OP_REL_N_MASK) {
507 FilterItems data;
508 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
509 data.elements = 0;
510 data.count = 0.5 + cond->count * (end_row - start_row + 1) /100.;
511 if (data.count < 1)
512 data.count = 1;
513 data.vals = g_new (GnmValue const *, data.count);
514 sheet_foreach_cell_in_region (filter->sheet,
515 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
516 col, start_row, col, end_row,
517 (CellIterFunc) cb_filter_find_items, &data);
518 data.target_sheet = target_sheet;
519 sheet_foreach_cell_in_region (filter->sheet,
520 CELL_ITER_IGNORE_HIDDEN,
521 col, start_row, col, end_row,
522 (CellIterFunc) cb_hide_unwanted_items, &data);
523 g_free (data.vals);
524 } else {
525 FilterPercentage data;
526 gnm_float offset;
528 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
529 data.initialized = FALSE;
530 sheet_foreach_cell_in_region (filter->sheet,
531 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
532 col, start_row, col, end_row,
533 (CellIterFunc) cb_filter_find_percentage, &data);
534 offset = (data.high - data.low) * cond->count / 100.;
535 data.high -= offset;
536 data.low += offset;
537 data.target_sheet = target_sheet;
538 sheet_foreach_cell_in_region (filter->sheet,
539 CELL_ITER_IGNORE_HIDDEN,
540 col, start_row, col, end_row,
541 (CellIterFunc) cb_hide_unwanted_percentage, &data);
543 } else { /* absolute */
544 FilterItems data;
545 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
546 data.elements = 0;
547 data.count = cond->count;
548 data.vals = g_new (GnmValue const *, data.count);
550 sheet_foreach_cell_in_region (filter->sheet,
551 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
552 col, start_row, col, end_row,
553 (CellIterFunc) cb_filter_find_items, &data);
554 data.target_sheet = target_sheet;
555 sheet_foreach_cell_in_region (filter->sheet,
556 CELL_ITER_IGNORE_HIDDEN,
557 col, start_row, col, end_row,
558 (CellIterFunc) cb_hide_unwanted_items, &data);
559 g_free (data.vals);
561 } else
562 g_warning ("Invalid operator %d", cond->op[0]);
565 enum {
566 COND_CHANGED,
567 LAST_SIGNAL
570 static guint signals [LAST_SIGNAL] = { 0 };
572 typedef struct {
573 SheetObjectClass parent;
575 void (*cond_changed) (GnmFilterCombo *);
576 } GnmFilterComboClass;
578 static void
579 gnm_filter_combo_finalize (GObject *object)
581 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (object);
582 GObjectClass *parent;
584 gnm_filter_condition_free (fcombo->cond);
585 fcombo->cond = NULL;
587 parent = g_type_class_peek (GNM_SO_TYPE);
588 parent->finalize (object);
591 static void
592 gnm_filter_combo_init (SheetObject *so)
594 /* keep the arrows from wandering with their cells */
595 so->flags &= ~SHEET_OBJECT_MOVE_WITH_CELLS;
597 static SheetObjectView *
598 gnm_filter_combo_view_new (SheetObject *so, SheetObjectViewContainer *container)
600 return gnm_cell_combo_view_new (so,
601 gnm_filter_combo_view_get_type (), container);
603 static void
604 gnm_filter_combo_class_init (GObjectClass *gobject_class)
606 SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
608 /* Object class method overrides */
609 gobject_class->finalize = gnm_filter_combo_finalize;
611 /* SheetObject class method overrides */
612 so_class->new_view = gnm_filter_combo_view_new;
613 so_class->write_xml_sax = NULL;
614 so_class->prep_sax_parser = NULL;
615 so_class->copy = NULL;
617 signals[COND_CHANGED] = g_signal_new ("cond-changed",
618 GNM_FILTER_COMBO_TYPE,
619 G_SIGNAL_RUN_LAST,
620 G_STRUCT_OFFSET (GnmFilterComboClass, cond_changed),
621 NULL, NULL,
622 g_cclosure_marshal_VOID__VOID,
623 G_TYPE_NONE, 0);
626 GSF_CLASS (GnmFilterCombo, gnm_filter_combo,
627 gnm_filter_combo_class_init, gnm_filter_combo_init,
628 GNM_SO_TYPE)
630 /*************************************************************************/
632 static void
633 gnm_filter_add_field (GnmFilter *filter, int i)
635 /* pretend to fill the cell, then clip the X start later */
636 static double const a_offsets[4] = { .0, .0, 1., 1. };
637 int n;
638 GnmRange tmp;
639 SheetObjectAnchor anchor;
640 GnmFilterCombo *fcombo = g_object_new (GNM_FILTER_COMBO_TYPE, NULL);
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);
650 g_ptr_array_add (filter->fields, NULL);
651 for (n = filter->fields->len; --n > i ; )
652 g_ptr_array_index (filter->fields, n) =
653 g_ptr_array_index (filter->fields, n - 1);
654 g_ptr_array_index (filter->fields, n) = fcombo;
655 /* We hold a reference to fcombo */
658 void
659 gnm_filter_attach (GnmFilter *filter, Sheet *sheet)
661 int i;
663 g_return_if_fail (filter != NULL);
664 g_return_if_fail (filter->sheet == NULL);
665 g_return_if_fail (IS_SHEET (sheet));
667 gnm_filter_ref (filter);
669 filter->sheet = sheet;
670 sheet->filters = g_slist_prepend (sheet->filters, filter);
671 sheet->priv->filters_changed = TRUE;
673 for (i = 0 ; i < range_width (&(filter->r)); i++)
674 gnm_filter_add_field (filter, i);
679 * gnm_filter_new:
680 * @sheet:
681 * @r:
683 * Init a filter and add it to @sheet
685 GnmFilter *
686 gnm_filter_new (Sheet *sheet, GnmRange const *r)
688 GnmFilter *filter;
690 g_return_val_if_fail (IS_SHEET (sheet), NULL);
691 g_return_val_if_fail (r != NULL, NULL);
693 filter = g_new0 (GnmFilter, 1);
695 filter->is_active = FALSE;
696 filter->r = *r;
697 filter->fields = g_ptr_array_new ();
699 /* This creates the initial ref. */
700 gnm_filter_attach (filter, sheet);
702 return filter;
706 * gnm_filter_dup:
707 * @src: #GnmFilter
708 * @sheet: #Sheet
710 * Duplicate @src into @sheet
712 GnmFilter *
713 gnm_filter_dup (GnmFilter const *src, Sheet *sheet)
715 int i;
716 GnmFilter *dst;
718 g_return_val_if_fail (src != NULL, NULL);
719 g_return_val_if_fail (IS_SHEET (sheet), NULL);
721 dst = g_new0 (GnmFilter, 1);
723 dst->is_active = src->is_active;
724 dst->r = src->r;
725 dst->fields = g_ptr_array_new ();
727 /* This creates the initial ref. */
728 gnm_filter_attach (dst, sheet);
730 for (i = 0 ; i < range_width (&dst->r); i++) {
731 gnm_filter_add_field (dst, i);
732 gnm_filter_set_condition (dst, i,
733 gnm_filter_condition_dup (
734 gnm_filter_get_condition (src, i)),
735 FALSE);
738 return dst;
741 GnmFilter *
742 gnm_filter_ref (GnmFilter *filter)
744 g_return_val_if_fail (filter != NULL, NULL);
745 filter->ref_count++;
746 return filter;
749 void
750 gnm_filter_unref (GnmFilter *filter)
752 g_return_if_fail (filter != NULL);
754 filter->ref_count--;
755 if (filter->ref_count > 0)
756 return;
758 g_ptr_array_free (filter->fields, TRUE);
759 g_free (filter);
762 GType
763 gnm_filter_get_type (void)
765 static GType t = 0;
767 if (t == 0) {
768 t = g_boxed_type_register_static ("GnmFilter",
769 (GBoxedCopyFunc)gnm_filter_ref,
770 (GBoxedFreeFunc)gnm_filter_unref);
772 return t;
775 void
776 gnm_filter_remove (GnmFilter *filter)
778 Sheet *sheet;
779 int i;
781 g_return_if_fail (filter != NULL);
782 g_return_if_fail (filter->sheet != NULL);
784 sheet = filter->sheet;
785 sheet->priv->filters_changed = TRUE;
786 sheet->filters = g_slist_remove (sheet->filters, filter);
787 for (i = filter->r.start.row; ++i <= filter->r.end.row ; ) {
788 ColRowInfo *ri = sheet_row_get (sheet, i);
789 if (ri != NULL) {
790 ri->in_filter = FALSE;
791 colrow_set_visibility (sheet, FALSE, TRUE, i, i);
794 filter->sheet = NULL;
796 for (i = 0 ; i < (int)filter->fields->len ; i++) {
797 SheetObject *so = g_ptr_array_index (filter->fields, i);
798 sheet_object_clear_sheet (so);
799 g_object_unref (so);
801 g_ptr_array_set_size (filter->fields, 0);
805 * gnm_filter_get_condition:
806 * @filter: #GnmFilter
807 * @i: zero-based index
809 * Returns: (transfer none): the @i'th condition of @filter
811 GnmFilterCondition const *
812 gnm_filter_get_condition (GnmFilter const *filter, unsigned i)
814 GnmFilterCombo *fcombo;
816 g_return_val_if_fail (filter != NULL, NULL);
817 g_return_val_if_fail (i < filter->fields->len, NULL);
819 fcombo = g_ptr_array_index (filter->fields, i);
820 return fcombo->cond;
824 * gnm_filter_reapply:
825 * @filter: #GnmFilter
827 * Reapplies @filter after changes.
829 void
830 gnm_filter_reapply (GnmFilter *filter)
832 unsigned i;
834 colrow_set_visibility (filter->sheet, FALSE, TRUE,
835 filter->r.start.row + 1, filter->r.end.row);
836 for (i = 0 ; i < filter->fields->len ; i++)
837 gnm_filter_combo_apply (g_ptr_array_index (filter->fields, i),
838 filter->sheet);
841 static void
842 gnm_filter_update_active (GnmFilter *filter)
844 unsigned i;
845 gboolean old_active = filter->is_active;
847 filter->is_active = FALSE;
848 for (i = 0 ; i < filter->fields->len ; i++) {
849 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, i);
850 if (fcombo->cond != NULL) {
851 filter->is_active = TRUE;
852 break;
856 if (filter->is_active != old_active) {
857 int r;
858 for (r = filter->r.start.row; ++r <= filter->r.end.row ; ) {
859 ColRowInfo *ri = sheet_row_fetch (filter->sheet, r);
860 ri->in_filter = filter->is_active;
867 * gnm_filter_set_condition:
868 * @filter:
869 * @i:
870 * @cond: #GnmFilterCondition
871 * @apply:
873 * Change the @i-th condition of @filter to @cond. If @apply is
874 * %TRUE, @filter is used to set the visibility of the rows in @filter::sheet
876 * Absorbs the reference to @cond.
878 void
879 gnm_filter_set_condition (GnmFilter *filter, unsigned i,
880 GnmFilterCondition *cond,
881 gboolean apply)
883 GnmFilterCombo *fcombo;
884 gboolean existing_cond = FALSE;
886 g_return_if_fail (filter != NULL);
887 g_return_if_fail (i < filter->fields->len);
889 fcombo = g_ptr_array_index (filter->fields, i);
891 if (fcombo->cond != NULL) {
892 existing_cond = TRUE;
893 gnm_filter_condition_free (fcombo->cond);
895 fcombo->cond = cond;
896 g_signal_emit (G_OBJECT (fcombo), signals [COND_CHANGED], 0);
898 if (apply) {
899 /* if there was an existing cond then we need to do
900 * redo the whole filter.
901 * This is because we do not record what elements this
902 * particular field filtered
904 if (existing_cond)
905 gnm_filter_reapply (filter);
906 else
907 /* When adding a new cond all we need to do is
908 * apply that filter */
909 gnm_filter_combo_apply (fcombo, filter->sheet);
912 gnm_filter_update_active (filter);
916 * gnm_filter_overlaps_range:
917 * @filter: #GnmFilter
918 * @r: #GnmRange
920 * Returns: %TRUE if @filter overlaps @r.
922 static gboolean
923 gnm_filter_overlaps_range (GnmFilter const *filter, GnmRange const *r)
925 g_return_val_if_fail (filter != NULL, FALSE);
926 g_return_val_if_fail (r != NULL, FALSE);
928 return range_overlap (&filter->r, r);
931 /*************************************************************************/
934 * gnm_sheet_filter_at_pos:
935 * @sheet: #Sheet
936 * @pos: location to query
938 * Returns: (transfer none) (nullable): #GnmFilter covering @pos.
940 GnmFilter *
941 gnm_sheet_filter_at_pos (Sheet const *sheet, GnmCellPos const *pos)
943 GSList *ptr;
944 GnmRange r;
946 g_return_val_if_fail (IS_SHEET (sheet), NULL);
947 g_return_val_if_fail (NULL != pos, NULL);
949 range_init_cellpos (&r, pos);
950 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
951 if (gnm_filter_overlaps_range (ptr->data, &r))
952 return ptr->data;
954 return NULL;
958 * gnm_sheet_filter_intersect_rows:
959 * @sheet: #Sheet
960 * @from: starting row number
961 * @to: ending row number
963 * Returns: (transfer none) (nullable): the #GnmFilter, if any, that
964 * intersects the rows @from to @to.
966 GnmFilter *
967 gnm_sheet_filter_intersect_rows (Sheet const *sheet, int from, int to)
969 GSList *ptr;
970 GnmRange r;
972 g_return_val_if_fail (IS_SHEET (sheet), NULL);
974 range_init_rows (&r, sheet, from, to);
975 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
976 if (gnm_filter_overlaps_range (ptr->data, &r))
977 return ptr->data;
979 return NULL;
983 * gnm_sheet_filter_can_be_extended:
985 * Returns: (transfer full): #GnmRange
987 GnmRange *
988 gnm_sheet_filter_can_be_extended (G_GNUC_UNUSED Sheet const *sheet,
989 GnmFilter const *f, GnmRange const *r)
991 if (r->start.row < f->r.start.row || r->end.row > f->r.end.row)
992 return NULL;
993 if ((r->end.col > f->r.end.col) ||
994 (r->start.col < f->r.start.col)) {
995 GnmRange *res = g_new (GnmRange, 1);
996 *res = range_union (&f->r, r);
997 return res;
999 return NULL;
1003 /*************************************************************************/
1005 struct cb_remove_col_undo {
1006 unsigned col;
1007 GnmFilterCondition *cond;
1010 static void
1011 cb_remove_col_undo_free (struct cb_remove_col_undo *r)
1013 gnm_filter_condition_free (r->cond);
1014 g_free (r);
1017 static void
1018 cb_remove_col_undo (GnmFilter *filter, struct cb_remove_col_undo *r,
1019 G_GNUC_UNUSED gpointer data)
1021 while (filter->fields->len <= r->col)
1022 gnm_filter_add_field (filter, filter->fields->len);
1023 gnm_filter_set_condition (filter, r->col,
1024 gnm_filter_condition_dup (r->cond),
1025 FALSE);
1028 static void
1029 remove_col (GnmFilter *filter, unsigned col, GOUndo **pundo)
1031 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, col);
1032 if (pundo) {
1033 struct cb_remove_col_undo *r = g_new (struct cb_remove_col_undo, 1);
1034 GOUndo *u;
1036 r->col = col;
1037 r->cond = gnm_filter_condition_dup (fcombo->cond);
1038 u = go_undo_binary_new
1039 (gnm_filter_ref (filter), r,
1040 (GOUndoBinaryFunc)cb_remove_col_undo,
1041 (GFreeFunc)gnm_filter_unref,
1042 (GFreeFunc)cb_remove_col_undo_free);
1043 *pundo = go_undo_combine (*pundo, u);
1045 g_object_unref (fcombo);
1046 g_ptr_array_remove_index (filter->fields, col);
1049 static void
1050 gnm_filter_set_range (GnmFilter *filter, GnmRange *r)
1052 GnmRange old_r = filter->r;
1053 int i;
1054 int start = r->start.col;
1056 filter->r = *r;
1057 for (i = start; i < old_r.start.col; i++)
1058 gnm_filter_add_field (filter, i - start);
1059 for (i = old_r.end.col + 1; i <= r->end.col; i++)
1060 gnm_filter_add_field (filter, i - start);
1064 * gnm_sheet_filter_insdel_colrow:
1065 * @sheet: #Sheet
1066 * @is_cols: %TRUE for columns, %FALSE for rows.
1067 * @is_insert: %TRUE for insert, %FALSE for delete.
1068 * @start: Starting column or row.
1069 * @count: Number of columns or rows.
1070 * @pundo: (out) (optional): location to store undo closures.
1072 * Adjust filters as necessary to handle col/row insertions and deletions
1074 void
1075 gnm_sheet_filter_insdel_colrow (Sheet *sheet,
1076 gboolean is_cols, gboolean is_insert,
1077 int start, int count,
1078 GOUndo **pundo)
1080 GSList *ptr, *filters;
1082 g_return_if_fail (IS_SHEET (sheet));
1084 filters = g_slist_copy (sheet->filters);
1085 for (ptr = filters; ptr != NULL ; ptr = ptr->next) {
1086 GnmFilter *filter = ptr->data;
1087 gboolean kill_filter = FALSE;
1088 gboolean reapply_filter = FALSE;
1089 GnmRange r = filter->r;
1091 if (is_cols) {
1092 if (start > filter->r.end.col) /* a */
1093 continue;
1095 sheet->priv->filters_changed = TRUE;
1097 if (is_insert) {
1098 /* INSERTING COLUMNS */
1099 filter->r.end.col += count;
1100 /* inserting in the middle of a filter adds
1101 * fields. Everything else just moves it */
1102 if (start > filter->r.start.col &&
1103 start <= filter->r.end.col) {
1104 int i;
1105 for (i = 0; i < count; i++)
1106 gnm_filter_add_field (filter,
1107 start - filter->r.start.col + i);
1108 } else
1109 filter->r.start.col += count;
1110 } else {
1111 /* REMOVING COLUMNS */
1112 int start_del = start - filter->r.start.col;
1113 int end_del = start_del + count;
1114 if (start_del <= 0) {
1115 start_del = 0;
1116 if (end_del > 0)
1117 filter->r.start.col = start; /* c */
1118 else
1119 filter->r.start.col -= count; /* b */
1120 filter->r.end.col -= count;
1121 } else {
1122 if ((unsigned)end_del > filter->fields->len) {
1123 end_del = filter->fields->len;
1124 filter->r.end.col = start - 1; /* d */
1125 } else
1126 filter->r.end.col -= count;
1129 if (filter->r.end.col < filter->r.start.col)
1130 kill_filter = TRUE;
1131 else {
1132 while (end_del-- > start_del) {
1133 remove_col (filter, end_del, pundo);
1134 reapply_filter = TRUE;
1138 } else {
1139 if (start > filter->r.end.row)
1140 continue;
1142 sheet->priv->filters_changed = TRUE;
1144 if (is_insert) {
1145 /* INSERTING ROWS */
1146 filter->r.end.row += count;
1147 if (start < filter->r.start.row)
1148 filter->r.start.row += count;
1149 } else {
1150 /* REMOVING ROWS */
1151 if (start <= filter->r.start.row) {
1152 filter->r.end.row -= count;
1153 if (start + count > filter->r.start.row)
1154 /* delete if the dropdowns are wiped */
1155 filter->r.start.row = filter->r.end.row + 1;
1156 else
1157 filter->r.start.row -= count;
1158 } else if (start + count > filter->r.end.row)
1159 filter->r.end.row = start -1;
1160 else
1161 filter->r.end.row -= count;
1163 if (filter->r.end.row < filter->r.start.row)
1164 kill_filter = TRUE;
1168 if (kill_filter) {
1170 * Empty the filter as we need fresh combo boxes
1171 * if we undo.
1173 while (filter->fields->len)
1174 remove_col (filter,
1175 filter->fields->len - 1,
1176 pundo);
1178 /* Restore the filters range */
1179 gnm_filter_remove (filter);
1180 filter->r = r;
1182 if (pundo) {
1183 GOUndo *u = go_undo_binary_new
1184 (gnm_filter_ref (filter),
1185 sheet,
1186 (GOUndoBinaryFunc)gnm_filter_attach,
1187 (GFreeFunc)gnm_filter_unref,
1188 NULL);
1189 *pundo = go_undo_combine (*pundo, u);
1191 gnm_filter_unref (filter);
1192 } else if (reapply_filter) {
1193 GnmRange *range = g_new (GnmRange, 1);
1194 *range = r;
1195 if (pundo) {
1196 GOUndo *u = go_undo_binary_new
1197 (gnm_filter_ref (filter),
1198 range,
1199 (GOUndoBinaryFunc)gnm_filter_set_range,
1200 (GFreeFunc)gnm_filter_unref,
1201 g_free);
1202 *pundo = go_undo_combine (*pundo, u);
1204 gnm_filter_update_active (filter);
1205 gnm_filter_reapply (filter);
1209 g_slist_free (filters);