Fix minor English grammar errors, mostly "a" versus "an".
[pspp.git] / src / ui / gui / find-dialog.c
blobd9d3a5f651571d96d3acc281532794674d6c30f3
1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2009, 2011, 2012, 2015, 2020 Free Software Foundation
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18 /* This module implements the "Find" dialog; a dialog box to locate cases
19 which match particular strings */
21 #include <config.h>
23 #include <ctype.h>
24 #include <gtk/gtk.h>
25 #include <regex.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <math.h>
30 #include "data/data-in.h"
31 #include "data/datasheet.h"
32 #include "data/format.h"
33 #include "data/value.h"
34 #include "libpspp/cast.h"
35 #include "libpspp/message.h"
36 #include "ui/gui/builder-wrapper.h"
37 #include "ui/gui/dict-display.h"
38 #include "ui/gui/find-dialog.h"
39 #include "ui/gui/helper.h"
40 #include "ui/gui/psppire-data-store.h"
41 #include "ui/gui/psppire-data-window.h"
42 #include "ui/gui/psppire-dialog.h"
43 #include "ui/gui/psppire-selector.h"
44 #include <ssw-sheet.h>
46 #include "gl/xalloc.h"
48 #include <gettext.h>
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
53 struct find_dialog
55 GtkBuilder *xml;
56 PsppireDict *dict;
57 struct datasheet *data;
58 PsppireDataWindow *de;
59 GtkWidget *variable_entry;
60 GtkWidget *value_entry;
61 GtkWidget *value_labels_checkbox;
62 GtkWidget *match_regexp_checkbox;
63 GtkWidget *match_substring_checkbox;
66 static void
67 find_value (const struct find_dialog *fd, casenumber current_row,
68 casenumber *row, int *column);
71 /* A callback which occurs whenever the "Refresh" button is clicked,
72 and when the dialog pops up.
73 It restores the dialog to its default state.
75 static void
76 refresh (GObject *obj, const struct find_dialog *fd)
78 gtk_toggle_button_set_active
79 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
80 FALSE);
82 gtk_toggle_button_set_active
83 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
84 FALSE);
86 gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
87 gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
89 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
90 FALSE);
92 gtk_toggle_button_set_active
93 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
96 gtk_toggle_button_set_active
97 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
100 /* Callback on the "Find" button */
101 static void
102 do_find (GObject *obj, const struct find_dialog *fd)
104 SswSheet *sheet = SSW_SHEET (fd->de->data_editor->data_sheet);
105 casenumber x = -1;
106 gint column = -1;
107 gint unused;
108 gint row = 0;
109 ssw_sheet_get_active_cell (sheet, &unused, &row);
111 find_value (fd, row, &x, &column);
113 if (x != -1)
115 gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
116 PSPPIRE_DATA_EDITOR_DATA_VIEW);
118 ssw_sheet_scroll_to (sheet, column, x);
119 ssw_sheet_set_active_cell (sheet, column, x, NULL);
123 /* Callback on the selector.
124 It gets invoked whenever a variable is selected */
125 static void
126 on_select (GtkEntry *entry, gpointer data)
128 struct find_dialog *fd = data;
129 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
130 struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
131 gboolean search_labels ;
133 g_return_if_fail (var);
135 gtk_widget_set_sensitive (fd->value_labels_checkbox,
136 var_has_value_labels (var));
138 search_labels =
139 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
141 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
142 var_is_alpha (var) || search_labels);
145 gtk_widget_set_sensitive (fd->match_substring_checkbox,
146 var_is_alpha (var) || search_labels);
149 /* Callback on the selector.
150 It gets invoked whenever a variable is unselected */
151 static void
152 on_deselect (GtkEntry *entry, gpointer data)
154 struct find_dialog *fd = data;
156 gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
157 gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
158 gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
161 static void
162 value_labels_toggled (GtkToggleButton *tb, gpointer data)
164 struct find_dialog *fd = data;
166 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
167 const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
169 gboolean active = gtk_toggle_button_get_active (tb) ;
171 gtk_widget_set_sensitive (fd->match_substring_checkbox,
172 active || (var && var_is_alpha (var)));
174 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
175 active || (var && var_is_alpha (var)));
178 /* Pops up the Find dialog box
180 void
181 find_dialog (PsppireDataWindow *de)
183 struct find_dialog fd;
185 GtkWidget *dialog ;
186 GtkWidget *source ;
187 GtkWidget *selector;
188 GtkWidget *find_button;
190 GtkWidget *buttonbox;
192 PsppireDataStore *ds ;
194 fd.xml = builder_new ("find.ui");
195 fd.de = de;
197 find_button = gtk_button_new_with_label (_("Find"));
198 gtk_widget_show (find_button);
200 buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
202 psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
203 gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
205 dialog = get_widget_assert (fd.xml, "find-dialog");
206 source = get_widget_assert (fd.xml, "find-variable-treeview");
207 selector = get_widget_assert (fd.xml, "find-selector");
209 g_object_get (de->data_editor,
210 "dictionary", &fd.dict,
211 "data-store", &ds,
212 NULL);
214 fd.data = ds->datasheet;
216 fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
217 fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
218 fd.value_labels_checkbox =
219 get_widget_assert (fd.xml,
220 "find-value-labels-checkbutton");
222 fd.match_regexp_checkbox =
223 get_widget_assert (fd.xml,
224 "find-match-regexp-checkbutton");
226 fd.match_substring_checkbox =
227 get_widget_assert (fd.xml,
228 "find-match-substring-checkbutton");
232 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
235 g_object_set (source, "model", fd.dict,
236 "selection-mode", GTK_SELECTION_SINGLE,
237 NULL);
240 psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
241 is_currently_in_entry);
243 g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
245 g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
247 g_signal_connect (selector, "selected",
248 G_CALLBACK (on_select), &fd);
250 g_signal_connect (selector, "de-selected",
251 G_CALLBACK (on_deselect), &fd);
253 g_signal_connect (fd.value_labels_checkbox, "toggled",
254 G_CALLBACK (value_labels_toggled), &fd);
257 psppire_dialog_run (PSPPIRE_DIALOG (dialog));
259 g_object_unref (fd.xml);
263 /* Iterators */
265 static void
266 forward (casenumber *i, struct datasheet *data UNUSED)
268 ++*i;
272 static void
273 forward_wrap (casenumber *i, struct datasheet *data)
275 if (++*i >= datasheet_get_n_rows (data)) *i = 0;
278 static void
279 backward (casenumber *i, struct datasheet *data UNUSED)
281 --*i;
285 static void
286 backward_wrap (casenumber *i, struct datasheet *data)
288 if (--*i < 0)
289 *i = datasheet_get_n_rows (data) - 1;
293 /* Current plus one */
294 static casenumber
295 cp1 (casenumber current, struct datasheet *data)
297 return current + 1;
300 /* Current plus one, circular */
301 static casenumber
302 cp1c (casenumber current, struct datasheet *data)
304 casenumber next = current;
306 forward_wrap (&next, data);
308 return next;
312 /* Current minus one */
313 static casenumber
314 cm1 (casenumber current, struct datasheet *data)
316 if (current == 0)
317 return datasheet_get_n_rows (data) - 1;
319 return current - 1;
322 /* Current minus one, circular */
323 static casenumber
324 cm1c (casenumber current, struct datasheet *data)
326 casenumber next = current;
328 backward_wrap (&next, data);
330 return next;
334 static casenumber
335 last (casenumber current, struct datasheet *data)
337 return datasheet_get_n_rows (data) ;
340 static casenumber
341 minus1 (casenumber current, struct datasheet *data)
343 return -1;
346 /* A type to facilitate iterating through casenumbers */
347 struct casenum_iterator
349 /* returns the first case to access */
350 casenumber (*start) (casenumber, struct datasheet *);
352 /* Returns one past the last case to access */
353 casenumber (*end) (casenumber, struct datasheet *);
355 /* Sets the first arg to the next case to access */
356 void (*next) (casenumber *, struct datasheet *);
359 enum iteration_type{
360 FORWARD = 0,
361 FORWARD_WRAP,
362 REVERSE,
363 REVERSE_WRAP,
364 n_iterators
367 static const struct casenum_iterator case_iterator[n_iterators] =
369 /* Forward iterator (linear) */
370 {cp1, last, forward},
372 /* Forward iterator (circular) */
373 {cp1c, cm1, forward_wrap},
375 /* Reverse iterator (linear) */
376 {cm1, minus1, backward},
378 /* Reverse iterator (circular */
379 {cm1c, cp1, backward_wrap}
384 /* A factory returning an iterator according to the dialog box's settings */
385 static const struct casenum_iterator *
386 get_iteration_params (const struct find_dialog *fd)
388 gboolean wrap = gtk_toggle_button_get_active
389 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
391 gboolean reverse = gtk_toggle_button_get_active
392 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
394 if (wrap)
396 if (reverse)
397 return &case_iterator[REVERSE_WRAP];
398 else
399 return &case_iterator[FORWARD_WRAP];
401 else
403 if (reverse)
404 return &case_iterator[REVERSE];
405 else
406 return &case_iterator[FORWARD];
411 enum string_cmp_flags
413 STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
414 values */
415 STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
417 STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
418 of the data */
422 /* An abstract base type for comparing union values against a reference */
423 struct comparator
425 const struct variable *var;
426 enum string_cmp_flags flags;
428 bool (*compare) (const struct comparator *,
429 const union value *);
431 void (*destroy) (struct comparator *);
435 /* A comparator which operates on the numerical values,
436 rounded to the number of decimal places indicated by
437 the variable's format. */
438 struct numeric_comparator
440 struct comparator parent;
441 double rounded_ref;
444 /* A comparator which matches string values or parts thereof */
445 struct string_comparator
447 struct comparator parent;
448 const char *pattern;
451 /* A comparator to match string values against a POSIX.2 regular expression */
452 struct regexp_comparator
454 struct comparator parent;
455 regex_t re;
458 /* Returns 10 raised to the power of X.
459 X must be a non-negative integer. */
460 static inline int
461 int_pow10 (int x)
463 int ret = 1;
464 assert (x >= 0);
465 while (x--)
466 ret *= 10;
468 return ret;
471 static bool
472 value_compare (const struct comparator *cmptr,
473 const union value *v)
475 const struct numeric_comparator *nc = (const struct numeric_comparator *) cmptr;
476 struct fmt_spec fs = var_get_print_format (cmptr->var);
478 double c = nearbyint (v->f * int_pow10 (fs.d));
480 return c == nc->rounded_ref;
484 /* Return true if the label of VAL matches the reference string*/
485 static bool
486 string_label_compare (const struct comparator *cmptr,
487 const union value *val)
489 const struct string_comparator *ssc =
490 (const struct string_comparator *) cmptr;
492 int width;
494 const char *text = var_lookup_value_label (cmptr->var, val);
495 if (text == NULL)
496 return false;
498 width = strlen (text);
500 assert (cmptr->flags & STR_CMP_LABELS);
502 g_return_val_if_fail (width > 0, false);
504 if (cmptr->flags & STR_CMP_SUBSTR)
505 return (NULL != g_strstr_len (text, width, ssc->pattern));
506 else
507 return (0 == strncmp (text, ssc->pattern, width));
510 /* Return true if VAL matches the reference string*/
511 static bool
512 string_value_compare (const struct comparator *cmptr,
513 const union value *val)
515 bool found;
516 char *text;
517 const struct string_comparator *ssc =
518 (const struct string_comparator *) cmptr;
520 int width = var_get_width (cmptr->var);
521 g_return_val_if_fail (width > 0, false);
522 assert (! (cmptr->flags & STR_CMP_LABELS));
524 text = value_to_text (*val, cmptr->var);
526 if (cmptr->flags & STR_CMP_SUBSTR)
527 found = (NULL != g_strstr_len (text, width, ssc->pattern));
528 else
529 found = (0 == strncmp (text, ssc->pattern, width));
531 free (text);
532 return found;
537 /* Return true if VAL matched the regexp */
538 static bool
539 regexp_value_compare (const struct comparator *cmptr,
540 const union value *val)
542 char *text;
543 bool retval;
544 const struct regexp_comparator *rec =
545 (const struct regexp_comparator *) cmptr;
547 int width = var_get_width (cmptr->var);
549 assert (! (cmptr->flags & STR_CMP_LABELS));
551 g_return_val_if_fail (width > 0, false);
553 text = value_to_text (*val, cmptr->var);
554 /* We must remove trailing whitespace, otherwise $ will not match where
555 one would expect */
556 g_strchomp (text);
558 retval = (0 == regexec (&rec->re, text, 0, 0, 0));
560 g_free (text);
562 return retval;
565 /* Return true if the label of VAL matched the regexp */
566 static bool
567 regexp_label_compare (const struct comparator *cmptr,
568 const union value *val)
570 const char *text;
571 const struct regexp_comparator *rec =
572 (const struct regexp_comparator *) cmptr;
574 int width ;
576 assert (cmptr->flags & STR_CMP_LABELS);
578 text = var_lookup_value_label (cmptr->var, val);
579 width = strlen (text);
581 g_return_val_if_fail (width > 0, false);
583 return (0 == regexec (&rec->re, text, 0, 0, 0));
588 static void
589 regexp_destroy (struct comparator *cmptr)
591 struct regexp_comparator *rec
592 = UP_CAST (cmptr, struct regexp_comparator, parent);
594 regfree (&rec->re);
597 static struct comparator *
598 numeric_comparator_create (const struct variable *var, const char *target)
600 struct numeric_comparator *nc = XZALLOC (struct numeric_comparator);
601 struct comparator *cmptr = &nc->parent;
603 cmptr->flags = 0;
604 cmptr->var = var;
605 cmptr->compare = value_compare;
606 struct fmt_spec fs = var_get_write_format (var);
608 union value val;
609 text_to_value (target, var, &val);
610 nc->rounded_ref = nearbyint (val.f * int_pow10 (fs.d));
611 value_destroy (&val, var_get_width (var));
613 return cmptr;
616 static struct comparator *
617 string_comparator_create (const struct variable *var, const char *target,
618 enum string_cmp_flags flags)
620 struct string_comparator *ssc = XZALLOC (struct string_comparator);
621 struct comparator *cmptr = &ssc->parent;
623 cmptr->flags = flags;
624 cmptr->var = var;
626 if (flags & STR_CMP_LABELS)
627 cmptr->compare = string_label_compare;
628 else
629 cmptr->compare = string_value_compare;
631 ssc->pattern = target;
633 return cmptr;
637 static struct comparator *
638 regexp_comparator_create (const struct variable *var, const char *target,
639 enum string_cmp_flags flags)
641 int code;
642 struct regexp_comparator *rec = XZALLOC (struct regexp_comparator);
643 struct comparator *cmptr = &rec->parent;
645 cmptr->flags = flags;
646 cmptr->var = var;
647 cmptr->compare = (flags & STR_CMP_LABELS)
648 ? regexp_label_compare : regexp_value_compare ;
650 cmptr->destroy = regexp_destroy;
652 code = regcomp (&rec->re, target, 0);
653 if (code != 0)
655 char *errbuf = NULL;
656 size_t errbuf_size = regerror (code, &rec->re, errbuf, 0);
658 errbuf = xmalloc (errbuf_size);
660 regerror (code, &rec->re, errbuf, errbuf_size);
662 msg (ME, _("Bad regular expression: %s"), errbuf);
664 free (cmptr);
665 free (errbuf);
666 return NULL;
669 return cmptr;
673 /* Compare V against CMPTR's reference */
674 static bool
675 comparator_compare (const struct comparator *cmptr,
676 const union value *v)
678 return cmptr->compare (cmptr, v);
681 /* Destroy CMPTR */
682 static void
683 comparator_destroy (struct comparator *cmptr)
685 if (! cmptr)
686 return ;
688 if (cmptr->destroy)
689 cmptr->destroy (cmptr);
691 free (cmptr);
695 static struct comparator *
696 comparator_factory (const struct variable *var, const char *str,
697 enum string_cmp_flags flags)
699 if (flags & STR_CMP_REGEXP)
700 return regexp_comparator_create (var, str, flags);
702 if (flags & (STR_CMP_SUBSTR | STR_CMP_LABELS))
703 return string_comparator_create (var, str, flags);
705 return numeric_comparator_create (var, str);
709 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
710 After the function returns, *ROW contains the row and *COLUMN the column.
711 If no such case is found, then *ROW will be set to -1
713 static void
714 find_value (const struct find_dialog *fd, casenumber current_row,
715 casenumber *row, int *column)
717 int width;
718 const struct variable *var;
719 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
720 const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
722 enum string_cmp_flags flags = 0;
724 var = dict_lookup_var (fd->dict->dict, var_name);
725 if (! var)
726 return ;
728 width = var_get_width (var);
730 *column = var_get_dict_index (var);
731 *row = -1;
733 if (gtk_toggle_button_get_active
734 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
735 flags |= STR_CMP_SUBSTR;
737 if (gtk_toggle_button_get_active
738 (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
739 flags |= STR_CMP_REGEXP;
741 if (gtk_toggle_button_get_active
742 (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
743 flags |= STR_CMP_LABELS;
746 union value val;
747 casenumber i;
748 const struct casenum_iterator *ip = get_iteration_params (fd);
749 struct comparator *cmptr =
750 comparator_factory (var, target_string, flags);
752 value_init (&val, width);
753 if (! cmptr)
754 goto finish;
756 for (i = ip->start (current_row, fd->data);
757 i != ip->end (current_row, fd->data);
758 ip->next (&i, fd->data))
760 datasheet_get_value (fd->data, i, var_get_dict_index (var), &val);
762 if (comparator_compare (cmptr, &val))
764 *row = i;
765 break;
769 finish:
770 comparator_destroy (cmptr);
771 value_destroy (&val, width);