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 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
:
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
:
66 * gnm_filter_condition_new_single :
70 * Create a new condition with 1 value.
71 * Absorbs the reference to @v.
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
;
87 * gnm_filter_condition_new_double :
94 * Create a new condition with 2 value.
95 * Absorbs the reference to @v0 and @v1.
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
;
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
;
127 gnm_filter_condition_dup (GnmFilterCondition
const *src
)
129 GnmFilterCondition
*dst
;
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]);
145 gnm_filter_condition_free (GnmFilterCondition
*cond
)
150 value_release (cond
->value
[0]);
151 value_release (cond
->value
[1]);
156 gnm_filter_condition_get_type (void)
161 t
= g_boxed_type_register_static ("GnmFilterCondition",
162 (GBoxedCopyFunc
)gnm_filter_condition_dup
,
163 (GBoxedFreeFunc
)gnm_filter_condition_free
);
168 /*****************************************************************************/
171 GnmFilterCondition
const *cond
;
173 GnmValue
*alt_val
[2];
175 Sheet
*target_sheet
; /* not necessarilly the src */
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
;
198 fexpr
->val
[i
] = format_match_number (str
, NULL
, date_conv
);
199 if (fexpr
->val
[i
] != NULL
)
202 fexpr
->val
[i
] = value_dup (tmp
);
206 filter_expr_release (FilterExpr
*fexpr
, unsigned i
)
208 if (fexpr
->val
[i
] == NULL
)
209 go_regfree (fexpr
->regexp
+ i
);
211 value_release (fexpr
->val
[i
]);
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
);
224 filter_expr_eval (GnmFilterOp op
, GnmValue
const *src
, GORegexp
const *regexp
,
227 GnmValue
*target
= cell
->value
;
229 GnmValue
*fake_val
= NULL
;
232 char *str
= filter_cell_contents (cell
);
234 int res
= go_regexec (regexp
, str
, 1, &rm
, 0);
235 gboolean whole
= (rm
.rm_so
== 0 && str
[rm
.rm_eo
] == 0);
242 return op
== GNM_FILTER_OP_EQUAL
;
246 return op
== GNM_FILTER_OP_NOT_EQUAL
;
249 g_warning ("Unexpected regexec result");
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
);
262 cmp
= value_compare (target
, src
, FALSE
);
263 value_release (fake_val
);
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
;
279 cb_filter_expr (GnmCellIter
const *iter
, FilterExpr
const *fexpr
)
281 if (iter
->cell
!= NULL
) {
284 for (ui
= 0; ui
< G_N_ELEMENTS (fexpr
->cond
->op
); ui
++) {
287 if (fexpr
->cond
->op
[ui
] == GNM_FILTER_UNUSED
)
290 res
= filter_expr_eval (fexpr
->cond
->op
[ui
],
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) */
305 colrow_set_visibility (fexpr
->target_sheet
, FALSE
, FALSE
,
306 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
310 /*****************************************************************************/
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
);
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
);
330 /*****************************************************************************/
336 GnmValue
const **vals
;
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
;
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];
355 data
->vals
[data
->elements
++] = v
;
356 if (data
->elements
== data
->count
) {
357 qsort (data
->vals
, data
->elements
,
359 data
->find_max
? value_cmp
: value_cmp_reverse
);
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
;
373 if (data
->vals
[i
] == v
)
376 colrow_set_visibility (data
->target_sheet
, FALSE
, FALSE
,
377 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
381 /*****************************************************************************/
384 gboolean initialized
, find_max
;
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
) {
398 else if (data
->high
< v
)
401 data
->initialized
= TRUE
;
402 data
->low
= data
->high
= v
;
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
) {
422 colrow_set_visibility (data
->target_sheet
, FALSE
, FALSE
,
423 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
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
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
;
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
||
462 cond
->op
[0] == GNM_FILTER_UNUSED
)
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
)) {
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
,
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
) {
503 data
.find_max
= (cond
->op
[0] & 0x1) ? FALSE
: TRUE
;
505 data
.count
= 0.5 + cond
->count
* (end_row
- start_row
+ 1) /100.;
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
);
519 FilterPercentage data
;
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.;
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 */
539 data
.find_max
= (cond
->op
[0] & 0x1) ? FALSE
: TRUE
;
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
);
554 g_warning ("Invalid operator %d", cond
->op
[0]);
562 static guint signals
[LAST_SIGNAL
] = { 0 };
565 SheetObjectClass parent
;
567 void (*cond_changed
) (GnmFilterCombo
*);
568 } GnmFilterComboClass
;
571 gnm_filter_combo_finalize (GObject
*object
)
573 GnmFilterCombo
*fcombo
= GNM_FILTER_COMBO (object
);
574 GObjectClass
*parent
;
576 gnm_filter_condition_free (fcombo
->cond
);
579 parent
= g_type_class_peek (GNM_SO_TYPE
);
580 parent
->finalize (object
);
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
);
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
,
612 G_STRUCT_OFFSET (GnmFilterComboClass
, cond_changed
),
614 g_cclosure_marshal_VOID__VOID
,
618 GSF_CLASS (GnmFilterCombo
, gnm_filter_combo
,
619 gnm_filter_combo_class_init
, gnm_filter_combo_init
,
622 /*************************************************************************/
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. };
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 */
651 gnm_filter_attach (GnmFilter
*filter
, Sheet
*sheet
)
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
);
675 * Init a filter and add it to @sheet
678 gnm_filter_new (Sheet
*sheet
, GnmRange
const *r
)
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
;
689 filter
->fields
= g_ptr_array_new ();
691 /* This creates the initial ref. */
692 gnm_filter_attach (filter
, sheet
);
702 * Duplicate @src into @sheet
705 gnm_filter_dup (GnmFilter
const *src
, Sheet
*sheet
)
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
;
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
)),
734 gnm_filter_ref (GnmFilter
*filter
)
736 g_return_val_if_fail (filter
!= NULL
, NULL
);
742 gnm_filter_unref (GnmFilter
*filter
)
744 g_return_if_fail (filter
!= NULL
);
747 if (filter
->ref_count
> 0)
750 g_ptr_array_free (filter
->fields
, TRUE
);
755 gnm_filter_get_type (void)
760 t
= g_boxed_type_register_static ("GnmFilter",
761 (GBoxedCopyFunc
)gnm_filter_ref
,
762 (GBoxedFreeFunc
)gnm_filter_unref
);
768 gnm_filter_remove (GnmFilter
*filter
)
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
);
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
);
793 g_ptr_array_set_size (filter
->fields
, 0);
797 * gnm_filter_get_condition :
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
);
815 gnm_filter_reapply (GnmFilter
*filter
)
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
),
827 gnm_filter_update_active (GnmFilter
*filter
)
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
;
841 if (filter
->is_active
!= old_active
) {
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 :
855 * @cond: #GnmFilterCondition
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.
864 gnm_filter_set_condition (GnmFilter
*filter
, unsigned i
,
865 GnmFilterCondition
*cond
,
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
);
881 g_signal_emit (G_OBJECT (fcombo
), signals
[COND_CHANGED
], 0);
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
890 gnm_filter_reapply (filter
);
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
905 * Returns: %TRUE if @filter overlaps @r.
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 :
922 * Returns : #GnmRange
925 gnm_sheet_filter_at_pos (Sheet
const *sheet
, GnmCellPos
const *pos
)
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
))
942 * gnm_sheet_filter_intersect_rows:
944 * @from: starting row number
945 * @to: ending row number
947 * Returns: the filter, if any, that intersect the rows @from to @to
950 gnm_sheet_filter_intersect_rows (Sheet
const *sheet
, int from
, int to
)
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
))
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
)
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
);
981 /*************************************************************************/
983 struct cb_remove_col_undo
{
985 GnmFilterCondition
*cond
;
989 cb_remove_col_undo_free (struct cb_remove_col_undo
*r
)
991 gnm_filter_condition_free (r
->cond
);
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
),
1007 remove_col (GnmFilter
*filter
, unsigned col
, GOUndo
**pundo
)
1009 GnmFilterCombo
*fcombo
= g_ptr_array_index (filter
->fields
, col
);
1011 struct cb_remove_col_undo
*r
= g_new (struct cb_remove_col_undo
, 1);
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
);
1028 gnm_filter_set_range (GnmFilter
*filter
, GnmRange
*r
)
1030 GnmRange old_r
= filter
->r
;
1032 int start
= r
->start
.col
;
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 :
1048 * @pundo: location to store undo closures.
1050 * Adjust filters as necessary to handle col/row insertions and deletions
1053 gnm_sheet_filter_insdel_colrow (Sheet
*sheet
,
1054 gboolean is_cols
, gboolean is_insert
,
1055 int start
, int count
,
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
;
1070 if (start
> filter
->r
.end
.col
) /* a */
1073 sheet
->priv
->filters_changed
= TRUE
;
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
) {
1083 for (i
= 0; i
< count
; i
++)
1084 gnm_filter_add_field (filter
,
1085 start
- filter
->r
.start
.col
+ i
);
1087 filter
->r
.start
.col
+= count
;
1089 /* REMOVING COLUMNS */
1090 int start_del
= start
- filter
->r
.start
.col
;
1091 int end_del
= start_del
+ count
;
1092 if (start_del
<= 0) {
1095 filter
->r
.start
.col
= start
; /* c */
1097 filter
->r
.start
.col
-= count
; /* b */
1098 filter
->r
.end
.col
-= count
;
1100 if ((unsigned)end_del
> filter
->fields
->len
) {
1101 end_del
= filter
->fields
->len
;
1102 filter
->r
.end
.col
= start
- 1; /* d */
1104 filter
->r
.end
.col
-= count
;
1107 if (filter
->r
.end
.col
< filter
->r
.start
.col
)
1110 while (end_del
-- > start_del
) {
1111 remove_col (filter
, end_del
, pundo
);
1112 reapply_filter
= TRUE
;
1117 if (start
> filter
->r
.end
.row
)
1120 sheet
->priv
->filters_changed
= TRUE
;
1123 /* INSERTING ROWS */
1124 filter
->r
.end
.row
+= count
;
1125 if (start
< filter
->r
.start
.row
)
1126 filter
->r
.start
.row
+= count
;
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;
1135 filter
->r
.start
.row
-= count
;
1136 } else if (start
+ count
> filter
->r
.end
.row
)
1137 filter
->r
.end
.row
= start
-1;
1139 filter
->r
.end
.row
-= count
;
1141 if (filter
->r
.end
.row
< filter
->r
.start
.row
)
1148 * Empty the filter as we need fresh combo boxes
1151 while (filter
->fields
->len
)
1153 filter
->fields
->len
- 1,
1156 /* Restore the filters range */
1157 gnm_filter_remove (filter
);
1161 GOUndo
*u
= go_undo_binary_new
1162 (gnm_filter_ref (filter
),
1164 (GOUndoBinaryFunc
)gnm_filter_attach
,
1165 (GFreeFunc
)gnm_filter_unref
,
1167 *pundo
= go_undo_combine (*pundo
, u
);
1169 gnm_filter_unref (filter
);
1170 } else if (reapply_filter
) {
1171 GnmRange
*range
= g_new (GnmRange
, 1);
1174 GOUndo
*u
= go_undo_binary_new
1175 (gnm_filter_ref (filter
),
1177 (GOUndoBinaryFunc
)gnm_filter_set_range
,
1178 (GFreeFunc
)gnm_filter_unref
,
1180 *pundo
= go_undo_combine (*pundo
, u
);
1182 gnm_filter_update_active (filter
);
1183 gnm_filter_reapply (filter
);
1187 g_slist_free (filters
);