1
2 /*
3 * sheet-filter.c: support for 'auto-filters'
4 *
5 * Copyright (C) 2002-2006 Jody Goldberg (jody@gnome.org)
6 *
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.
11 *
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.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #include <gnumeric-config.h>
23 #include <libgnumeric.h>
24 #include <sheet-filter.h>
25 #include <sheet-filter-combo.h>
26
27 #include <workbook.h>
28 #include <sheet.h>
29 #include <sheet-private.h>
30 #include <sheet-view.h>
31 #include <sheet-control.h>
32 #include <cell.h>
33 #include <expr.h>
34 #include <value.h>
35 #include <gnm-format.h>
36 #include <ranges.h>
37 #include <number-match.h>
38 #include <gutils.h>
39 #include <sheet-object.h>
40 #include <widgets/gnm-filter-combo-view.h>
41 #include <widgets/gnm-cell-combo-view.h>
42 #include <gsf/gsf-impl-utils.h>
43
44 #include <glib/gi18n-lib.h>
45 #include <stdlib.h>
46 #include <string.h>
47
48 static gboolean
gnm_filter_op_needs_value(GnmFilterOp op)49 gnm_filter_op_needs_value (GnmFilterOp op)
50 {
51 g_return_val_if_fail (op != GNM_FILTER_UNUSED, FALSE);
52
53 switch (op & GNM_FILTER_OP_TYPE_MASK) {
54 case GNM_FILTER_OP_TYPE_OP:
55 case GNM_FILTER_OP_TYPE_BUCKETS:
56 case GNM_FILTER_OP_TYPE_MATCH:
57 return TRUE;
58 default:
59 g_assert_not_reached ();
60 case GNM_FILTER_OP_TYPE_BLANKS:
61 case GNM_FILTER_OP_TYPE_AVERAGE:
62 case GNM_FILTER_OP_TYPE_STDDEV:
63 return FALSE;
64 }
65 }
66
67
68 /**
69 * gnm_filter_condition_new_single:
70 * @op: #GnmFilterOp
71 * @v: #GnmValue
72 *
73 * Create a new condition with 1 value.
74 * Absorbs the reference to @v.
75 **/
76 GnmFilterCondition *
gnm_filter_condition_new_single(GnmFilterOp op,GnmValue * v)77 gnm_filter_condition_new_single (GnmFilterOp op, GnmValue *v)
78 {
79 GnmFilterCondition *res;
80
81 g_return_val_if_fail ((v != NULL) == gnm_filter_op_needs_value (op),
82 (value_release (v), NULL));
83
84 res = g_new0 (GnmFilterCondition, 1);
85 res->op[0] = op; res->op[1] = GNM_FILTER_UNUSED;
86 res->value[0] = v;
87 return res;
88 }
89
90 /**
91 * gnm_filter_condition_new_double:
92 * @op0: #GnmFilterOp
93 * @v0: #GnmValue
94 * @join_with_and:
95 * @op1: #GnmFilterOp
96 * @v1: #GnmValue
97 *
98 * Create a new condition with 2 value.
99 * Absorbs the reference to @v0 and @v1.
100 **/
101 GnmFilterCondition *
gnm_filter_condition_new_double(GnmFilterOp op0,GnmValue * v0,gboolean join_with_and,GnmFilterOp op1,GnmValue * v1)102 gnm_filter_condition_new_double (GnmFilterOp op0, GnmValue *v0,
103 gboolean join_with_and,
104 GnmFilterOp op1, GnmValue *v1)
105 {
106 GnmFilterCondition *res;
107
108 g_return_val_if_fail ((v0 != NULL) == gnm_filter_op_needs_value (op0),
109 (value_release (v0), value_release (v1), NULL));
110 g_return_val_if_fail ((v1 != NULL) == gnm_filter_op_needs_value (op1),
111 (value_release (v0), value_release (v1), NULL));
112
113 res = g_new0 (GnmFilterCondition, 1);
114 res->op[0] = op0; res->op[1] = op1;
115 res->is_and = join_with_and;
116 res->value[0] = v0; res->value[1] = v1;
117 return res;
118 }
119
120 GnmFilterCondition *
gnm_filter_condition_new_bucket(gboolean top,gboolean absolute,gboolean rel_range,double n)121 gnm_filter_condition_new_bucket (gboolean top, gboolean absolute,
122 gboolean rel_range, double n)
123 {
124 GnmFilterCondition *res = g_new0 (GnmFilterCondition, 1);
125 res->op[0] = GNM_FILTER_OP_TOP_N | (top ? 0 : 1) |
126 (absolute ? 0 : (rel_range ? 2 : 4));
127 res->op[1] = GNM_FILTER_UNUSED;
128 res->count = n;
129 return res;
130 }
131
132 GnmFilterCondition *
gnm_filter_condition_dup(GnmFilterCondition const * src)133 gnm_filter_condition_dup (GnmFilterCondition const *src)
134 {
135 GnmFilterCondition *dst;
136
137 if (src == NULL)
138 return NULL;
139
140 dst = g_new0 (GnmFilterCondition, 1);
141 dst->op[0] = src->op[0];
142 dst->op[1] = src->op[1];
143 dst->is_and = src->is_and;
144 dst->count = src->count;
145 dst->value[0] = value_dup (src->value[0]);
146 dst->value[1] = value_dup (src->value[1]);
147 return dst;
148 }
149
150 void
gnm_filter_condition_free(GnmFilterCondition * cond)151 gnm_filter_condition_free (GnmFilterCondition *cond)
152 {
153 if (cond == NULL)
154 return;
155
156 value_release (cond->value[0]);
157 value_release (cond->value[1]);
158 g_free (cond);
159 }
160
161 GType
gnm_filter_condition_get_type(void)162 gnm_filter_condition_get_type (void)
163 {
164 static GType t = 0;
165
166 if (t == 0) {
167 t = g_boxed_type_register_static ("GnmFilterCondition",
168 (GBoxedCopyFunc)gnm_filter_condition_dup,
169 (GBoxedFreeFunc)gnm_filter_condition_free);
170 }
171 return t;
172 }
173
174 /*****************************************************************************/
175
176 typedef struct {
177 GnmFilterCondition const *cond;
178 GnmValue *val[2];
179 GnmValue *alt_val[2];
180 GORegexp regexp[2];
181 Sheet *target_sheet; /* not necessarilly the src */
182 } FilterExpr;
183
184 static void
filter_expr_init(FilterExpr * fexpr,unsigned i,GnmFilterCondition const * cond,GnmFilter const * filter)185 filter_expr_init (FilterExpr *fexpr, unsigned i,
186 GnmFilterCondition const *cond,
187 GnmFilter const *filter)
188 {
189 GnmValue *tmp = cond->value[i];
190
191 if (tmp && VALUE_IS_STRING (tmp)) {
192 GnmFilterOp op = cond->op[i];
193 char const *str = value_peek_string (tmp);
194 GODateConventions const *date_conv =
195 sheet_date_conv (filter->sheet);
196
197 if ((op == GNM_FILTER_OP_EQUAL || op == GNM_FILTER_OP_NOT_EQUAL) &&
198 gnm_regcomp_XL (fexpr->regexp + i, str, GO_REG_ICASE, TRUE, TRUE) == GO_REG_OK) {
199 /* FIXME: Do we want to anchor at the end above? */
200 fexpr->val[i] = NULL;
201 return;
202 }
203
204 fexpr->val[i] = format_match_number (str, NULL, date_conv);
205 if (fexpr->val[i] != NULL)
206 return;
207 }
208 fexpr->val[i] = value_dup (tmp);
209 }
210
211 static void
filter_expr_release(FilterExpr * fexpr,unsigned i)212 filter_expr_release (FilterExpr *fexpr, unsigned i)
213 {
214 if (fexpr->val[i] == NULL)
215 go_regfree (fexpr->regexp + i);
216 else
217 value_release (fexpr->val[i]);
218 }
219
220 static char *
filter_cell_contents(GnmCell * cell)221 filter_cell_contents (GnmCell *cell)
222 {
223 GOFormat const *format = gnm_cell_get_format (cell);
224 GODateConventions const *date_conv =
225 sheet_date_conv (cell->base.sheet);
226 return format_value (format, cell->value, -1, date_conv);
227 }
228
229 static gboolean
filter_expr_eval(GnmFilterOp op,GnmValue const * src,GORegexp const * regexp,GnmCell * cell)230 filter_expr_eval (GnmFilterOp op, GnmValue const *src, GORegexp const *regexp,
231 GnmCell *cell)
232 {
233 GnmValue *target = cell->value;
234 GnmValDiff cmp;
235 GnmValue *fake_val = NULL;
236
237 if (src == NULL) {
238 char *str = filter_cell_contents (cell);
239 GORegmatch rm;
240 int res = go_regexec (regexp, str, 1, &rm, 0);
241 gboolean whole = (res == GO_REG_OK && rm.rm_so == 0 && str[rm.rm_eo] == 0);
242
243 g_free (str);
244
245 switch (res) {
246 case GO_REG_OK:
247 if (whole)
248 return op == GNM_FILTER_OP_EQUAL;
249 /* fall through */
250
251 case GO_REG_NOMATCH:
252 return op == GNM_FILTER_OP_NOT_EQUAL;
253
254 default:
255 g_warning ("Unexpected regexec result");
256 return FALSE;
257 }
258 }
259
260 if (VALUE_IS_STRING (target) && VALUE_IS_NUMBER (src)) {
261 GODateConventions const *date_conv =
262 sheet_date_conv (cell->base.sheet);
263 char *str = format_value (NULL, src, -1, date_conv);
264 fake_val = value_new_string_nocopy (str);
265 src = fake_val;
266 }
267
268 cmp = value_compare (target, src, FALSE);
269 value_release (fake_val);
270
271 switch (op) {
272 case GNM_FILTER_OP_EQUAL : return cmp == IS_EQUAL;
273 case GNM_FILTER_OP_NOT_EQUAL : return cmp != IS_EQUAL;
274 case GNM_FILTER_OP_GTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
275 case GNM_FILTER_OP_GT : return cmp == IS_GREATER;
276 case GNM_FILTER_OP_LTE : if (cmp == IS_EQUAL) return TRUE; /* fall */
277 case GNM_FILTER_OP_LT : return cmp == IS_LESS;
278 default:
279 g_warning ("Huh?");
280 return FALSE;
281 }
282 }
283
284 static GnmValue *
cb_filter_expr(GnmCellIter const * iter,FilterExpr const * fexpr)285 cb_filter_expr (GnmCellIter const *iter, FilterExpr const *fexpr)
286 {
287 if (iter->cell != NULL) {
288 unsigned int ui;
289
290 for (ui = 0; ui < G_N_ELEMENTS (fexpr->cond->op); ui++) {
291 gboolean res;
292
293 if (fexpr->cond->op[ui] == GNM_FILTER_UNUSED)
294 continue;
295
296 res = filter_expr_eval (fexpr->cond->op[ui],
297 fexpr->val[ui],
298 fexpr->regexp + ui,
299 iter->cell);
300 if (fexpr->cond->is_and && !res)
301 goto nope; /* AND(...,FALSE,...) */
302 if (res && !fexpr->cond->is_and)
303 return NULL; /* OR(...,TRUE,...) */
304 }
305
306 if (fexpr->cond->is_and)
307 return NULL; /* AND(TRUE,...,TRUE) */
308 }
309
310 nope:
311 colrow_set_visibility (fexpr->target_sheet, FALSE, FALSE,
312 iter->pp.eval.row, iter->pp.eval.row);
313 return NULL;
314 }
315
316 /*****************************************************************************/
317
318 static GnmValue *
cb_filter_non_blanks(GnmCellIter const * iter,Sheet * target_sheet)319 cb_filter_non_blanks (GnmCellIter const *iter, Sheet *target_sheet)
320 {
321 if (gnm_cell_is_blank (iter->cell))
322 colrow_set_visibility (target_sheet, FALSE, FALSE,
323 iter->pp.eval.row, iter->pp.eval.row);
324 return NULL;
325 }
326
327 static GnmValue *
cb_filter_blanks(GnmCellIter const * iter,Sheet * target_sheet)328 cb_filter_blanks (GnmCellIter const *iter, Sheet *target_sheet)
329 {
330 if (!gnm_cell_is_blank (iter->cell))
331 colrow_set_visibility (target_sheet, FALSE, FALSE,
332 iter->pp.eval.row, iter->pp.eval.row);
333 return NULL;
334 }
335
336 /*****************************************************************************/
337
338 typedef struct {
339 unsigned count;
340 unsigned elements;
341 gboolean find_max;
342 GnmValue const **vals;
343 Sheet *target_sheet;
344 } FilterItems;
345
346 static GnmValue *
cb_filter_find_items(GnmCellIter const * iter,FilterItems * data)347 cb_filter_find_items (GnmCellIter const *iter, FilterItems *data)
348 {
349 GnmValue const *v = iter->cell->value;
350 if (data->elements >= data->count) {
351 unsigned j, i = data->elements;
352 GnmValDiff const cond = data->find_max ? IS_GREATER : IS_LESS;
353 while (i-- > 0)
354 if (value_compare (v, data->vals[i], TRUE) == cond) {
355 for (j = 0; j < i ; j++)
356 data->vals[j] = data->vals[j+1];
357 data->vals[i] = v;
358 break;
359 }
360 } else {
361 data->vals [data->elements++] = v;
362 if (data->elements == data->count) {
363 qsort (data->vals, data->elements,
364 sizeof (GnmValue *),
365 data->find_max ? value_cmp : value_cmp_reverse);
366 }
367 }
368 return NULL;
369 }
370
371 static GnmValue *
cb_hide_unwanted_items(GnmCellIter const * iter,FilterItems const * data)372 cb_hide_unwanted_items (GnmCellIter const *iter, FilterItems const *data)
373 {
374 if (iter->cell != NULL) {
375 int i = data->elements;
376 GnmValue const *v = iter->cell->value;
377
378 while (i-- > 0)
379 if (data->vals[i] == v)
380 return NULL;
381 }
382 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
383 iter->pp.eval.row, iter->pp.eval.row);
384 return NULL;
385 }
386
387 /*****************************************************************************/
388
389 typedef struct {
390 gboolean initialized, find_max;
391 gnm_float low, high;
392 Sheet *target_sheet;
393 } FilterPercentage;
394
395 static GnmValue *
cb_filter_find_percentage(GnmCellIter const * iter,FilterPercentage * data)396 cb_filter_find_percentage (GnmCellIter const *iter, FilterPercentage *data)
397 {
398 if (VALUE_IS_NUMBER (iter->cell->value)) {
399 gnm_float const v = value_get_as_float (iter->cell->value);
400
401 if (data->initialized) {
402 if (data->low > v)
403 data->low = v;
404 else if (data->high < v)
405 data->high = v;
406 } else {
407 data->initialized = TRUE;
408 data->low = data->high = v;
409 }
410 }
411 return NULL;
412 }
413
414 static GnmValue *
cb_hide_unwanted_percentage(GnmCellIter const * iter,FilterPercentage const * data)415 cb_hide_unwanted_percentage (GnmCellIter const *iter,
416 FilterPercentage const *data)
417 {
418 if (iter->cell != NULL && VALUE_IS_NUMBER (iter->cell->value)) {
419 gnm_float const v = value_get_as_float (iter->cell->value);
420 if (data->find_max) {
421 if (v >= data->high)
422 return NULL;
423 } else {
424 if (v <= data->low)
425 return NULL;
426 }
427 }
428 colrow_set_visibility (data->target_sheet, FALSE, FALSE,
429 iter->pp.eval.row, iter->pp.eval.row);
430 return NULL;
431 }
432 /*****************************************************************************/
433
434 int
gnm_filter_combo_index(GnmFilterCombo * fcombo)435 gnm_filter_combo_index (GnmFilterCombo *fcombo)
436 {
437 g_return_val_if_fail (GNM_IS_FILTER_COMBO (fcombo), 0);
438
439 return (sheet_object_get_range (GNM_SO (fcombo))->start.col -
440 fcombo->filter->r.start.col);
441 }
442
443
444 /**
445 * gnm_filter_combo_apply:
446 * @fcombo: #GnmFilterCombo
447 * @target_sheet: @Sheet
448 *
449 **/
450 void
gnm_filter_combo_apply(GnmFilterCombo * fcombo,Sheet * target_sheet)451 gnm_filter_combo_apply (GnmFilterCombo *fcombo, Sheet *target_sheet)
452 {
453 GnmFilter const *filter;
454 GnmFilterCondition const *cond;
455 int col, start_row, end_row;
456 CellIterFlags iter_flags = CELL_ITER_IGNORE_HIDDEN;
457
458 g_return_if_fail (GNM_IS_FILTER_COMBO (fcombo));
459
460 filter = fcombo->filter;
461 cond = fcombo->cond;
462 col = sheet_object_get_range (GNM_SO (fcombo))->start.col;
463 start_row = filter->r.start.row + 1;
464 end_row = filter->r.end.row;
465
466 if (start_row > end_row ||
467 cond == NULL ||
468 cond->op[0] == GNM_FILTER_UNUSED)
469 return;
470
471 /*
472 * For the combo we filter a temporary sheet using the data from
473 * filter->sheet and need to include everything from the source,
474 * because it has a different set of conditions
475 */
476 if (target_sheet != filter->sheet)
477 iter_flags = CELL_ITER_ALL;
478
479 if (0x10 >= (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
480 FilterExpr data;
481 data.cond = cond;
482 data.target_sheet = target_sheet;
483 filter_expr_init (&data, 0, cond, filter);
484 if (cond->op[1] != GNM_FILTER_UNUSED)
485 filter_expr_init (&data, 1, cond, filter);
486
487 sheet_foreach_cell_in_region (filter->sheet,
488 iter_flags,
489 col, start_row, col, end_row,
490 (CellIterFunc) cb_filter_expr, &data);
491
492 filter_expr_release (&data, 0);
493 if (cond->op[1] != GNM_FILTER_UNUSED)
494 filter_expr_release (&data, 1);
495 } else if (cond->op[0] == GNM_FILTER_OP_BLANKS)
496 sheet_foreach_cell_in_region (filter->sheet,
497 CELL_ITER_IGNORE_HIDDEN,
498 col, start_row, col, end_row,
499 (CellIterFunc) cb_filter_blanks, target_sheet);
500 else if (cond->op[0] == GNM_FILTER_OP_NON_BLANKS)
501 sheet_foreach_cell_in_region (filter->sheet,
502 CELL_ITER_IGNORE_HIDDEN,
503 col, start_row, col, end_row,
504 (CellIterFunc) cb_filter_non_blanks, target_sheet);
505 else if (0x30 == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
506 if (cond->op[0] & GNM_FILTER_OP_PERCENT_MASK) { /* relative */
507 if (cond->op[0] & GNM_FILTER_OP_REL_N_MASK) {
508 FilterItems data;
509 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
510 data.elements = 0;
511 data.count = 0.5 + cond->count * (end_row - start_row + 1) /100.;
512 if (data.count < 1)
513 data.count = 1;
514 data.vals = g_new (GnmValue const *, data.count);
515 sheet_foreach_cell_in_region (filter->sheet,
516 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
517 col, start_row, col, end_row,
518 (CellIterFunc) cb_filter_find_items, &data);
519 data.target_sheet = target_sheet;
520 sheet_foreach_cell_in_region (filter->sheet,
521 CELL_ITER_IGNORE_HIDDEN,
522 col, start_row, col, end_row,
523 (CellIterFunc) cb_hide_unwanted_items, &data);
524 g_free (data.vals);
525 } else {
526 FilterPercentage data;
527 gnm_float offset;
528
529 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
530 data.initialized = FALSE;
531 sheet_foreach_cell_in_region (filter->sheet,
532 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
533 col, start_row, col, end_row,
534 (CellIterFunc) cb_filter_find_percentage, &data);
535 offset = (data.high - data.low) * cond->count / 100.;
536 data.high -= offset;
537 data.low += offset;
538 data.target_sheet = target_sheet;
539 sheet_foreach_cell_in_region (filter->sheet,
540 CELL_ITER_IGNORE_HIDDEN,
541 col, start_row, col, end_row,
542 (CellIterFunc) cb_hide_unwanted_percentage, &data);
543 }
544 } else { /* absolute */
545 FilterItems data;
546 data.find_max = (cond->op[0] & 0x1) ? FALSE : TRUE;
547 data.elements = 0;
548 data.count = cond->count;
549 data.vals = g_new (GnmValue const *, data.count);
550
551 sheet_foreach_cell_in_region (filter->sheet,
552 CELL_ITER_IGNORE_HIDDEN | CELL_ITER_IGNORE_BLANK,
553 col, start_row, col, end_row,
554 (CellIterFunc) cb_filter_find_items, &data);
555 data.target_sheet = target_sheet;
556 sheet_foreach_cell_in_region (filter->sheet,
557 CELL_ITER_IGNORE_HIDDEN,
558 col, start_row, col, end_row,
559 (CellIterFunc) cb_hide_unwanted_items, &data);
560 g_free (data.vals);
561 }
562 } else
563 g_warning ("Invalid operator %d", cond->op[0]);
564 }
565
566 enum {
567 COND_CHANGED,
568 LAST_SIGNAL
569 };
570
571 static guint signals [LAST_SIGNAL] = { 0 };
572
573 typedef struct {
574 SheetObjectClass parent;
575
576 void (*cond_changed) (GnmFilterCombo *);
577 } GnmFilterComboClass;
578
579 static void
gnm_filter_combo_finalize(GObject * object)580 gnm_filter_combo_finalize (GObject *object)
581 {
582 GnmFilterCombo *fcombo = GNM_FILTER_COMBO (object);
583 GObjectClass *parent;
584
585 gnm_filter_condition_free (fcombo->cond);
586 fcombo->cond = NULL;
587
588 parent = g_type_class_peek (GNM_SO_TYPE);
589 parent->finalize (object);
590 }
591
592 static void
gnm_filter_combo_init(SheetObject * so)593 gnm_filter_combo_init (SheetObject *so)
594 {
595 /* keep the arrows from wandering with their cells */
596 so->flags &= ~SHEET_OBJECT_MOVE_WITH_CELLS;
597 }
598 static SheetObjectView *
gnm_filter_combo_view_new(SheetObject * so,SheetObjectViewContainer * container)599 gnm_filter_combo_view_new (SheetObject *so, SheetObjectViewContainer *container)
600 {
601 return gnm_cell_combo_view_new (so,
602 gnm_filter_combo_view_get_type (), container);
603 }
604 static void
gnm_filter_combo_class_init(GObjectClass * gobject_class)605 gnm_filter_combo_class_init (GObjectClass *gobject_class)
606 {
607 SheetObjectClass *so_class = GNM_SO_CLASS (gobject_class);
608
609 /* Object class method overrides */
610 gobject_class->finalize = gnm_filter_combo_finalize;
611
612 /* SheetObject class method overrides */
613 so_class->new_view = gnm_filter_combo_view_new;
614 so_class->write_xml_sax = NULL;
615 so_class->prep_sax_parser = NULL;
616 so_class->copy = NULL;
617
618 signals[COND_CHANGED] = g_signal_new ("cond-changed",
619 GNM_FILTER_COMBO_TYPE,
620 G_SIGNAL_RUN_LAST,
621 G_STRUCT_OFFSET (GnmFilterComboClass, cond_changed),
622 NULL, NULL,
623 g_cclosure_marshal_VOID__VOID,
624 G_TYPE_NONE, 0);
625 }
626
GSF_CLASS(GnmFilterCombo,gnm_filter_combo,gnm_filter_combo_class_init,gnm_filter_combo_init,GNM_SO_TYPE)627 GSF_CLASS (GnmFilterCombo, gnm_filter_combo,
628 gnm_filter_combo_class_init, gnm_filter_combo_init,
629 GNM_SO_TYPE)
630
631 /*************************************************************************/
632
633 static void
634 gnm_filter_add_field (GnmFilter *filter, int i)
635 {
636 /* pretend to fill the cell, then clip the X start later */
637 static double const a_offsets[4] = { .0, .0, 1., 1. };
638 GnmRange tmp;
639 SheetObjectAnchor anchor;
640 GnmFilterCombo *fcombo = g_object_new (GNM_FILTER_COMBO_TYPE, NULL);
641
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);
649
650 #ifdef HAVE_G_PTR_ARRAY_INSERT
651 g_ptr_array_insert (filter->fields, i, fcombo);
652 #else
653 go_ptr_array_insert (filter->fields, fcombo, i);
654 #endif
655 /* We hold a reference to fcombo */
656 }
657
658 void
gnm_filter_attach(GnmFilter * filter,Sheet * sheet)659 gnm_filter_attach (GnmFilter *filter, Sheet *sheet)
660 {
661 int i;
662
663 g_return_if_fail (filter != NULL);
664 g_return_if_fail (filter->sheet == NULL);
665 g_return_if_fail (IS_SHEET (sheet));
666
667 gnm_filter_ref (filter);
668
669 filter->sheet = sheet;
670 sheet->filters = g_slist_prepend (sheet->filters, filter);
671 sheet->priv->filters_changed = TRUE;
672
673 for (i = 0 ; i < range_width (&(filter->r)); i++)
674 gnm_filter_add_field (filter, i);
675 }
676
677
678 /**
679 * gnm_filter_new:
680 * @sheet: #Sheet for which to create the filter.
681 * @r: #GnmRange that the filter covers.
682 * @attach: whether to attach the filter.
683 *
684 * Returns: (transfer full): A new filter.
685 **/
686 GnmFilter *
gnm_filter_new(Sheet * sheet,GnmRange const * r,gboolean attach)687 gnm_filter_new (Sheet *sheet, GnmRange const *r, gboolean attach)
688 {
689 GnmFilter *filter;
690
691 g_return_val_if_fail (IS_SHEET (sheet), NULL);
692 g_return_val_if_fail (r != NULL, NULL);
693
694 filter = g_new0 (GnmFilter, 1);
695
696 filter->is_active = FALSE;
697 filter->r = *r;
698 filter->fields = g_ptr_array_new ();
699
700 if (attach) {
701 /* This creates the initial ref. */
702 gnm_filter_attach (filter, sheet);
703 } else {
704 gnm_filter_ref (filter);
705 }
706
707 return filter;
708 }
709
710 /**
711 * gnm_filter_dup:
712 * @src: #GnmFilter
713 * @sheet: #Sheet
714 *
715 * Duplicate @src into @sheet
716 **/
717 GnmFilter *
gnm_filter_dup(GnmFilter const * src,Sheet * sheet)718 gnm_filter_dup (GnmFilter const *src, Sheet *sheet)
719 {
720 int i;
721 GnmFilter *dst;
722
723 g_return_val_if_fail (src != NULL, NULL);
724 g_return_val_if_fail (IS_SHEET (sheet), NULL);
725
726 dst = g_new0 (GnmFilter, 1);
727
728 dst->is_active = src->is_active;
729 dst->r = src->r;
730 dst->fields = g_ptr_array_new ();
731
732 /* This creates the initial ref. */
733 gnm_filter_attach (dst, sheet);
734
735 for (i = 0 ; i < range_width (&dst->r); i++) {
736 gnm_filter_add_field (dst, i);
737 gnm_filter_set_condition (dst, i,
738 gnm_filter_condition_dup (
739 gnm_filter_get_condition (src, i)),
740 FALSE);
741 }
742
743 return dst;
744 }
745
746 GnmFilter *
gnm_filter_ref(GnmFilter * filter)747 gnm_filter_ref (GnmFilter *filter)
748 {
749 g_return_val_if_fail (filter != NULL, NULL);
750 filter->ref_count++;
751 return filter;
752 }
753
754 void
gnm_filter_unref(GnmFilter * filter)755 gnm_filter_unref (GnmFilter *filter)
756 {
757 g_return_if_fail (filter != NULL);
758
759 filter->ref_count--;
760 if (filter->ref_count > 0)
761 return;
762
763 g_ptr_array_free (filter->fields, TRUE);
764 g_free (filter);
765 }
766
767 GType
gnm_filter_get_type(void)768 gnm_filter_get_type (void)
769 {
770 static GType t = 0;
771
772 if (t == 0) {
773 t = g_boxed_type_register_static ("GnmFilter",
774 (GBoxedCopyFunc)gnm_filter_ref,
775 (GBoxedFreeFunc)gnm_filter_unref);
776 }
777 return t;
778 }
779
780 void
gnm_filter_remove(GnmFilter * filter)781 gnm_filter_remove (GnmFilter *filter)
782 {
783 Sheet *sheet;
784 int i;
785
786 g_return_if_fail (filter != NULL);
787 g_return_if_fail (filter->sheet != NULL);
788
789 sheet = filter->sheet;
790 sheet->priv->filters_changed = TRUE;
791 sheet->filters = g_slist_remove (sheet->filters, filter);
792 for (i = filter->r.start.row; ++i <= filter->r.end.row ; ) {
793 ColRowInfo *ri = sheet_row_get (sheet, i);
794 if (ri != NULL) {
795 ri->in_filter = FALSE;
796 colrow_set_visibility (sheet, FALSE, TRUE, i, i);
797 }
798 }
799 filter->sheet = NULL;
800
801 SHEET_FOREACH_CONTROL
802 (sheet, view, sc, sc_freeze_object_view (sc, TRUE););
803
804 for (i = filter->fields->len; i-- > 0; ) {
805 SheetObject *so = g_ptr_array_index (filter->fields, i);
806 sheet_object_clear_sheet (so);
807 g_object_unref (so);
808 }
809 g_ptr_array_set_size (filter->fields, 0);
810
811 SHEET_FOREACH_CONTROL
812 (sheet, view, sc, sc_freeze_object_view (sc, FALSE););
813 }
814
815 /**
816 * gnm_filter_get_condition:
817 * @filter: #GnmFilter
818 * @i: zero-based index
819 *
820 * Returns: (transfer none): the @i'th condition of @filter
821 **/
822 GnmFilterCondition const *
gnm_filter_get_condition(GnmFilter const * filter,unsigned i)823 gnm_filter_get_condition (GnmFilter const *filter, unsigned i)
824 {
825 GnmFilterCombo *fcombo;
826
827 g_return_val_if_fail (filter != NULL, NULL);
828 g_return_val_if_fail (i < filter->fields->len, NULL);
829
830 fcombo = g_ptr_array_index (filter->fields, i);
831 return fcombo->cond;
832 }
833
834 /**
835 * gnm_filter_reapply:
836 * @filter: #GnmFilter
837 *
838 * Reapplies @filter after changes.
839 **/
840 void
gnm_filter_reapply(GnmFilter * filter)841 gnm_filter_reapply (GnmFilter *filter)
842 {
843 unsigned i;
844
845 colrow_set_visibility (filter->sheet, FALSE, TRUE,
846 filter->r.start.row + 1, filter->r.end.row);
847 for (i = 0 ; i < filter->fields->len ; i++)
848 gnm_filter_combo_apply (g_ptr_array_index (filter->fields, i),
849 filter->sheet);
850 }
851
852 static void
gnm_filter_update_active(GnmFilter * filter)853 gnm_filter_update_active (GnmFilter *filter)
854 {
855 unsigned i;
856 gboolean old_active = filter->is_active;
857
858 filter->is_active = FALSE;
859 for (i = 0 ; i < filter->fields->len ; i++) {
860 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, i);
861 if (fcombo->cond != NULL) {
862 filter->is_active = TRUE;
863 break;
864 }
865 }
866
867 if (filter->is_active != old_active) {
868 int r;
869 for (r = filter->r.start.row; ++r <= filter->r.end.row ; ) {
870 ColRowInfo *ri = sheet_row_fetch (filter->sheet, r);
871 ri->in_filter = filter->is_active;
872 }
873 }
874 }
875
876
877 /**
878 * gnm_filter_set_condition:
879 * @filter:
880 * @i:
881 * @cond: #GnmFilterCondition
882 * @apply:
883 *
884 * Change the @i-th condition of @filter to @cond. If @apply is
885 * %TRUE, @filter is used to set the visibility of the rows in @filter::sheet
886 *
887 * Absorbs the reference to @cond.
888 **/
889 void
gnm_filter_set_condition(GnmFilter * filter,unsigned i,GnmFilterCondition * cond,gboolean apply)890 gnm_filter_set_condition (GnmFilter *filter, unsigned i,
891 GnmFilterCondition *cond,
892 gboolean apply)
893 {
894 GnmFilterCombo *fcombo;
895 gboolean existing_cond = FALSE;
896
897 g_return_if_fail (filter != NULL);
898 g_return_if_fail (i < filter->fields->len);
899
900 fcombo = g_ptr_array_index (filter->fields, i);
901
902 if (fcombo->cond != NULL) {
903 existing_cond = TRUE;
904 gnm_filter_condition_free (fcombo->cond);
905 }
906 fcombo->cond = cond;
907 g_signal_emit (G_OBJECT (fcombo), signals [COND_CHANGED], 0);
908
909 if (apply) {
910 /* if there was an existing cond then we need to do
911 * redo the whole filter.
912 * This is because we do not record what elements this
913 * particular field filtered
914 */
915 if (existing_cond)
916 gnm_filter_reapply (filter);
917 else
918 /* When adding a new cond all we need to do is
919 * apply that filter */
920 gnm_filter_combo_apply (fcombo, filter->sheet);
921 }
922
923 gnm_filter_update_active (filter);
924 }
925
926 /**
927 * gnm_filter_overlaps_range:
928 * @filter: #GnmFilter
929 * @r: #GnmRange
930 *
931 * Returns: %TRUE if @filter overlaps @r.
932 **/
933 static gboolean
gnm_filter_overlaps_range(GnmFilter const * filter,GnmRange const * r)934 gnm_filter_overlaps_range (GnmFilter const *filter, GnmRange const *r)
935 {
936 g_return_val_if_fail (filter != NULL, FALSE);
937 g_return_val_if_fail (r != NULL, FALSE);
938
939 return range_overlap (&filter->r, r);
940 }
941
942 /*************************************************************************/
943
944 /**
945 * gnm_sheet_filter_at_pos:
946 * @sheet: #Sheet
947 * @pos: location to query
948 *
949 * Returns: (transfer none) (nullable): #GnmFilter covering @pos.
950 **/
951 GnmFilter *
gnm_sheet_filter_at_pos(Sheet const * sheet,GnmCellPos const * pos)952 gnm_sheet_filter_at_pos (Sheet const *sheet, GnmCellPos const *pos)
953 {
954 GSList *ptr;
955 GnmRange r;
956
957 g_return_val_if_fail (IS_SHEET (sheet), NULL);
958 g_return_val_if_fail (NULL != pos, NULL);
959
960 range_init_cellpos (&r, pos);
961 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
962 if (gnm_filter_overlaps_range (ptr->data, &r))
963 return ptr->data;
964
965 return NULL;
966 }
967
968 /**
969 * gnm_sheet_filter_intersect_rows:
970 * @sheet: #Sheet
971 * @from: starting row number
972 * @to: ending row number
973 *
974 * Returns: (transfer none) (nullable): the #GnmFilter, if any, that
975 * intersects the rows @from to @to.
976 **/
977 GnmFilter *
gnm_sheet_filter_intersect_rows(Sheet const * sheet,int from,int to)978 gnm_sheet_filter_intersect_rows (Sheet const *sheet, int from, int to)
979 {
980 GSList *ptr;
981 GnmRange r;
982
983 g_return_val_if_fail (IS_SHEET (sheet), NULL);
984
985 range_init_rows (&r, sheet, from, to);
986 for (ptr = sheet->filters; ptr != NULL ; ptr = ptr->next)
987 if (gnm_filter_overlaps_range (ptr->data, &r))
988 return ptr->data;
989
990 return NULL;
991 }
992
993 /**
994 * gnm_sheet_filter_can_be_extended:
995 *
996 * Returns: (transfer full): #GnmRange
997 */
998 GnmRange *
gnm_sheet_filter_can_be_extended(G_GNUC_UNUSED Sheet const * sheet,GnmFilter const * f,GnmRange const * r)999 gnm_sheet_filter_can_be_extended (G_GNUC_UNUSED Sheet const *sheet,
1000 GnmFilter const *f, GnmRange const *r)
1001 {
1002 if (r->start.row < f->r.start.row || r->end.row > f->r.end.row)
1003 return NULL;
1004 if ((r->end.col > f->r.end.col) ||
1005 (r->start.col < f->r.start.col)) {
1006 GnmRange *res = g_new (GnmRange, 1);
1007 *res = range_union (&f->r, r);
1008 return res;
1009 }
1010 return NULL;
1011 }
1012
1013
1014 /*************************************************************************/
1015
1016 struct cb_remove_col_undo {
1017 unsigned col;
1018 GnmFilterCondition *cond;
1019 };
1020
1021 static void
cb_remove_col_undo_free(struct cb_remove_col_undo * r)1022 cb_remove_col_undo_free (struct cb_remove_col_undo *r)
1023 {
1024 gnm_filter_condition_free (r->cond);
1025 g_free (r);
1026 }
1027
1028 static void
cb_remove_col_undo(GnmFilter * filter,struct cb_remove_col_undo * r,G_GNUC_UNUSED gpointer data)1029 cb_remove_col_undo (GnmFilter *filter, struct cb_remove_col_undo *r,
1030 G_GNUC_UNUSED gpointer data)
1031 {
1032 while (filter->fields->len <= r->col)
1033 gnm_filter_add_field (filter, filter->fields->len);
1034 gnm_filter_set_condition (filter, r->col,
1035 gnm_filter_condition_dup (r->cond),
1036 FALSE);
1037 }
1038
1039 static void
remove_col(GnmFilter * filter,unsigned col,GOUndo ** pundo)1040 remove_col (GnmFilter *filter, unsigned col, GOUndo **pundo)
1041 {
1042 GnmFilterCombo *fcombo = g_ptr_array_index (filter->fields, col);
1043 if (pundo) {
1044 struct cb_remove_col_undo *r = g_new (struct cb_remove_col_undo, 1);
1045 GOUndo *u;
1046
1047 r->col = col;
1048 r->cond = gnm_filter_condition_dup (fcombo->cond);
1049 u = go_undo_binary_new
1050 (gnm_filter_ref (filter), r,
1051 (GOUndoBinaryFunc)cb_remove_col_undo,
1052 (GFreeFunc)gnm_filter_unref,
1053 (GFreeFunc)cb_remove_col_undo_free);
1054 *pundo = go_undo_combine (*pundo, u);
1055 }
1056 g_object_unref (fcombo);
1057 g_ptr_array_remove_index (filter->fields, col);
1058 }
1059
1060 static void
gnm_filter_set_range(GnmFilter * filter,GnmRange * r)1061 gnm_filter_set_range (GnmFilter *filter, GnmRange *r)
1062 {
1063 GnmRange old_r = filter->r;
1064 int i;
1065 int start = r->start.col;
1066
1067 filter->r = *r;
1068 for (i = start; i < old_r.start.col; i++)
1069 gnm_filter_add_field (filter, i - start);
1070 for (i = old_r.end.col + 1; i <= r->end.col; i++)
1071 gnm_filter_add_field (filter, i - start);
1072 }
1073
1074 /**
1075 * gnm_sheet_filter_insdel_colrow:
1076 * @sheet: #Sheet
1077 * @is_cols: %TRUE for columns, %FALSE for rows.
1078 * @is_insert: %TRUE for insert, %FALSE for delete.
1079 * @start: Starting column or row.
1080 * @count: Number of columns or rows.
1081 * @pundo: (out) (optional): location to store undo closures.
1082 *
1083 * Adjust filters as necessary to handle col/row insertions and deletions
1084 **/
1085 void
gnm_sheet_filter_insdel_colrow(Sheet * sheet,gboolean is_cols,gboolean is_insert,int start,int count,GOUndo ** pundo)1086 gnm_sheet_filter_insdel_colrow (Sheet *sheet,
1087 gboolean is_cols, gboolean is_insert,
1088 int start, int count,
1089 GOUndo **pundo)
1090 {
1091 GSList *ptr, *filters;
1092
1093 g_return_if_fail (IS_SHEET (sheet));
1094
1095 filters = g_slist_copy (sheet->filters);
1096 for (ptr = filters; ptr != NULL ; ptr = ptr->next) {
1097 GnmFilter *filter = ptr->data;
1098 gboolean kill_filter = FALSE;
1099 gboolean reapply_filter = FALSE;
1100 GnmRange r = filter->r;
1101
1102 if (is_cols) {
1103 if (start > filter->r.end.col) /* a */
1104 continue;
1105
1106 sheet->priv->filters_changed = TRUE;
1107
1108 if (is_insert) {
1109 /* INSERTING COLUMNS */
1110 filter->r.end.col =
1111 MIN (gnm_sheet_get_last_col (sheet),
1112 filter->r.end.col + count);
1113 /* inserting in the middle of a filter adds
1114 * fields. Everything else just moves it */
1115 if (start > filter->r.start.col &&
1116 start <= filter->r.end.col) {
1117 int i;
1118 for (i = 0; i < count; i++)
1119 gnm_filter_add_field (filter,
1120 start - filter->r.start.col + i);
1121 } else
1122 filter->r.start.col += count;
1123 } else {
1124 /* REMOVING COLUMNS */
1125 int start_del = start - filter->r.start.col;
1126 int end_del = start_del + count;
1127 if (start_del <= 0) {
1128 start_del = 0;
1129 if (end_del > 0)
1130 filter->r.start.col = start; /* c */
1131 else
1132 filter->r.start.col -= count; /* b */
1133 filter->r.end.col -= count;
1134 } else {
1135 if ((unsigned)end_del > filter->fields->len) {
1136 end_del = filter->fields->len;
1137 filter->r.end.col = start - 1; /* d */
1138 } else
1139 filter->r.end.col -= count;
1140 }
1141
1142 if (filter->r.end.col < filter->r.start.col)
1143 kill_filter = TRUE;
1144 else {
1145 while (end_del-- > start_del) {
1146 remove_col (filter, end_del, pundo);
1147 reapply_filter = TRUE;
1148 }
1149 }
1150 }
1151 } else {
1152 if (start > filter->r.end.row)
1153 continue;
1154
1155 sheet->priv->filters_changed = TRUE;
1156
1157 if (is_insert) {
1158 /* INSERTING ROWS */
1159 filter->r.end.row =
1160 MIN (gnm_sheet_get_last_row (sheet),
1161 filter->r.end.row + count);
1162 if (start < filter->r.start.row)
1163 filter->r.start.row += count;
1164 } else {
1165 /* REMOVING ROWS */
1166 if (start <= filter->r.start.row) {
1167 filter->r.end.row -= count;
1168 if (start + count > filter->r.start.row)
1169 /* delete if the dropdowns are wiped */
1170 filter->r.start.row = filter->r.end.row + 1;
1171 else
1172 filter->r.start.row -= count;
1173 } else if (start + count > filter->r.end.row)
1174 filter->r.end.row = start -1;
1175 else
1176 filter->r.end.row -= count;
1177
1178 if (filter->r.end.row < filter->r.start.row)
1179 kill_filter = TRUE;
1180 }
1181 }
1182
1183 if (kill_filter) {
1184 /*
1185 * Empty the filter as we need fresh combo boxes
1186 * if we undo.
1187 */
1188 while (filter->fields->len)
1189 remove_col (filter,
1190 filter->fields->len - 1,
1191 pundo);
1192
1193 /* Restore the filters range */
1194 gnm_filter_remove (filter);
1195 filter->r = r;
1196
1197 if (pundo) {
1198 GOUndo *u = go_undo_binary_new
1199 (gnm_filter_ref (filter),
1200 sheet,
1201 (GOUndoBinaryFunc)gnm_filter_attach,
1202 (GFreeFunc)gnm_filter_unref,
1203 NULL);
1204 *pundo = go_undo_combine (*pundo, u);
1205 }
1206 gnm_filter_unref (filter);
1207 } else if (reapply_filter) {
1208 GnmRange *range = g_new (GnmRange, 1);
1209 *range = r;
1210 if (pundo) {
1211 GOUndo *u = go_undo_binary_new
1212 (gnm_filter_ref (filter),
1213 range,
1214 (GOUndoBinaryFunc)gnm_filter_set_range,
1215 (GFreeFunc)gnm_filter_unref,
1216 g_free);
1217 *pundo = go_undo_combine (*pundo, u);
1218 }
1219 gnm_filter_update_active (filter);
1220 gnm_filter_reapply (filter);
1221 }
1222 }
1223
1224 g_slist_free (filters);
1225 }
1226