GUI: Move .ui files from goffice resources to glib resources
[gnumeric.git] / src / dialogs / dialog-autofilter.c
blobb69b2660da300eba1910c83df6577a2d740472e9
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /**
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
20 * USA
23 #include <gnumeric-config.h>
24 #include <glib/gi18n-lib.h>
25 #include <gnumeric.h>
26 #include "dialogs.h"
27 #include "help.h"
29 #include <gui-util.h>
30 #include <commands.h>
31 #include <workbook-control.h>
32 #include <workbook.h>
33 #include <wbc-gtk.h>
34 #include <sheet.h>
35 #include <cell.h>
36 #include <ranges.h>
37 #include <value.h>
38 #include <sheet-filter.h>
39 #include <number-match.h>
40 #include <undo.h>
42 #include <gtk/gtk.h>
43 #include <string.h>
45 typedef struct {
46 GtkBuilder *gui;
47 WBCGtk *wbcg;
48 GtkWidget *dialog;
49 GnmFilter *filter;
50 unsigned field;
51 gboolean is_expr;
52 } AutoFilterState;
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[] = {
59 "items-largest",
60 "items-smallest",
61 "percentage-largest",
62 "percentage-smallest",
63 "percentage-largest-number",
64 "percentage-smallest-number",
65 NULL
68 static GnmFilterOp
69 autofilter_get_type (AutoFilterState *state)
71 return (GNM_FILTER_OP_TYPE_BUCKETS |
72 gnm_gui_group_value (state->gui, type_group));
76 static void
77 cb_autofilter_destroy (AutoFilterState *state)
79 if (state->gui != NULL) {
80 g_object_unref (state->gui);
81 state->gui = NULL;
84 state->dialog = NULL;
85 g_free (state);
88 static GnmValue *
89 map_op (AutoFilterState *state, GnmFilterOp *op,
90 char const *op_widget, char const *val_widget)
92 int i;
93 GtkWidget *w = go_gtk_builder_get_widget (state->gui, val_widget);
94 char const *txt = gtk_entry_get_text (GTK_ENTRY (w));
95 GnmValue *v = NULL;
97 *op = GNM_FILTER_UNUSED;
98 if (txt == NULL || *txt == '\0')
99 return NULL;
101 w = go_gtk_builder_get_widget (state->gui, op_widget);
102 i = gtk_combo_box_get_active (GTK_COMBO_BOX (w));
103 switch (i) {
104 case 0: return NULL;
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;
112 case 7:
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));
115 break;
117 case 9:
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));
120 break;
122 case 11:
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));
125 break;
126 default :
127 g_warning ("huh?");
128 return NULL;
131 if (v == NULL) {
132 Workbook *wb = wb_control_get_workbook (GNM_WBC (state->wbcg));
133 v = format_match (txt, NULL, workbook_date_conv (wb));
135 if (v == NULL)
136 v = value_new_string (txt);
138 return v;
141 static void
142 cb_autofilter_ok (G_GNUC_UNUSED GtkWidget *button,
143 AutoFilterState *state)
145 GnmFilterCondition *cond = NULL;
146 GtkWidget *w;
148 if (state->is_expr) {
149 GnmFilterOp op0;
150 GnmValue *v0 = map_op (state, &op0, "op0", "value0");
152 if (op0 != GNM_FILTER_UNUSED) {
153 GnmFilterOp op1;
154 GnmValue *v1 = map_op (state, &op1, "op1", "value1");
155 if (op1 != GNM_FILTER_UNUSED) {
156 w = go_gtk_builder_get_widget (state->gui,
157 "and_button");
158 cond = gnm_filter_condition_new_double
159 (op0, v0,
160 gtk_toggle_button_get_active
161 (GTK_TOGGLE_BUTTON (w)),
162 op1, v1);
163 } else
164 cond = gnm_filter_condition_new_single
165 (op0, v0);
167 } else {
168 int count;
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),
178 count);
180 if (cond != NULL)
181 cmd_autofilter_set_condition (GNM_WBC (state->wbcg),
182 state->filter, state->field,
183 cond);
185 gtk_widget_destroy (state->dialog);
188 static void
189 cb_autofilter_cancel (G_GNUC_UNUSED GtkWidget *button,
190 AutoFilterState *state)
192 gtk_widget_destroy (state->dialog);
195 static void
196 cb_top10_count_changed (GtkSpinButton *button,
197 AutoFilterState *state)
199 int val = 0.5 + gtk_spin_button_get_value (button);
200 GtkWidget *w;
201 gchar *label;
202 int cval = val, count;
204 count = range_height(&(state->filter->r)) - 1;
206 if (cval > count)
207 cval = count;
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",
214 cval),
215 cval);
216 gtk_button_set_label (GTK_BUTTON (w),label);
217 g_free(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",
224 cval),
225 cval);
226 gtk_button_set_label (GTK_BUTTON (w),label);
227 g_free(label);
229 if (val > 100)
230 val = 100;
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),
238 val);
239 gtk_button_set_label (GTK_BUTTON (w),label);
240 g_free(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),
248 val);
249 gtk_button_set_label (GTK_BUTTON (w),label);
250 g_free(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),
259 val);
260 gtk_button_set_label (GTK_BUTTON (w),label);
261 g_free(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),
269 val);
270 gtk_button_set_label (GTK_BUTTON (w),label);
271 g_free(label);
276 static void
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.,
286 100.);
287 gtk_label_set_text (GTK_LABEL (label), _("Percentage:"));
288 } else {
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:"));
296 static void
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;
303 int i;
305 switch (op) {
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;
312 default :
313 return;
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);
326 } else if (ends) {
327 str += 1;
328 i += 8;
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);
335 if (v != NULL)
336 gtk_entry_set_text (GTK_ENTRY (w), content ? content : str);
338 g_free (content);
341 static gchar *
342 dialog_auto_filter_get_col_name (GnmCell *cell, int col, int len)
344 gchar *label;
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);
353 g_free (content);
354 return label;
356 static void
357 dialog_auto_filter_expression (WBCGtk *wbcg,
358 GnmFilter *filter, int field,
359 GnmFilterCondition *cond)
361 AutoFilterState *state;
362 GtkWidget *w;
363 GtkBuilder *gui;
364 int col;
365 gchar *label;
366 GnmCell *cell;
367 int const len = 15;
369 g_return_if_fail (wbcg != NULL);
371 if (gnm_dialog_raise_if_exists
372 (wbcg, DIALOG_KEY_EXPRESSION))
373 return;
374 gui = gnm_gtk_builder_load ("res:ui/autofilter-expression.ui",
375 NULL, GO_CMD_CONTEXT (wbcg));
376 if (gui == NULL)
377 return;
379 state = g_new (AutoFilterState, 1);
380 state->wbcg = wbcg;
381 state->filter = filter;
382 state->field = field;
383 state->is_expr = TRUE;
384 state->gui = gui;
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));
394 else
395 label = dialog_auto_filter_get_col_name (cell, col, len);
397 gtk_label_set_text
398 (GTK_LABEL (go_gtk_builder_get_widget (state->gui, "col-label1")), label);
399 gtk_label_set_text
400 (GTK_LABEL (go_gtk_builder_get_widget (state->gui, "col-label2")), label);
401 g_free (label);
403 state->dialog = go_gtk_builder_get_widget (state->gui, "dialog");
404 if (cond != NULL) {
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);
416 } else {
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),
426 "clicked",
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),
430 "clicked",
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),
439 state->wbcg,
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);
451 void
452 dialog_auto_filter (WBCGtk *wbcg,
453 GnmFilter *filter, int field,
454 gboolean is_expr, GnmFilterCondition *cond)
456 AutoFilterState *state;
457 GtkWidget *w;
458 GtkBuilder *gui;
459 int col;
460 gchar *label;
461 GnmCell *cell;
462 int len = is_expr ? 15 : 30;
463 char const * const *rb;
465 if (is_expr) {
466 dialog_auto_filter_expression (wbcg, filter, field, cond);
467 return;
470 g_return_if_fail (wbcg != NULL);
472 if (gnm_dialog_raise_if_exists (wbcg, DIALOG_KEY))
473 return;
474 gui = gnm_gtk_builder_load ("res:ui/autofilter-top10.ui",
475 NULL, GO_CMD_CONTEXT (wbcg));
476 if (gui == NULL)
477 return;
479 state = g_new (AutoFilterState, 1);
480 state->wbcg = wbcg;
481 state->filter = filter;
482 state->field = field;
483 state->is_expr = FALSE;
484 state->gui = gui;
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));
494 else
495 label = dialog_auto_filter_get_col_name (cell, col, len);
497 gtk_label_set_text
498 (GTK_LABEL (go_gtk_builder_get_widget (state->gui, "col-label")), label);
499 g_free (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:
506 default:
507 radio = type_group[0];
508 break;
509 case GNM_FILTER_OP_BOTTOM_N:
510 radio = type_group[1];
511 break;
512 case GNM_FILTER_OP_TOP_N_PERCENT:
513 radio = type_group[2];
514 break;
515 case GNM_FILTER_OP_BOTTOM_N_PERCENT:
516 radio = type_group[3];
517 break;
518 case GNM_FILTER_OP_TOP_N_PERCENT_N:
519 radio = type_group[4];
520 break;
521 case GNM_FILTER_OP_BOTTOM_N_PERCENT_N:
522 radio = type_group[5];
523 break;
525 w = go_gtk_builder_get_widget (state->gui, radio);
526 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (w), TRUE);
527 } else {
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),
534 "value-changed",
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);
538 else
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);
544 rb = type_group;
545 while (*rb != NULL) {
546 w = go_gtk_builder_get_widget (state->gui, *rb);
547 g_signal_connect (G_OBJECT (w),
548 "toggled",
549 G_CALLBACK (cb_top10_type_changed), state);
550 rb++;
554 w = go_gtk_builder_get_widget (state->gui, "ok_button");
555 g_signal_connect (G_OBJECT (w),
556 "clicked",
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),
560 "clicked",
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),
569 state->wbcg,
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),
577 DIALOG_KEY);
578 gtk_widget_show (state->dialog);