1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * dialog-autofilter.c: A pair of dialogs for autofilter conditions
5 * (c) Copyright 2002 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
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
31 #include <workbook-control.h>
38 #include <sheet-filter.h>
39 #include <number-match.h>
54 #define DIALOG_KEY "autofilter"
55 #define DIALOG_KEY_EXPRESSION "autofilter-expression"
56 #define UNICODE_ELLIPSIS "\xe2\x80\xa6"
58 static char const * const type_group
[] = {
62 "percentage-smallest",
63 "percentage-largest-number",
64 "percentage-smallest-number",
69 autofilter_get_type (AutoFilterState
*state
)
71 return (GNM_FILTER_OP_TYPE_BUCKETS
|
72 gnm_gui_group_value (state
->gui
, type_group
));
77 cb_autofilter_destroy (AutoFilterState
*state
)
79 if (state
->gui
!= NULL
) {
80 g_object_unref (state
->gui
);
89 map_op (AutoFilterState
*state
, GnmFilterOp
*op
,
90 char const *op_widget
, char const *val_widget
)
93 GtkWidget
*w
= go_gtk_builder_get_widget (state
->gui
, val_widget
);
94 char const *txt
= gtk_entry_get_text (GTK_ENTRY (w
));
97 *op
= GNM_FILTER_UNUSED
;
98 if (txt
== NULL
|| *txt
== '\0')
101 w
= go_gtk_builder_get_widget (state
->gui
, op_widget
);
102 i
= gtk_combo_box_get_active (GTK_COMBO_BOX (w
));
105 case 1: *op
= GNM_FILTER_OP_EQUAL
; break;
106 case 2: *op
= GNM_FILTER_OP_NOT_EQUAL
; break;
107 case 3: *op
= GNM_FILTER_OP_GT
; break;
108 case 4: *op
= GNM_FILTER_OP_GTE
; break;
109 case 5: *op
= GNM_FILTER_OP_LT
; break;
110 case 6: *op
= GNM_FILTER_OP_LTE
; break;
113 case 8: *op
= (i
== 8) ? GNM_FILTER_OP_NOT_EQUAL
: GNM_FILTER_OP_EQUAL
;
114 v
= value_new_string_nocopy (g_strconcat (txt
, "*", NULL
));
118 case 10: *op
= (i
== 10) ? GNM_FILTER_OP_NOT_EQUAL
: GNM_FILTER_OP_EQUAL
;
119 v
= value_new_string_nocopy (g_strconcat ("*", txt
, NULL
));
123 case 12: *op
= (i
== 12) ? GNM_FILTER_OP_NOT_EQUAL
: GNM_FILTER_OP_EQUAL
;
124 v
= value_new_string_nocopy (g_strconcat ("*", txt
, "*", NULL
));
132 Workbook
*wb
= wb_control_get_workbook (GNM_WBC (state
->wbcg
));
133 v
= format_match (txt
, NULL
, workbook_date_conv (wb
));
136 v
= value_new_string (txt
);
142 cb_autofilter_ok (G_GNUC_UNUSED GtkWidget
*button
,
143 AutoFilterState
*state
)
145 GnmFilterCondition
*cond
= NULL
;
148 if (state
->is_expr
) {
150 GnmValue
*v0
= map_op (state
, &op0
, "op0", "value0");
152 if (op0
!= GNM_FILTER_UNUSED
) {
154 GnmValue
*v1
= map_op (state
, &op1
, "op1", "value1");
155 if (op1
!= GNM_FILTER_UNUSED
) {
156 w
= go_gtk_builder_get_widget (state
->gui
,
158 cond
= gnm_filter_condition_new_double
160 gtk_toggle_button_get_active
161 (GTK_TOGGLE_BUTTON (w
)),
164 cond
= gnm_filter_condition_new_single
169 GnmFilterOp op
= autofilter_get_type (state
);
171 w
= go_gtk_builder_get_widget (state
->gui
, "item_count");
172 count
= gtk_spin_button_get_value (GTK_SPIN_BUTTON (w
));
174 cond
= gnm_filter_condition_new_bucket
175 (!(op
& GNM_FILTER_OP_BOTTOM_MASK
),
176 !(op
& GNM_FILTER_OP_PERCENT_MASK
),
177 !(op
& GNM_FILTER_OP_REL_N_MASK
),
181 cmd_autofilter_set_condition (GNM_WBC (state
->wbcg
),
182 state
->filter
, state
->field
,
185 gtk_widget_destroy (state
->dialog
);
189 cb_autofilter_cancel (G_GNUC_UNUSED GtkWidget
*button
,
190 AutoFilterState
*state
)
192 gtk_widget_destroy (state
->dialog
);
196 cb_top10_count_changed (GtkSpinButton
*button
,
197 AutoFilterState
*state
)
199 int val
= 0.5 + gtk_spin_button_get_value (button
);
202 int cval
= val
, count
;
204 count
= range_height(&(state
->filter
->r
)) - 1;
209 w
= go_gtk_builder_get_widget (state
->gui
, type_group
[0]);
210 /* xgettext : %d gives the number of items in the autofilter. */
211 /* This is input to ngettext. */
212 label
= g_strdup_printf (ngettext ("Show the largest item",
213 "Show the %3d largest items",
216 gtk_button_set_label (GTK_BUTTON (w
),label
);
219 w
= go_gtk_builder_get_widget (state
->gui
, type_group
[1]);
220 /* xgettext : %d gives the number of items in the autofilter. */
221 /* This is input to ngettext. */
222 label
= g_strdup_printf (ngettext ("Show the smallest item",
223 "Show the %3d smallest items",
226 gtk_button_set_label (GTK_BUTTON (w
),label
);
232 w
= go_gtk_builder_get_widget (state
->gui
, type_group
[2]);
233 /* xgettext : %d gives the percentage of the data range in the autofilter. */
234 /* This is input to ngettext. */
235 label
= g_strdup_printf
236 (ngettext ("Show the items in the top %3d%% of the data range",
237 "Show the items in the top %3d%% of the data range", val
),
239 gtk_button_set_label (GTK_BUTTON (w
),label
);
242 w
= go_gtk_builder_get_widget (state
->gui
, type_group
[3]);
243 /* xgettext : %d gives the percentage of the data range in the autofilter. */
244 /* This is input to ngettext. */
245 label
= g_strdup_printf
246 (ngettext ("Show the items in the bottom %3d%% of the data range",
247 "Show the items in the bottom %3d%% of the data range", val
),
249 gtk_button_set_label (GTK_BUTTON (w
),label
);
253 w
= go_gtk_builder_get_widget (state
->gui
, type_group
[4]);
254 /* xgettext : %d gives the percentage of item number in the autofilter. */
255 /* This is input to ngettext. */
256 label
= g_strdup_printf
257 (ngettext ("Show the top %3d%% of all items",
258 "Show the top %3d%% of all items", val
),
260 gtk_button_set_label (GTK_BUTTON (w
),label
);
263 w
= go_gtk_builder_get_widget (state
->gui
, type_group
[5]);
264 /* xgettext : %d gives the percentage of the item number in the autofilter. */
265 /* This is input to ngettext. */
266 label
= g_strdup_printf
267 (ngettext ("Show the bottom %3d%% of all items",
268 "Show the bottom %3d%% of all items", val
),
270 gtk_button_set_label (GTK_BUTTON (w
),label
);
277 cb_top10_type_changed (G_GNUC_UNUSED GtkToggleButton
*button
,
278 AutoFilterState
*state
)
280 GnmFilterOp op
= autofilter_get_type (state
);
281 GtkWidget
*spin
= go_gtk_builder_get_widget (state
->gui
, "item_count");
282 GtkWidget
*label
= go_gtk_builder_get_widget (state
->gui
, "cp-label");
284 if ((op
& GNM_FILTER_OP_PERCENT_MASK
) != 0) {
285 gtk_spin_button_set_range (GTK_SPIN_BUTTON (spin
), 1.,
287 gtk_label_set_text (GTK_LABEL (label
), _("Percentage:"));
289 gtk_spin_button_set_range
290 (GTK_SPIN_BUTTON (spin
), 1.,
291 range_height(&(state
->filter
->r
)) - 1);
292 gtk_label_set_text (GTK_LABEL (label
), _("Count:"));
297 init_operator (AutoFilterState
*state
, GnmFilterOp op
, GnmValue
const *v
,
298 char const *op_widget
, char const *val_widget
)
300 GtkWidget
*w
= go_gtk_builder_get_widget (state
->gui
, op_widget
);
301 char const *str
= v
? value_peek_string (v
) : NULL
;
302 char *content
= NULL
;
306 case GNM_FILTER_OP_EQUAL
: i
= 1; break;
307 case GNM_FILTER_OP_GT
: i
= 3; break;
308 case GNM_FILTER_OP_LT
: i
= 5; break;
309 case GNM_FILTER_OP_GTE
: i
= 4; break;
310 case GNM_FILTER_OP_LTE
: i
= 6; break;
311 case GNM_FILTER_OP_NOT_EQUAL
: i
= 2; break;
316 if (v
!= NULL
&& VALUE_IS_STRING (v
) && (i
== 1 || i
== 2)) {
317 unsigned const len
= strlen (str
);
319 /* there needs to be at least 1 letter */
320 int ends
= (len
> 1 && str
[0] == '*') ? 1 : 0; /* as a bool and offset */
322 if (len
> 1 && str
[len
-1] == '*' && str
[len
-2] != '~') {
323 content
= g_strdup (str
+ ends
);
324 content
[len
- ends
- 1] = '\0';
325 i
+= (ends
? 10 : 6);
331 gtk_combo_box_set_active (GTK_COMBO_BOX (w
), i
);
333 w
= go_gtk_builder_get_widget (state
->gui
, val_widget
);
334 gnm_editable_enters (GTK_WINDOW (state
->dialog
), w
);
336 gtk_entry_set_text (GTK_ENTRY (w
), content
? content
: str
);
342 dialog_auto_filter_get_col_name (GnmCell
*cell
, int col
, int len
)
345 char *content
= gnm_cell_get_rendered_text (cell
);
346 if (g_utf8_strlen (content
, -1) > len
) {
347 char *end
= g_utf8_find_prev_char
348 (content
, content
+ len
+ 1 - strlen (UNICODE_ELLIPSIS
));
349 strcpy (end
, UNICODE_ELLIPSIS
);
351 label
= g_strdup_printf (_("Column %s (\"%s\")"),
352 col_name (col
), content
);
357 dialog_auto_filter_expression (WBCGtk
*wbcg
,
358 GnmFilter
*filter
, int field
,
359 GnmFilterCondition
*cond
)
361 AutoFilterState
*state
;
369 g_return_if_fail (wbcg
!= NULL
);
371 if (gnm_dialog_raise_if_exists
372 (wbcg
, DIALOG_KEY_EXPRESSION
))
374 gui
= gnm_gtk_builder_load ("res:ui/autofilter-expression.ui",
375 NULL
, GO_CMD_CONTEXT (wbcg
));
379 state
= g_new (AutoFilterState
, 1);
381 state
->filter
= filter
;
382 state
->field
= field
;
383 state
->is_expr
= TRUE
;
386 g_return_if_fail (state
->gui
!= NULL
);
388 col
= filter
->r
.start
.col
+ field
;
390 cell
= sheet_cell_get (filter
->sheet
, col
, filter
->r
.start
.row
);
392 if (cell
== NULL
|| gnm_cell_is_blank (cell
))
393 label
= g_strdup_printf (_("Column %s"), col_name (col
));
395 label
= dialog_auto_filter_get_col_name (cell
, col
, len
);
398 (GTK_LABEL (go_gtk_builder_get_widget (state
->gui
, "col-label1")), label
);
400 (GTK_LABEL (go_gtk_builder_get_widget (state
->gui
, "col-label2")), label
);
403 state
->dialog
= go_gtk_builder_get_widget (state
->gui
, "dialog");
405 GnmFilterOp
const op
= cond
->op
[0];
406 if (0 == (op
& GNM_FILTER_OP_TYPE_MASK
)) {
407 init_operator (state
, cond
->op
[0],
408 cond
->value
[0], "op0", "value0");
409 if (cond
->op
[1] != GNM_FILTER_UNUSED
)
410 init_operator (state
, cond
->op
[1],
411 cond
->value
[1], "op1", "value1");
412 w
= go_gtk_builder_get_widget (state
->gui
,
413 cond
->is_and
? "and_button" : "or_button");
414 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w
), TRUE
);
417 /* initialize the combo boxes (not done by li.ui) */
418 w
= go_gtk_builder_get_widget (state
->gui
, "op0");
419 gtk_combo_box_set_active (GTK_COMBO_BOX (w
), 0);
420 w
= go_gtk_builder_get_widget (state
->gui
, "op1");
421 gtk_combo_box_set_active (GTK_COMBO_BOX (w
), 0);
424 w
= go_gtk_builder_get_widget (state
->gui
, "ok_button");
425 g_signal_connect (G_OBJECT (w
),
427 G_CALLBACK (cb_autofilter_ok
), state
);
428 w
= go_gtk_builder_get_widget (state
->gui
, "cancel_button");
429 g_signal_connect (G_OBJECT (w
),
431 G_CALLBACK (cb_autofilter_cancel
), state
);
433 /* a candidate for merging into attach guru */
434 gnm_init_help_button (
435 go_gtk_builder_get_widget (state
->gui
, "help_button"),
436 GNUMERIC_HELP_LINK_AUTOFILTER_CUSTOM
);
438 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state
->dialog
),
440 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED
);
442 wbc_gtk_attach_guru (state
->wbcg
, state
->dialog
);
443 g_object_set_data_full (G_OBJECT (state
->dialog
),
444 "state", state
, (GDestroyNotify
)cb_autofilter_destroy
);
446 gnm_keyed_dialog (wbcg
, GTK_WINDOW (state
->dialog
),
447 DIALOG_KEY_EXPRESSION
);
448 gtk_widget_show (state
->dialog
);
452 dialog_auto_filter (WBCGtk
*wbcg
,
453 GnmFilter
*filter
, int field
,
454 gboolean is_expr
, GnmFilterCondition
*cond
)
456 AutoFilterState
*state
;
462 int len
= is_expr
? 15 : 30;
463 char const * const *rb
;
466 dialog_auto_filter_expression (wbcg
, filter
, field
, cond
);
470 g_return_if_fail (wbcg
!= NULL
);
472 if (gnm_dialog_raise_if_exists (wbcg
, DIALOG_KEY
))
474 gui
= gnm_gtk_builder_load ("res:ui/autofilter-top10.ui",
475 NULL
, GO_CMD_CONTEXT (wbcg
));
479 state
= g_new (AutoFilterState
, 1);
481 state
->filter
= filter
;
482 state
->field
= field
;
483 state
->is_expr
= FALSE
;
486 g_return_if_fail (state
->gui
!= NULL
);
488 col
= filter
->r
.start
.col
+ field
;
490 cell
= sheet_cell_get (filter
->sheet
, col
, filter
->r
.start
.row
);
492 if (cell
== NULL
|| gnm_cell_is_blank (cell
))
493 label
= g_strdup_printf (_("Column %s"), col_name (col
));
495 label
= dialog_auto_filter_get_col_name (cell
, col
, len
);
498 (GTK_LABEL (go_gtk_builder_get_widget (state
->gui
, "col-label")), label
);
501 state
->dialog
= go_gtk_builder_get_widget (state
->gui
, "dialog");
502 if (cond
!= NULL
&& GNM_FILTER_OP_TOP_N
== (cond
->op
[0] & GNM_FILTER_OP_TYPE_MASK
)) {
503 gchar
const *radio
= NULL
;
504 switch (cond
->op
[0]) {
505 case GNM_FILTER_OP_TOP_N
:
507 radio
= type_group
[0];
509 case GNM_FILTER_OP_BOTTOM_N
:
510 radio
= type_group
[1];
512 case GNM_FILTER_OP_TOP_N_PERCENT
:
513 radio
= type_group
[2];
515 case GNM_FILTER_OP_BOTTOM_N_PERCENT
:
516 radio
= type_group
[3];
518 case GNM_FILTER_OP_TOP_N_PERCENT_N
:
519 radio
= type_group
[4];
521 case GNM_FILTER_OP_BOTTOM_N_PERCENT_N
:
522 radio
= type_group
[5];
525 w
= go_gtk_builder_get_widget (state
->gui
, radio
);
526 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w
), TRUE
);
528 w
= go_gtk_builder_get_widget (state
->gui
, "items-largest");
529 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w
), TRUE
);
532 w
= go_gtk_builder_get_widget (state
->gui
, "item_count");
533 g_signal_connect (G_OBJECT (w
),
535 G_CALLBACK (cb_top10_count_changed
), state
);
536 if (cond
!= NULL
&& GNM_FILTER_OP_TOP_N
== (cond
->op
[0] & GNM_FILTER_OP_TYPE_MASK
))
537 gtk_spin_button_set_value (GTK_SPIN_BUTTON (w
), cond
->count
);
539 gtk_spin_button_set_value (GTK_SPIN_BUTTON (w
),
540 range_height(&(state
->filter
->r
))/2);
541 cb_top10_count_changed (GTK_SPIN_BUTTON (w
), state
);
542 cb_top10_type_changed (NULL
, state
);
545 while (*rb
!= NULL
) {
546 w
= go_gtk_builder_get_widget (state
->gui
, *rb
);
547 g_signal_connect (G_OBJECT (w
),
549 G_CALLBACK (cb_top10_type_changed
), state
);
554 w
= go_gtk_builder_get_widget (state
->gui
, "ok_button");
555 g_signal_connect (G_OBJECT (w
),
557 G_CALLBACK (cb_autofilter_ok
), state
);
558 w
= go_gtk_builder_get_widget (state
->gui
, "cancel_button");
559 g_signal_connect (G_OBJECT (w
),
561 G_CALLBACK (cb_autofilter_cancel
), state
);
563 /* a candidate for merging into attach guru */
564 gnm_init_help_button (
565 go_gtk_builder_get_widget (state
->gui
, "help_button"),
566 GNUMERIC_HELP_LINK_AUTOFILTER_TOP_TEN
);
568 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state
->dialog
),
570 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED
);
572 wbc_gtk_attach_guru (state
->wbcg
, state
->dialog
);
573 g_object_set_data_full (G_OBJECT (state
->dialog
),
574 "state", state
, (GDestroyNotify
)cb_autofilter_destroy
);
576 gnm_keyed_dialog (wbcg
, GTK_WINDOW (state
->dialog
),
578 gtk_widget_show (state
->dialog
);