Compilation: don't compile dialogs separately.
[gnumeric.git] / src / dialogs / dialog-autofilter.c
blob5b227b063b237ba3dfa78e6769614e4b39cfb8d3
1 /*
2 * dialog-autofilter.c: A pair of dialogs for autofilter conditions
4 * (c) Copyright 2002 Jody Goldberg <jody@gnome.org>
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) version 3.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
19 * USA
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include "dialogs.h"
26 #include "help.h"
28 #include <gui-util.h>
29 #include <commands.h>
30 #include <workbook-control.h>
31 #include <workbook.h>
32 #include <wbc-gtk.h>
33 #include <sheet.h>
34 #include <cell.h>
35 #include <ranges.h>
36 #include <value.h>
37 #include <sheet-filter.h>
38 #include <number-match.h>
39 #include <undo.h>
41 #include <gtk/gtk.h>
42 #include <string.h>
44 typedef struct {
45 GtkBuilder *gui;
46 WBCGtk *wbcg;
47 GtkWidget *dialog;
48 GnmFilter *filter;
49 unsigned field;
50 gboolean is_expr;
51 } AutoFilterState;
53 #define DIALOG_KEY "autofilter"
54 #define DIALOG_KEY_EXPRESSION "autofilter-expression"
55 #define UNICODE_ELLIPSIS "\xe2\x80\xa6"
57 static char const * const type_group[] = {
58 "items-largest",
59 "items-smallest",
60 "percentage-largest",
61 "percentage-smallest",
62 "percentage-largest-number",
63 "percentage-smallest-number",
64 NULL
67 static GnmFilterOp
68 autofilter_get_type (AutoFilterState *state)
70 return (GNM_FILTER_OP_TYPE_BUCKETS |
71 gnm_gui_group_value (state->gui, type_group));
75 static void
76 cb_autofilter_destroy (AutoFilterState *state)
78 if (state->gui != NULL) {
79 g_object_unref (state->gui);
80 state->gui = NULL;
83 state->dialog = NULL;
84 g_free (state);
87 static GnmValue *
88 map_op (AutoFilterState *state, GnmFilterOp *op,
89 char const *op_widget, char const *val_widget)
91 int i;
92 GtkWidget *w = go_gtk_builder_get_widget (state->gui, val_widget);
93 char const *txt = gtk_entry_get_text (GTK_ENTRY (w));
94 GnmValue *v = NULL;
96 *op = GNM_FILTER_UNUSED;
97 if (txt == NULL || *txt == '\0')
98 return NULL;
100 w = go_gtk_builder_get_widget (state->gui, op_widget);
101 i = gtk_combo_box_get_active (GTK_COMBO_BOX (w));
102 switch (i) {
103 case 0: return NULL;
104 case 1: *op = GNM_FILTER_OP_EQUAL; break;
105 case 2: *op = GNM_FILTER_OP_NOT_EQUAL; break;
106 case 3: *op = GNM_FILTER_OP_GT; break;
107 case 4: *op = GNM_FILTER_OP_GTE; break;
108 case 5: *op = GNM_FILTER_OP_LT; break;
109 case 6: *op = GNM_FILTER_OP_LTE; break;
111 case 7:
112 case 8: *op = (i == 8) ? GNM_FILTER_OP_NOT_EQUAL : GNM_FILTER_OP_EQUAL;
113 v = value_new_string_nocopy (g_strconcat (txt, "*", NULL));
114 break;
116 case 9:
117 case 10: *op = (i == 10) ? GNM_FILTER_OP_NOT_EQUAL : GNM_FILTER_OP_EQUAL;
118 v = value_new_string_nocopy (g_strconcat ("*", txt, NULL));
119 break;
121 case 11:
122 case 12: *op = (i == 12) ? GNM_FILTER_OP_NOT_EQUAL : GNM_FILTER_OP_EQUAL;
123 v = value_new_string_nocopy (g_strconcat ("*", txt, "*", NULL));
124 break;
125 default :
126 g_warning ("huh?");
127 return NULL;
130 if (v == NULL) {
131 Workbook *wb = wb_control_get_workbook (GNM_WBC (state->wbcg));
132 v = format_match (txt, NULL, workbook_date_conv (wb));
134 if (v == NULL)
135 v = value_new_string (txt);
137 return v;
140 static void
141 cb_autofilter_ok (G_GNUC_UNUSED GtkWidget *button,
142 AutoFilterState *state)
144 GnmFilterCondition *cond = NULL;
145 GtkWidget *w;
147 if (state->is_expr) {
148 GnmFilterOp op0;
149 GnmValue *v0 = map_op (state, &op0, "op0", "value0");
151 if (op0 != GNM_FILTER_UNUSED) {
152 GnmFilterOp op1;
153 GnmValue *v1 = map_op (state, &op1, "op1", "value1");
154 if (op1 != GNM_FILTER_UNUSED) {
155 w = go_gtk_builder_get_widget (state->gui,
156 "and_button");
157 cond = gnm_filter_condition_new_double
158 (op0, v0,
159 gtk_toggle_button_get_active
160 (GTK_TOGGLE_BUTTON (w)),
161 op1, v1);
162 } else
163 cond = gnm_filter_condition_new_single
164 (op0, v0);
166 } else {
167 int count;
168 GnmFilterOp op = autofilter_get_type (state);
170 w = go_gtk_builder_get_widget (state->gui, "item_count");
171 count = gtk_spin_button_get_value (GTK_SPIN_BUTTON (w));
173 cond = gnm_filter_condition_new_bucket
174 (!(op & GNM_FILTER_OP_BOTTOM_MASK),
175 !(op & GNM_FILTER_OP_PERCENT_MASK),
176 !(op & GNM_FILTER_OP_REL_N_MASK),
177 count);
179 if (cond != NULL)
180 cmd_autofilter_set_condition (GNM_WBC (state->wbcg),
181 state->filter, state->field,
182 cond);
184 gtk_widget_destroy (state->dialog);
187 static void
188 cb_autofilter_cancel (G_GNUC_UNUSED GtkWidget *button,
189 AutoFilterState *state)
191 gtk_widget_destroy (state->dialog);
194 static void
195 cb_top10_count_changed (GtkSpinButton *button,
196 AutoFilterState *state)
198 int val = 0.5 + gtk_spin_button_get_value (button);
199 GtkWidget *w;
200 gchar *label;
201 int cval = val, count;
203 count = range_height(&(state->filter->r)) - 1;
205 if (cval > count)
206 cval = count;
208 w = go_gtk_builder_get_widget (state->gui, type_group[0]);
209 /* xgettext : %d gives the number of items in the autofilter. */
210 /* This is input to ngettext. */
211 label = g_strdup_printf (ngettext ("Show the largest item",
212 "Show the %3d largest items",
213 cval),
214 cval);
215 gtk_button_set_label (GTK_BUTTON (w),label);
216 g_free(label);
218 w = go_gtk_builder_get_widget (state->gui, type_group[1]);
219 /* xgettext : %d gives the number of items in the autofilter. */
220 /* This is input to ngettext. */
221 label = g_strdup_printf (ngettext ("Show the smallest item",
222 "Show the %3d smallest items",
223 cval),
224 cval);
225 gtk_button_set_label (GTK_BUTTON (w),label);
226 g_free(label);
228 if (val > 100)
229 val = 100;
231 w = go_gtk_builder_get_widget (state->gui, type_group[2]);
232 /* xgettext : %d gives the percentage of the data range in the autofilter. */
233 /* This is input to ngettext. */
234 label = g_strdup_printf
235 (ngettext ("Show the items in the top %3d%% of the data range",
236 "Show the items in the top %3d%% of the data range", val),
237 val);
238 gtk_button_set_label (GTK_BUTTON (w),label);
239 g_free(label);
241 w = go_gtk_builder_get_widget (state->gui, type_group[3]);
242 /* xgettext : %d gives the percentage of the data range in the autofilter. */
243 /* This is input to ngettext. */
244 label = g_strdup_printf
245 (ngettext ("Show the items in the bottom %3d%% of the data range",
246 "Show the items in the bottom %3d%% of the data range", val),
247 val);
248 gtk_button_set_label (GTK_BUTTON (w),label);
249 g_free(label);
252 w = go_gtk_builder_get_widget (state->gui, type_group[4]);
253 /* xgettext : %d gives the percentage of item number in the autofilter. */
254 /* This is input to ngettext. */
255 label = g_strdup_printf
256 (ngettext ("Show the top %3d%% of all items",
257 "Show the top %3d%% of all items", val),
258 val);
259 gtk_button_set_label (GTK_BUTTON (w),label);
260 g_free(label);
262 w = go_gtk_builder_get_widget (state->gui, type_group[5]);
263 /* xgettext : %d gives the percentage of the item number in the autofilter. */
264 /* This is input to ngettext. */
265 label = g_strdup_printf
266 (ngettext ("Show the bottom %3d%% of all items",
267 "Show the bottom %3d%% of all items", val),
268 val);
269 gtk_button_set_label (GTK_BUTTON (w),label);
270 g_free(label);
275 static void
276 cb_top10_type_changed (G_GNUC_UNUSED GtkToggleButton *button,
277 AutoFilterState *state)
279 GnmFilterOp op = autofilter_get_type (state);
280 GtkWidget *spin = go_gtk_builder_get_widget (state->gui, "item_count");
281 GtkWidget *label = go_gtk_builder_get_widget (state->gui, "cp-label");
283 if ((op & GNM_FILTER_OP_PERCENT_MASK) != 0) {
284 gtk_spin_button_set_range (GTK_SPIN_BUTTON (spin), 1.,
285 100.);
286 gtk_label_set_text (GTK_LABEL (label), _("Percentage:"));
287 } else {
288 gtk_spin_button_set_range
289 (GTK_SPIN_BUTTON (spin), 1.,
290 range_height(&(state->filter->r)) - 1);
291 gtk_label_set_text (GTK_LABEL (label), _("Count:"));
295 static void
296 init_operator (AutoFilterState *state, GnmFilterOp op, GnmValue const *v,
297 char const *op_widget, char const *val_widget)
299 GtkWidget *w = go_gtk_builder_get_widget (state->gui, op_widget);
300 char const *str = v ? value_peek_string (v) : NULL;
301 char *content = NULL;
302 int i;
304 switch (op) {
305 case GNM_FILTER_OP_EQUAL: i = 1; break;
306 case GNM_FILTER_OP_GT: i = 3; break;
307 case GNM_FILTER_OP_LT: i = 5; break;
308 case GNM_FILTER_OP_GTE: i = 4; break;
309 case GNM_FILTER_OP_LTE: i = 6; break;
310 case GNM_FILTER_OP_NOT_EQUAL: i = 2; break;
311 default :
312 return;
315 if (v != NULL && VALUE_IS_STRING (v) && (i == 1 || i == 2)) {
316 unsigned const len = strlen (str);
318 /* there needs to be at least 1 letter */
319 int ends = (len > 1 && str[0] == '*') ? 1 : 0; /* as a bool and offset */
321 if (len > 1 && str[len-1] == '*' && str[len-2] != '~') {
322 content = g_strdup (str + ends);
323 content[len - ends - 1] = '\0';
324 i += (ends ? 10 : 6);
325 } else if (ends) {
326 str += 1;
327 i += 8;
330 gtk_combo_box_set_active (GTK_COMBO_BOX (w), i);
332 w = go_gtk_builder_get_widget (state->gui, val_widget);
333 gnm_editable_enters (GTK_WINDOW (state->dialog), w);
334 if (v != NULL)
335 gtk_entry_set_text (GTK_ENTRY (w), content ? content : str);
337 g_free (content);
340 static gchar *
341 dialog_auto_filter_get_col_name (GnmCell *cell, int col, int len)
343 gchar *label;
344 char *content = gnm_cell_get_rendered_text (cell);
345 if (g_utf8_strlen (content, -1) > len) {
346 char *end = g_utf8_find_prev_char
347 (content, content + len + 1 - strlen (UNICODE_ELLIPSIS));
348 strcpy (end, UNICODE_ELLIPSIS);
350 label = g_strdup_printf (_("Column %s (\"%s\")"),
351 col_name (col), content);
352 g_free (content);
353 return label;
355 static void
356 dialog_auto_filter_expression (WBCGtk *wbcg,
357 GnmFilter *filter, int field,
358 GnmFilterCondition *cond)
360 AutoFilterState *state;
361 GtkWidget *w;
362 GtkBuilder *gui;
363 int col;
364 gchar *label;
365 GnmCell *cell;
366 int const len = 15;
368 g_return_if_fail (wbcg != NULL);
370 if (gnm_dialog_raise_if_exists
371 (wbcg, DIALOG_KEY_EXPRESSION))
372 return;
373 gui = gnm_gtk_builder_load ("res:ui/autofilter-expression.ui",
374 NULL, GO_CMD_CONTEXT (wbcg));
375 if (gui == NULL)
376 return;
378 state = g_new (AutoFilterState, 1);
379 state->wbcg = wbcg;
380 state->filter = filter;
381 state->field = field;
382 state->is_expr = TRUE;
383 state->gui = gui;
385 g_return_if_fail (state->gui != NULL);
387 col = filter->r.start.col + field;
389 cell = sheet_cell_get (filter->sheet, col, filter->r.start.row);
391 if (cell == NULL || gnm_cell_is_blank (cell))
392 label = g_strdup_printf (_("Column %s"), col_name (col));
393 else
394 label = dialog_auto_filter_get_col_name (cell, col, len);
396 gtk_label_set_text
397 (GTK_LABEL (go_gtk_builder_get_widget (state->gui, "col-label1")), label);
398 gtk_label_set_text
399 (GTK_LABEL (go_gtk_builder_get_widget (state->gui, "col-label2")), label);
400 g_free (label);
402 state->dialog = go_gtk_builder_get_widget (state->gui, "dialog");
403 if (cond != NULL) {
404 GnmFilterOp const op = cond->op[0];
405 if (0 == (op & GNM_FILTER_OP_TYPE_MASK)) {
406 init_operator (state, cond->op[0],
407 cond->value[0], "op0", "value0");
408 if (cond->op[1] != GNM_FILTER_UNUSED)
409 init_operator (state, cond->op[1],
410 cond->value[1], "op1", "value1");
411 w = go_gtk_builder_get_widget (state->gui,
412 cond->is_and ? "and_button" : "or_button");
413 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
415 } else {
416 /* initialize the combo boxes (not done by li.ui) */
417 w = go_gtk_builder_get_widget (state->gui, "op0");
418 gtk_combo_box_set_active (GTK_COMBO_BOX (w), 0);
419 w = go_gtk_builder_get_widget (state->gui, "op1");
420 gtk_combo_box_set_active (GTK_COMBO_BOX (w), 0);
423 w = go_gtk_builder_get_widget (state->gui, "ok_button");
424 g_signal_connect (G_OBJECT (w),
425 "clicked",
426 G_CALLBACK (cb_autofilter_ok), state);
427 w = go_gtk_builder_get_widget (state->gui, "cancel_button");
428 g_signal_connect (G_OBJECT (w),
429 "clicked",
430 G_CALLBACK (cb_autofilter_cancel), state);
432 /* a candidate for merging into attach guru */
433 gnm_init_help_button (
434 go_gtk_builder_get_widget (state->gui, "help_button"),
435 GNUMERIC_HELP_LINK_AUTOFILTER_CUSTOM);
437 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
438 state->wbcg,
439 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
441 wbc_gtk_attach_guru (state->wbcg, state->dialog);
442 g_object_set_data_full (G_OBJECT (state->dialog),
443 "state", state, (GDestroyNotify)cb_autofilter_destroy);
445 gnm_keyed_dialog (wbcg, GTK_WINDOW (state->dialog),
446 DIALOG_KEY_EXPRESSION);
447 gtk_widget_show (state->dialog);
450 void
451 dialog_auto_filter (WBCGtk *wbcg,
452 GnmFilter *filter, int field,
453 gboolean is_expr, GnmFilterCondition *cond)
455 AutoFilterState *state;
456 GtkWidget *w;
457 GtkBuilder *gui;
458 int col;
459 gchar *label;
460 GnmCell *cell;
461 int len = is_expr ? 15 : 30;
462 char const * const *rb;
464 if (is_expr) {
465 dialog_auto_filter_expression (wbcg, filter, field, cond);
466 return;
469 g_return_if_fail (wbcg != NULL);
471 if (gnm_dialog_raise_if_exists (wbcg, DIALOG_KEY))
472 return;
473 gui = gnm_gtk_builder_load ("res:ui/autofilter-top10.ui",
474 NULL, GO_CMD_CONTEXT (wbcg));
475 if (gui == NULL)
476 return;
478 state = g_new (AutoFilterState, 1);
479 state->wbcg = wbcg;
480 state->filter = filter;
481 state->field = field;
482 state->is_expr = FALSE;
483 state->gui = gui;
485 g_return_if_fail (state->gui != NULL);
487 col = filter->r.start.col + field;
489 cell = sheet_cell_get (filter->sheet, col, filter->r.start.row);
491 if (cell == NULL || gnm_cell_is_blank (cell))
492 label = g_strdup_printf (_("Column %s"), col_name (col));
493 else
494 label = dialog_auto_filter_get_col_name (cell, col, len);
496 gtk_label_set_text
497 (GTK_LABEL (go_gtk_builder_get_widget (state->gui, "col-label")), label);
498 g_free (label);
500 state->dialog = go_gtk_builder_get_widget (state->gui, "dialog");
501 if (cond != NULL && GNM_FILTER_OP_TOP_N == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK)) {
502 gchar const *radio = NULL;
503 switch (cond->op[0]) {
504 case GNM_FILTER_OP_TOP_N:
505 default:
506 radio = type_group[0];
507 break;
508 case GNM_FILTER_OP_BOTTOM_N:
509 radio = type_group[1];
510 break;
511 case GNM_FILTER_OP_TOP_N_PERCENT:
512 radio = type_group[2];
513 break;
514 case GNM_FILTER_OP_BOTTOM_N_PERCENT:
515 radio = type_group[3];
516 break;
517 case GNM_FILTER_OP_TOP_N_PERCENT_N:
518 radio = type_group[4];
519 break;
520 case GNM_FILTER_OP_BOTTOM_N_PERCENT_N:
521 radio = type_group[5];
522 break;
524 w = go_gtk_builder_get_widget (state->gui, radio);
525 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
526 } else {
527 w = go_gtk_builder_get_widget (state->gui, "items-largest");
528 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
531 w = go_gtk_builder_get_widget (state->gui, "item_count");
532 g_signal_connect (G_OBJECT (w),
533 "value-changed",
534 G_CALLBACK (cb_top10_count_changed), state);
535 if (cond != NULL && GNM_FILTER_OP_TOP_N == (cond->op[0] & GNM_FILTER_OP_TYPE_MASK))
536 gtk_spin_button_set_value (GTK_SPIN_BUTTON (w), cond->count);
537 else
538 gtk_spin_button_set_value (GTK_SPIN_BUTTON (w),
539 range_height(&(state->filter->r))/2);
540 cb_top10_count_changed (GTK_SPIN_BUTTON (w), state);
541 cb_top10_type_changed (NULL, state);
543 rb = type_group;
544 while (*rb != NULL) {
545 w = go_gtk_builder_get_widget (state->gui, *rb);
546 g_signal_connect (G_OBJECT (w),
547 "toggled",
548 G_CALLBACK (cb_top10_type_changed), state);
549 rb++;
553 w = go_gtk_builder_get_widget (state->gui, "ok_button");
554 g_signal_connect (G_OBJECT (w),
555 "clicked",
556 G_CALLBACK (cb_autofilter_ok), state);
557 w = go_gtk_builder_get_widget (state->gui, "cancel_button");
558 g_signal_connect (G_OBJECT (w),
559 "clicked",
560 G_CALLBACK (cb_autofilter_cancel), state);
562 /* a candidate for merging into attach guru */
563 gnm_init_help_button (
564 go_gtk_builder_get_widget (state->gui, "help_button"),
565 GNUMERIC_HELP_LINK_AUTOFILTER_TOP_TEN);
567 gnm_dialog_setup_destroy_handlers (GTK_DIALOG (state->dialog),
568 state->wbcg,
569 GNM_DIALOG_DESTROY_CURRENT_SHEET_REMOVED);
571 wbc_gtk_attach_guru (state->wbcg, state->dialog);
572 g_object_set_data_full (G_OBJECT (state->dialog),
573 "state", state, (GDestroyNotify)cb_autofilter_destroy);
575 gnm_keyed_dialog (wbcg, GTK_WINDOW (state->dialog),
576 DIALOG_KEY);
577 gtk_widget_show (state->dialog);