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 */
27 #include <sys/types.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"
49 #define _(msgid) gettext (msgid)
50 #define N_(msgid) msgid
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;
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.
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")),
82 gtk_toggle_button_set_active
83 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
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),
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 */
102 do_find (GObject *obj, const struct find_dialog *fd)
108 find_value (fd, row, &x, &column);
112 SswSheet *sheet = SSW_SHEET (fd->de->data_editor->data_sheet);
113 gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
114 PSPPIRE_DATA_EDITOR_DATA_VIEW);
116 ssw_sheet_scroll_to (sheet, column, x);
117 ssw_sheet_set_active_cell (sheet, column, x, NULL);
121 /* Callback on the selector.
122 It gets invoked whenever a variable is selected */
124 on_select (GtkEntry *entry, gpointer data)
126 struct find_dialog *fd = data;
127 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
128 struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
129 gboolean search_labels ;
131 g_return_if_fail (var);
133 gtk_widget_set_sensitive (fd->value_labels_checkbox,
134 var_has_value_labels (var));
137 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
139 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
140 var_is_alpha (var) || search_labels);
143 gtk_widget_set_sensitive (fd->match_substring_checkbox,
144 var_is_alpha (var) || search_labels);
147 /* Callback on the selector.
148 It gets invoked whenever a variable is unselected */
150 on_deselect (GtkEntry *entry, gpointer data)
152 struct find_dialog *fd = data;
154 gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
155 gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
156 gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
160 value_labels_toggled (GtkToggleButton *tb, gpointer data)
162 struct find_dialog *fd = data;
164 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
165 const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
167 gboolean active = gtk_toggle_button_get_active (tb) ;
169 gtk_widget_set_sensitive (fd->match_substring_checkbox,
170 active || (var && var_is_alpha (var)));
172 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
173 active || (var && var_is_alpha (var)));
176 /* Pops up the Find dialog box
179 find_dialog (PsppireDataWindow *de)
181 struct find_dialog fd;
186 GtkWidget *find_button;
188 GtkWidget *buttonbox;
190 PsppireDataStore *ds ;
192 fd.xml = builder_new ("find.ui");
195 find_button = gtk_button_new_with_label (_("Find"));
196 gtk_widget_show (find_button);
198 buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
200 psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
201 gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
203 dialog = get_widget_assert (fd.xml, "find-dialog");
204 source = get_widget_assert (fd.xml, "find-variable-treeview");
205 selector = get_widget_assert (fd.xml, "find-selector");
207 g_object_get (de->data_editor,
208 "dictionary", &fd.dict,
212 fd.data = ds->datasheet;
214 fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
215 fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
216 fd.value_labels_checkbox =
217 get_widget_assert (fd.xml,
218 "find-value-labels-checkbutton");
220 fd.match_regexp_checkbox =
221 get_widget_assert (fd.xml,
222 "find-match-regexp-checkbutton");
224 fd.match_substring_checkbox =
225 get_widget_assert (fd.xml,
226 "find-match-substring-checkbutton");
230 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
233 g_object_set (source, "model", fd.dict,
234 "selection-mode", GTK_SELECTION_SINGLE,
238 psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
239 is_currently_in_entry);
241 g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
243 g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
245 g_signal_connect (selector, "selected",
246 G_CALLBACK (on_select), &fd);
248 g_signal_connect (selector, "de-selected",
249 G_CALLBACK (on_deselect), &fd);
251 g_signal_connect (fd.value_labels_checkbox, "toggled",
252 G_CALLBACK (value_labels_toggled), &fd);
255 psppire_dialog_run (PSPPIRE_DIALOG (dialog));
257 g_object_unref (fd.xml);
264 forward (casenumber *i, struct datasheet *data UNUSED)
271 forward_wrap (casenumber *i, struct datasheet *data)
273 if (++*i >= datasheet_get_n_rows (data)) *i = 0;
277 backward (casenumber *i, struct datasheet *data UNUSED)
284 backward_wrap (casenumber *i, struct datasheet *data)
287 *i = datasheet_get_n_rows (data) - 1;
291 /* Current plus one */
293 cp1 (casenumber current, struct datasheet *data)
298 /* Current plus one, circular */
300 cp1c (casenumber current, struct datasheet *data)
302 casenumber next = current;
304 forward_wrap (&next, data);
310 /* Current minus one */
312 cm1 (casenumber current, struct datasheet *data)
315 return datasheet_get_n_rows (data);
320 /* Current minus one, circular */
322 cm1c (casenumber current, struct datasheet *data)
324 casenumber next = current;
327 return datasheet_get_n_rows (data);
329 backward_wrap (&next, data);
336 last (casenumber current, struct datasheet *data)
338 return datasheet_get_n_rows (data) ;
342 minus1 (casenumber current, struct datasheet *data)
350 /* An type to facilitate iterating through casenumbers */
351 struct casenum_iterator
353 /* returns the first case to access */
354 casenumber (*start) (casenumber, struct datasheet *);
356 /* Returns one past the last case to access */
357 casenumber (*end) (casenumber, struct datasheet *);
359 /* Sets the first arg to the next case to access */
360 void (*next) (casenumber *, struct datasheet *);
371 static const struct casenum_iterator ip[n_iterators] =
373 {cp1, last, forward},
374 {cp1c, cm1, forward_wrap},
375 {cm1, minus1, backward},
376 {cm1c, cp1, backward_wrap}
381 /* A factory returning an iterator according to the dialog box's settings */
382 static const struct casenum_iterator *
383 get_iteration_params (const struct find_dialog *fd)
385 gboolean wrap = gtk_toggle_button_get_active
386 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
388 gboolean reverse = gtk_toggle_button_get_active
389 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
394 return &ip[REVERSE_WRAP];
396 return &ip[FORWARD_WRAP];
408 enum string_cmp_flags
410 STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
412 STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
414 STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
419 /* An abstract base type for comparing union values against a reference */
422 const struct variable *var;
423 enum string_cmp_flags flags;
425 bool (*compare) (const struct comparator *,
426 const union value *);
428 void (*destroy) (struct comparator *);
432 /* A comparator which operates on the numerical values,
433 rounded to the number of decimal places indicated by
434 the variable's format. */
435 struct numeric_comparator
437 struct comparator parent;
441 /* A comparator which matches string values or parts thereof */
442 struct string_comparator
444 struct comparator parent;
448 /* A comparator to match string values against a POSIX.2 regular expression */
449 struct regexp_comparator
451 struct comparator parent;
455 /* Returns 10 raised to the power of X.
456 X must be a non-negative integer. */
469 value_compare (const struct comparator *cmptr,
470 const union value *v)
472 const struct numeric_comparator *nc = (const struct numeric_comparator *) cmptr;
473 const struct fmt_spec *fs = var_get_print_format (cmptr->var);
475 double c = nearbyint (v->f * int_pow10 (fs->d));
477 return c == nc->rounded_ref;
481 /* Return true if the label of VAL matches the reference string*/
483 string_label_compare (const struct comparator *cmptr,
484 const union value *val)
486 const struct string_comparator *ssc =
487 (const struct string_comparator *) cmptr;
491 const char *text = var_lookup_value_label (cmptr->var, val);
495 width = strlen (text);
497 assert (cmptr->flags & STR_CMP_LABELS);
499 g_return_val_if_fail (width > 0, false);
501 if (cmptr->flags & STR_CMP_SUBSTR)
502 return (NULL != g_strstr_len (text, width, ssc->pattern));
504 return (0 == strncmp (text, ssc->pattern, width));
507 /* Return true if VAL matches the reference string*/
509 string_value_compare (const struct comparator *cmptr,
510 const union value *val)
514 const struct string_comparator *ssc =
515 (const struct string_comparator *) cmptr;
517 int width = var_get_width (cmptr->var);
518 g_return_val_if_fail (width > 0, false);
519 assert (! (cmptr->flags & STR_CMP_LABELS));
521 text = value_to_text (*val, cmptr->var);
523 if (cmptr->flags & STR_CMP_SUBSTR)
524 found = (NULL != g_strstr_len (text, width, ssc->pattern));
526 found = (0 == strncmp (text, ssc->pattern, width));
534 /* Return true if VAL matched the regexp */
536 regexp_value_compare (const struct comparator *cmptr,
537 const union value *val)
541 const struct regexp_comparator *rec =
542 (const struct regexp_comparator *) cmptr;
544 int width = var_get_width (cmptr->var);
546 assert (! (cmptr->flags & STR_CMP_LABELS));
548 g_return_val_if_fail (width > 0, false);
550 text = value_to_text (*val, cmptr->var);
551 /* We must remove trailing whitespace, otherwise $ will not match where
555 retval = (0 == regexec (&rec->re, text, 0, 0, 0));
562 /* Return true if the label of VAL matched the regexp */
564 regexp_label_compare (const struct comparator *cmptr,
565 const union value *val)
568 const struct regexp_comparator *rec =
569 (const struct regexp_comparator *) cmptr;
573 assert (cmptr->flags & STR_CMP_LABELS);
575 text = var_lookup_value_label (cmptr->var, val);
576 width = strlen (text);
578 g_return_val_if_fail (width > 0, false);
580 return (0 == regexec (&rec->re, text, 0, 0, 0));
586 regexp_destroy (struct comparator *cmptr)
588 struct regexp_comparator *rec
589 = UP_CAST (cmptr, struct regexp_comparator, parent);
594 static struct comparator *
595 numeric_comparator_create (const struct variable *var, const char *target)
597 struct numeric_comparator *nc = xzalloc (sizeof (*nc));
598 struct comparator *cmptr = &nc->parent;
602 cmptr->compare = value_compare;
603 const struct fmt_spec *fs = var_get_write_format (var);
606 text_to_value (target, var, &val);
607 nc->rounded_ref = nearbyint (val.f * int_pow10 (fs->d));
608 value_destroy (&val, var_get_width (var));
613 static struct comparator *
614 string_comparator_create (const struct variable *var, const char *target,
615 enum string_cmp_flags flags)
617 struct string_comparator *ssc = xzalloc (sizeof (*ssc));
618 struct comparator *cmptr = &ssc->parent;
620 cmptr->flags = flags;
623 if (flags & STR_CMP_LABELS)
624 cmptr->compare = string_label_compare;
626 cmptr->compare = string_value_compare;
628 ssc->pattern = target;
634 static struct comparator *
635 regexp_comparator_create (const struct variable *var, const char *target,
636 enum string_cmp_flags flags)
639 struct regexp_comparator *rec = xzalloc (sizeof (*rec));
640 struct comparator *cmptr = &rec->parent;
642 cmptr->flags = flags;
644 cmptr->compare = (flags & STR_CMP_LABELS)
645 ? regexp_label_compare : regexp_value_compare ;
647 cmptr->destroy = regexp_destroy;
649 code = regcomp (&rec->re, target, 0);
653 size_t errbuf_size = regerror (code, &rec->re, errbuf, 0);
655 errbuf = xmalloc (errbuf_size);
657 regerror (code, &rec->re, errbuf, errbuf_size);
659 msg (ME, _("Bad regular expression: %s"), errbuf);
670 /* Compare V against CMPTR's reference */
672 comparator_compare (const struct comparator *cmptr,
673 const union value *v)
675 return cmptr->compare (cmptr, v);
680 comparator_destroy (struct comparator *cmptr)
686 cmptr->destroy (cmptr);
692 static struct comparator *
693 comparator_factory (const struct variable *var, const char *str,
694 enum string_cmp_flags flags)
696 if (flags & STR_CMP_REGEXP)
697 return regexp_comparator_create (var, str, flags);
699 if (flags & (STR_CMP_SUBSTR | STR_CMP_LABELS))
700 return string_comparator_create (var, str, flags);
702 return numeric_comparator_create (var, str);
706 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
707 After the function returns, *ROW contains the row and *COLUMN the column.
708 If no such case is found, then *ROW will be set to -1
711 find_value (const struct find_dialog *fd, casenumber current_row,
712 casenumber *row, int *column)
715 const struct variable *var;
716 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
717 const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
719 enum string_cmp_flags flags = 0;
721 var = dict_lookup_var (fd->dict->dict, var_name);
725 width = var_get_width (var);
727 *column = var_get_dict_index (var);
730 if (gtk_toggle_button_get_active
731 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
732 flags |= STR_CMP_SUBSTR;
734 if (gtk_toggle_button_get_active
735 (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
736 flags |= STR_CMP_REGEXP;
738 if (gtk_toggle_button_get_active
739 (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
740 flags |= STR_CMP_LABELS;
745 const struct casenum_iterator *ip = get_iteration_params (fd);
746 struct comparator *cmptr =
747 comparator_factory (var, target_string, flags);
749 value_init (&val, width);
753 for (i = ip->start (current_row, fd->data);
754 i != ip->end (current_row, fd->data);
755 ip->next (&i, fd->data))
757 datasheet_get_value (fd->data, i, var_get_case_index (var), &val);
759 if (comparator_compare (cmptr, &val))
767 comparator_destroy (cmptr);
768 value_destroy (&val, width);