1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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
23 #include <gnumeric-config.h>
24 #include "libgnumeric.h"
25 #include "sheet-filter.h"
26 #include "sheet-filter-combo.h"
30 #include "sheet-private.h"
34 #include "gnm-format.h"
36 #include "number-match.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>
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
:
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
:
68 * gnm_filter_condition_new_single:
72 * Create a new condition with 1 value.
73 * Absorbs the reference to @v.
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
;
90 * gnm_filter_condition_new_double:
97 * Create a new condition with 2 value.
98 * Absorbs the reference to @v0 and @v1.
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
;
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
;
132 gnm_filter_condition_dup (GnmFilterCondition
const *src
)
134 GnmFilterCondition
*dst
;
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]);
150 gnm_filter_condition_free (GnmFilterCondition
*cond
)
155 value_release (cond
->value
[0]);
156 value_release (cond
->value
[1]);
161 gnm_filter_condition_get_type (void)
166 t
= g_boxed_type_register_static ("GnmFilterCondition",
167 (GBoxedCopyFunc
)gnm_filter_condition_dup
,
168 (GBoxedFreeFunc
)gnm_filter_condition_free
);
173 /*****************************************************************************/
176 GnmFilterCondition
const *cond
;
178 GnmValue
*alt_val
[2];
180 Sheet
*target_sheet
; /* not necessarilly the src */
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
;
203 fexpr
->val
[i
] = format_match_number (str
, NULL
, date_conv
);
204 if (fexpr
->val
[i
] != NULL
)
207 fexpr
->val
[i
] = value_dup (tmp
);
211 filter_expr_release (FilterExpr
*fexpr
, unsigned i
)
213 if (fexpr
->val
[i
] == NULL
)
214 go_regfree (fexpr
->regexp
+ i
);
216 value_release (fexpr
->val
[i
]);
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
);
229 filter_expr_eval (GnmFilterOp op
, GnmValue
const *src
, GORegexp
const *regexp
,
232 GnmValue
*target
= cell
->value
;
234 GnmValue
*fake_val
= NULL
;
237 char *str
= filter_cell_contents (cell
);
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);
247 return op
== GNM_FILTER_OP_EQUAL
;
251 return op
== GNM_FILTER_OP_NOT_EQUAL
;
254 g_warning ("Unexpected regexec result");
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
);
267 cmp
= value_compare (target
, src
, FALSE
);
268 value_release (fake_val
);
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
;
284 cb_filter_expr (GnmCellIter
const *iter
, FilterExpr
const *fexpr
)
286 if (iter
->cell
!= NULL
) {
289 for (ui
= 0; ui
< G_N_ELEMENTS (fexpr
->cond
->op
); ui
++) {
292 if (fexpr
->cond
->op
[ui
] == GNM_FILTER_UNUSED
)
295 res
= filter_expr_eval (fexpr
->cond
->op
[ui
],
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) */
310 colrow_set_visibility (fexpr
->target_sheet
, FALSE
, FALSE
,
311 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
315 /*****************************************************************************/
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
);
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
);
335 /*****************************************************************************/
341 GnmValue
const **vals
;
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
;
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];
360 data
->vals
[data
->elements
++] = v
;
361 if (data
->elements
== data
->count
) {
362 qsort (data
->vals
, data
->elements
,
364 data
->find_max
? value_cmp
: value_cmp_reverse
);
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
;
378 if (data
->vals
[i
] == v
)
381 colrow_set_visibility (data
->target_sheet
, FALSE
, FALSE
,
382 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
386 /*****************************************************************************/
389 gboolean initialized
, find_max
;
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
) {
403 else if (data
->high
< v
)
406 data
->initialized
= TRUE
;
407 data
->low
= data
->high
= v
;
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
) {
427 colrow_set_visibility (data
->target_sheet
, FALSE
, FALSE
,
428 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
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
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
;
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
||
467 cond
->op
[0] == GNM_FILTER_UNUSED
)
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
)) {
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
,
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
) {
508 data
.find_max
= (cond
->op
[0] & 0x1) ? FALSE
: TRUE
;
510 data
.count
= 0.5 + cond
->count
* (end_row
- start_row
+ 1) /100.;
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
);
525 FilterPercentage data
;
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.;
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 */
545 data
.find_max
= (cond
->op
[0] & 0x1) ? FALSE
: TRUE
;
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
);
562 g_warning ("Invalid operator %d", cond
->op
[0]);
570 static guint signals
[LAST_SIGNAL
] = { 0 };
573 SheetObjectClass parent
;
575 void (*cond_changed
) (GnmFilterCombo
*);
576 } GnmFilterComboClass
;
579 gnm_filter_combo_finalize (GObject
*object
)
581 GnmFilterCombo
*fcombo
= GNM_FILTER_COMBO (object
);
582 GObjectClass
*parent
;
584 gnm_filter_condition_free (fcombo
->cond
);
587 parent
= g_type_class_peek (GNM_SO_TYPE
);
588 parent
->finalize (object
);
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
);
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
,
620 G_STRUCT_OFFSET (GnmFilterComboClass
, cond_changed
),
622 g_cclosure_marshal_VOID__VOID
,
626 GSF_CLASS (GnmFilterCombo
, gnm_filter_combo
,
627 gnm_filter_combo_class_init
, gnm_filter_combo_init
,
630 /*************************************************************************/
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. };
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 */
659 gnm_filter_attach (GnmFilter
*filter
, Sheet
*sheet
)
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
);
683 * Init a filter and add it to @sheet
686 gnm_filter_new (Sheet
*sheet
, GnmRange
const *r
)
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
;
697 filter
->fields
= g_ptr_array_new ();
699 /* This creates the initial ref. */
700 gnm_filter_attach (filter
, sheet
);
710 * Duplicate @src into @sheet
713 gnm_filter_dup (GnmFilter
const *src
, Sheet
*sheet
)
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
;
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
)),
742 gnm_filter_ref (GnmFilter
*filter
)
744 g_return_val_if_fail (filter
!= NULL
, NULL
);
750 gnm_filter_unref (GnmFilter
*filter
)
752 g_return_if_fail (filter
!= NULL
);
755 if (filter
->ref_count
> 0)
758 g_ptr_array_free (filter
->fields
, TRUE
);
763 gnm_filter_get_type (void)
768 t
= g_boxed_type_register_static ("GnmFilter",
769 (GBoxedCopyFunc
)gnm_filter_ref
,
770 (GBoxedFreeFunc
)gnm_filter_unref
);
776 gnm_filter_remove (GnmFilter
*filter
)
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
);
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
);
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
);
824 * gnm_filter_reapply:
825 * @filter: #GnmFilter
827 * Reapplies @filter after changes.
830 gnm_filter_reapply (GnmFilter
*filter
)
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
),
842 gnm_filter_update_active (GnmFilter
*filter
)
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
;
856 if (filter
->is_active
!= old_active
) {
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:
870 * @cond: #GnmFilterCondition
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.
879 gnm_filter_set_condition (GnmFilter
*filter
, unsigned i
,
880 GnmFilterCondition
*cond
,
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
);
896 g_signal_emit (G_OBJECT (fcombo
), signals
[COND_CHANGED
], 0);
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
905 gnm_filter_reapply (filter
);
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
920 * Returns: %TRUE if @filter overlaps @r.
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:
936 * @pos: location to query
938 * Returns: (transfer none) (nullable): #GnmFilter covering @pos.
941 gnm_sheet_filter_at_pos (Sheet
const *sheet
, GnmCellPos
const *pos
)
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
))
958 * gnm_sheet_filter_intersect_rows:
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.
967 gnm_sheet_filter_intersect_rows (Sheet
const *sheet
, int from
, int to
)
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
))
983 * gnm_sheet_filter_can_be_extended:
985 * Returns: (transfer full): #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
)
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
);
1003 /*************************************************************************/
1005 struct cb_remove_col_undo
{
1007 GnmFilterCondition
*cond
;
1011 cb_remove_col_undo_free (struct cb_remove_col_undo
*r
)
1013 gnm_filter_condition_free (r
->cond
);
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
),
1029 remove_col (GnmFilter
*filter
, unsigned col
, GOUndo
**pundo
)
1031 GnmFilterCombo
*fcombo
= g_ptr_array_index (filter
->fields
, col
);
1033 struct cb_remove_col_undo
*r
= g_new (struct cb_remove_col_undo
, 1);
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
);
1050 gnm_filter_set_range (GnmFilter
*filter
, GnmRange
*r
)
1052 GnmRange old_r
= filter
->r
;
1054 int start
= r
->start
.col
;
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:
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
1075 gnm_sheet_filter_insdel_colrow (Sheet
*sheet
,
1076 gboolean is_cols
, gboolean is_insert
,
1077 int start
, int count
,
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
;
1092 if (start
> filter
->r
.end
.col
) /* a */
1095 sheet
->priv
->filters_changed
= TRUE
;
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
) {
1105 for (i
= 0; i
< count
; i
++)
1106 gnm_filter_add_field (filter
,
1107 start
- filter
->r
.start
.col
+ i
);
1109 filter
->r
.start
.col
+= count
;
1111 /* REMOVING COLUMNS */
1112 int start_del
= start
- filter
->r
.start
.col
;
1113 int end_del
= start_del
+ count
;
1114 if (start_del
<= 0) {
1117 filter
->r
.start
.col
= start
; /* c */
1119 filter
->r
.start
.col
-= count
; /* b */
1120 filter
->r
.end
.col
-= count
;
1122 if ((unsigned)end_del
> filter
->fields
->len
) {
1123 end_del
= filter
->fields
->len
;
1124 filter
->r
.end
.col
= start
- 1; /* d */
1126 filter
->r
.end
.col
-= count
;
1129 if (filter
->r
.end
.col
< filter
->r
.start
.col
)
1132 while (end_del
-- > start_del
) {
1133 remove_col (filter
, end_del
, pundo
);
1134 reapply_filter
= TRUE
;
1139 if (start
> filter
->r
.end
.row
)
1142 sheet
->priv
->filters_changed
= TRUE
;
1145 /* INSERTING ROWS */
1146 filter
->r
.end
.row
+= count
;
1147 if (start
< filter
->r
.start
.row
)
1148 filter
->r
.start
.row
+= count
;
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;
1157 filter
->r
.start
.row
-= count
;
1158 } else if (start
+ count
> filter
->r
.end
.row
)
1159 filter
->r
.end
.row
= start
-1;
1161 filter
->r
.end
.row
-= count
;
1163 if (filter
->r
.end
.row
< filter
->r
.start
.row
)
1170 * Empty the filter as we need fresh combo boxes
1173 while (filter
->fields
->len
)
1175 filter
->fields
->len
- 1,
1178 /* Restore the filters range */
1179 gnm_filter_remove (filter
);
1183 GOUndo
*u
= go_undo_binary_new
1184 (gnm_filter_ref (filter
),
1186 (GOUndoBinaryFunc
)gnm_filter_attach
,
1187 (GFreeFunc
)gnm_filter_unref
,
1189 *pundo
= go_undo_combine (*pundo
, u
);
1191 gnm_filter_unref (filter
);
1192 } else if (reapply_filter
) {
1193 GnmRange
*range
= g_new (GnmRange
, 1);
1196 GOUndo
*u
= go_undo_binary_new
1197 (gnm_filter_ref (filter
),
1199 (GOUndoBinaryFunc
)gnm_filter_set_range
,
1200 (GFreeFunc
)gnm_filter_unref
,
1202 *pundo
= go_undo_combine (*pundo
, u
);
1204 gnm_filter_update_active (filter
);
1205 gnm_filter_reapply (filter
);
1209 g_slist_free (filters
);