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
22 #include <gnumeric-config.h>
23 #include <libgnumeric.h>
24 #include <sheet-filter.h>
25 #include <sheet-filter-combo.h>
29 #include <sheet-private.h>
33 #include <gnm-format.h>
35 #include <number-match.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>
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
:
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
:
67 * gnm_filter_condition_new_single:
71 * Create a new condition with 1 value.
72 * Absorbs the reference to @v.
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
;
89 * gnm_filter_condition_new_double:
96 * Create a new condition with 2 value.
97 * Absorbs the reference to @v0 and @v1.
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
;
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
;
131 gnm_filter_condition_dup (GnmFilterCondition
const *src
)
133 GnmFilterCondition
*dst
;
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]);
149 gnm_filter_condition_free (GnmFilterCondition
*cond
)
154 value_release (cond
->value
[0]);
155 value_release (cond
->value
[1]);
160 gnm_filter_condition_get_type (void)
165 t
= g_boxed_type_register_static ("GnmFilterCondition",
166 (GBoxedCopyFunc
)gnm_filter_condition_dup
,
167 (GBoxedFreeFunc
)gnm_filter_condition_free
);
172 /*****************************************************************************/
175 GnmFilterCondition
const *cond
;
177 GnmValue
*alt_val
[2];
179 Sheet
*target_sheet
; /* not necessarilly the src */
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
;
202 fexpr
->val
[i
] = format_match_number (str
, NULL
, date_conv
);
203 if (fexpr
->val
[i
] != NULL
)
206 fexpr
->val
[i
] = value_dup (tmp
);
210 filter_expr_release (FilterExpr
*fexpr
, unsigned i
)
212 if (fexpr
->val
[i
] == NULL
)
213 go_regfree (fexpr
->regexp
+ i
);
215 value_release (fexpr
->val
[i
]);
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
);
228 filter_expr_eval (GnmFilterOp op
, GnmValue
const *src
, GORegexp
const *regexp
,
231 GnmValue
*target
= cell
->value
;
233 GnmValue
*fake_val
= NULL
;
236 char *str
= filter_cell_contents (cell
);
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);
246 return op
== GNM_FILTER_OP_EQUAL
;
250 return op
== GNM_FILTER_OP_NOT_EQUAL
;
253 g_warning ("Unexpected regexec result");
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
);
266 cmp
= value_compare (target
, src
, FALSE
);
267 value_release (fake_val
);
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
;
283 cb_filter_expr (GnmCellIter
const *iter
, FilterExpr
const *fexpr
)
285 if (iter
->cell
!= NULL
) {
288 for (ui
= 0; ui
< G_N_ELEMENTS (fexpr
->cond
->op
); ui
++) {
291 if (fexpr
->cond
->op
[ui
] == GNM_FILTER_UNUSED
)
294 res
= filter_expr_eval (fexpr
->cond
->op
[ui
],
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) */
309 colrow_set_visibility (fexpr
->target_sheet
, FALSE
, FALSE
,
310 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
314 /*****************************************************************************/
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
);
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
);
334 /*****************************************************************************/
340 GnmValue
const **vals
;
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
;
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];
359 data
->vals
[data
->elements
++] = v
;
360 if (data
->elements
== data
->count
) {
361 qsort (data
->vals
, data
->elements
,
363 data
->find_max
? value_cmp
: value_cmp_reverse
);
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
;
377 if (data
->vals
[i
] == v
)
380 colrow_set_visibility (data
->target_sheet
, FALSE
, FALSE
,
381 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
385 /*****************************************************************************/
388 gboolean initialized
, find_max
;
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
) {
402 else if (data
->high
< v
)
405 data
->initialized
= TRUE
;
406 data
->low
= data
->high
= v
;
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
) {
426 colrow_set_visibility (data
->target_sheet
, FALSE
, FALSE
,
427 iter
->pp
.eval
.row
, iter
->pp
.eval
.row
);
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
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
;
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
||
466 cond
->op
[0] == GNM_FILTER_UNUSED
)
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
)) {
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
,
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
) {
507 data
.find_max
= (cond
->op
[0] & 0x1) ? FALSE
: TRUE
;
509 data
.count
= 0.5 + cond
->count
* (end_row
- start_row
+ 1) /100.;
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
);
524 FilterPercentage data
;
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.;
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 */
544 data
.find_max
= (cond
->op
[0] & 0x1) ? FALSE
: TRUE
;
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
);
561 g_warning ("Invalid operator %d", cond
->op
[0]);
569 static guint signals
[LAST_SIGNAL
] = { 0 };
572 SheetObjectClass parent
;
574 void (*cond_changed
) (GnmFilterCombo
*);
575 } GnmFilterComboClass
;
578 gnm_filter_combo_finalize (GObject
*object
)
580 GnmFilterCombo
*fcombo
= GNM_FILTER_COMBO (object
);
581 GObjectClass
*parent
;
583 gnm_filter_condition_free (fcombo
->cond
);
586 parent
= g_type_class_peek (GNM_SO_TYPE
);
587 parent
->finalize (object
);
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
);
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
,
619 G_STRUCT_OFFSET (GnmFilterComboClass
, cond_changed
),
621 g_cclosure_marshal_VOID__VOID
,
625 GSF_CLASS (GnmFilterCombo
, gnm_filter_combo
,
626 gnm_filter_combo_class_init
, gnm_filter_combo_init
,
629 /*************************************************************************/
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. };
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 */
658 gnm_filter_attach (GnmFilter
*filter
, Sheet
*sheet
)
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
);
682 * Init a filter and add it to @sheet
685 gnm_filter_new (Sheet
*sheet
, GnmRange
const *r
)
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
;
696 filter
->fields
= g_ptr_array_new ();
698 /* This creates the initial ref. */
699 gnm_filter_attach (filter
, sheet
);
709 * Duplicate @src into @sheet
712 gnm_filter_dup (GnmFilter
const *src
, Sheet
*sheet
)
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
;
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
)),
741 gnm_filter_ref (GnmFilter
*filter
)
743 g_return_val_if_fail (filter
!= NULL
, NULL
);
749 gnm_filter_unref (GnmFilter
*filter
)
751 g_return_if_fail (filter
!= NULL
);
754 if (filter
->ref_count
> 0)
757 g_ptr_array_free (filter
->fields
, TRUE
);
762 gnm_filter_get_type (void)
767 t
= g_boxed_type_register_static ("GnmFilter",
768 (GBoxedCopyFunc
)gnm_filter_ref
,
769 (GBoxedFreeFunc
)gnm_filter_unref
);
775 gnm_filter_remove (GnmFilter
*filter
)
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
);
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
);
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
);
823 * gnm_filter_reapply:
824 * @filter: #GnmFilter
826 * Reapplies @filter after changes.
829 gnm_filter_reapply (GnmFilter
*filter
)
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
),
841 gnm_filter_update_active (GnmFilter
*filter
)
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
;
855 if (filter
->is_active
!= old_active
) {
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:
869 * @cond: #GnmFilterCondition
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.
878 gnm_filter_set_condition (GnmFilter
*filter
, unsigned i
,
879 GnmFilterCondition
*cond
,
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
);
895 g_signal_emit (G_OBJECT (fcombo
), signals
[COND_CHANGED
], 0);
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
904 gnm_filter_reapply (filter
);
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
919 * Returns: %TRUE if @filter overlaps @r.
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:
935 * @pos: location to query
937 * Returns: (transfer none) (nullable): #GnmFilter covering @pos.
940 gnm_sheet_filter_at_pos (Sheet
const *sheet
, GnmCellPos
const *pos
)
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
))
957 * gnm_sheet_filter_intersect_rows:
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.
966 gnm_sheet_filter_intersect_rows (Sheet
const *sheet
, int from
, int to
)
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
))
982 * gnm_sheet_filter_can_be_extended:
984 * Returns: (transfer full): #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
)
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
);
1002 /*************************************************************************/
1004 struct cb_remove_col_undo
{
1006 GnmFilterCondition
*cond
;
1010 cb_remove_col_undo_free (struct cb_remove_col_undo
*r
)
1012 gnm_filter_condition_free (r
->cond
);
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
),
1028 remove_col (GnmFilter
*filter
, unsigned col
, GOUndo
**pundo
)
1030 GnmFilterCombo
*fcombo
= g_ptr_array_index (filter
->fields
, col
);
1032 struct cb_remove_col_undo
*r
= g_new (struct cb_remove_col_undo
, 1);
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
);
1049 gnm_filter_set_range (GnmFilter
*filter
, GnmRange
*r
)
1051 GnmRange old_r
= filter
->r
;
1053 int start
= r
->start
.col
;
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:
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
1074 gnm_sheet_filter_insdel_colrow (Sheet
*sheet
,
1075 gboolean is_cols
, gboolean is_insert
,
1076 int start
, int count
,
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
;
1091 if (start
> filter
->r
.end
.col
) /* a */
1094 sheet
->priv
->filters_changed
= TRUE
;
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
) {
1104 for (i
= 0; i
< count
; i
++)
1105 gnm_filter_add_field (filter
,
1106 start
- filter
->r
.start
.col
+ i
);
1108 filter
->r
.start
.col
+= count
;
1110 /* REMOVING COLUMNS */
1111 int start_del
= start
- filter
->r
.start
.col
;
1112 int end_del
= start_del
+ count
;
1113 if (start_del
<= 0) {
1116 filter
->r
.start
.col
= start
; /* c */
1118 filter
->r
.start
.col
-= count
; /* b */
1119 filter
->r
.end
.col
-= count
;
1121 if ((unsigned)end_del
> filter
->fields
->len
) {
1122 end_del
= filter
->fields
->len
;
1123 filter
->r
.end
.col
= start
- 1; /* d */
1125 filter
->r
.end
.col
-= count
;
1128 if (filter
->r
.end
.col
< filter
->r
.start
.col
)
1131 while (end_del
-- > start_del
) {
1132 remove_col (filter
, end_del
, pundo
);
1133 reapply_filter
= TRUE
;
1138 if (start
> filter
->r
.end
.row
)
1141 sheet
->priv
->filters_changed
= TRUE
;
1144 /* INSERTING ROWS */
1145 filter
->r
.end
.row
+= count
;
1146 if (start
< filter
->r
.start
.row
)
1147 filter
->r
.start
.row
+= count
;
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;
1156 filter
->r
.start
.row
-= count
;
1157 } else if (start
+ count
> filter
->r
.end
.row
)
1158 filter
->r
.end
.row
= start
-1;
1160 filter
->r
.end
.row
-= count
;
1162 if (filter
->r
.end
.row
< filter
->r
.start
.row
)
1169 * Empty the filter as we need fresh combo boxes
1172 while (filter
->fields
->len
)
1174 filter
->fields
->len
- 1,
1177 /* Restore the filters range */
1178 gnm_filter_remove (filter
);
1182 GOUndo
*u
= go_undo_binary_new
1183 (gnm_filter_ref (filter
),
1185 (GOUndoBinaryFunc
)gnm_filter_attach
,
1186 (GFreeFunc
)gnm_filter_unref
,
1188 *pundo
= go_undo_combine (*pundo
, u
);
1190 gnm_filter_unref (filter
);
1191 } else if (reapply_filter
) {
1192 GnmRange
*range
= g_new (GnmRange
, 1);
1195 GOUndo
*u
= go_undo_binary_new
1196 (gnm_filter_ref (filter
),
1198 (GOUndoBinaryFunc
)gnm_filter_set_range
,
1199 (GFreeFunc
)gnm_filter_unref
,
1201 *pundo
= go_undo_combine (*pundo
, u
);
1203 gnm_filter_update_active (filter
);
1204 gnm_filter_reapply (filter
);
1208 g_slist_free (filters
);