1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2009, 2011, 2012, 2015 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>
29 #include "data/data-in.h"
30 #include "data/datasheet.h"
31 #include "data/format.h"
32 #include "data/value.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/message.h"
35 #include "ui/gui/builder-wrapper.h"
36 #include "ui/gui/dict-display.h"
37 #include "ui/gui/find-dialog.h"
38 #include "ui/gui/helper.h"
39 #include "ui/gui/psppire-data-store.h"
40 #include "ui/gui/psppire-data-window.h"
41 #include "ui/gui/psppire-dialog.h"
42 #include "ui/gui/psppire-selector.h"
44 #include "gl/xalloc.h"
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
55 struct datasheet *data;
56 PsppireDataWindow *de;
57 GtkWidget *variable_entry;
58 GtkWidget *value_entry;
59 GtkWidget *value_labels_checkbox;
60 GtkWidget *match_regexp_checkbox;
61 GtkWidget *match_substring_checkbox;
65 find_value (const struct find_dialog *fd, casenumber current_row,
66 casenumber *row, int *column);
69 /* A callback which occurs whenever the "Refresh" button is clicked,
70 and when the dialog pops up.
71 It restores the dialog to its default state.
74 refresh (GObject *obj, const struct find_dialog *fd)
76 gtk_toggle_button_set_active
77 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
80 gtk_toggle_button_set_active
81 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
84 gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
85 gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
87 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
90 gtk_toggle_button_set_active
91 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
94 gtk_toggle_button_set_active
95 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
98 /* Callback on the "Find" button */
100 do_find (GObject *obj, const struct find_dialog *fd)
109 find_value (fd, row, &x, &column);
113 gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
114 PSPPIRE_DATA_EDITOR_DATA_VIEW);
119 /* Callback on the selector.
120 It gets invoked whenever a variable is selected */
122 on_select (GtkEntry *entry, gpointer data)
124 struct find_dialog *fd = data;
125 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
126 struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
127 gboolean search_labels ;
129 g_return_if_fail (var);
131 gtk_widget_set_sensitive (fd->value_labels_checkbox,
132 var_has_value_labels (var));
135 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
137 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
138 var_is_alpha (var) || search_labels);
141 gtk_widget_set_sensitive (fd->match_substring_checkbox,
142 var_is_alpha (var) || search_labels);
145 /* Callback on the selector.
146 It gets invoked whenever a variable is unselected */
148 on_deselect (GtkEntry *entry, gpointer data)
150 struct find_dialog *fd = data;
152 gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
153 gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
154 gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
158 value_labels_toggled (GtkToggleButton *tb, gpointer data)
160 struct find_dialog *fd = data;
162 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
163 const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
165 gboolean active = gtk_toggle_button_get_active (tb) ;
167 gtk_widget_set_sensitive (fd->match_substring_checkbox,
168 active || (var && var_is_alpha (var)));
170 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
171 active || (var && var_is_alpha (var)));
174 /* Pops up the Find dialog box
177 find_dialog (PsppireDataWindow *de)
179 struct find_dialog fd;
184 GtkWidget *find_button;
186 GtkWidget *buttonbox;
188 PsppireDataStore *ds ;
190 fd.xml = builder_new ("find.ui");
193 find_button = gtk_button_new_with_label (_("Find"));
194 gtk_widget_show (find_button);
196 buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
198 psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
199 gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
201 dialog = get_widget_assert (fd.xml, "find-dialog");
202 source = get_widget_assert (fd.xml, "find-variable-treeview");
203 selector = get_widget_assert (fd.xml, "find-selector");
205 g_object_get (de->data_editor,
206 "dictionary", &fd.dict,
210 fd.data = ds->datasheet;
212 fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
213 fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
214 fd.value_labels_checkbox =
215 get_widget_assert (fd.xml,
216 "find-value-labels-checkbutton");
218 fd.match_regexp_checkbox =
219 get_widget_assert (fd.xml,
220 "find-match-regexp-checkbutton");
222 fd.match_substring_checkbox =
223 get_widget_assert (fd.xml,
224 "find-match-substring-checkbutton");
228 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
231 g_object_set (source, "model", fd.dict,
232 "selection-mode", GTK_SELECTION_SINGLE,
236 psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
237 is_currently_in_entry);
239 g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
241 g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
243 g_signal_connect (selector, "selected",
244 G_CALLBACK (on_select), &fd);
246 g_signal_connect (selector, "de-selected",
247 G_CALLBACK (on_deselect), &fd);
249 g_signal_connect (fd.value_labels_checkbox, "toggled",
250 G_CALLBACK (value_labels_toggled), &fd);
253 psppire_dialog_run (PSPPIRE_DIALOG (dialog));
255 g_object_unref (fd.xml);
262 forward (casenumber *i, struct datasheet *data UNUSED)
269 forward_wrap (casenumber *i, struct datasheet *data)
271 if ( ++*i >= datasheet_get_n_rows (data) ) *i = 0;
275 backward (casenumber *i, struct datasheet *data UNUSED)
282 backward_wrap (casenumber *i, struct datasheet *data)
285 *i = datasheet_get_n_rows (data) - 1;
289 /* Current plus one */
291 cp1 (casenumber current, struct datasheet *data)
296 /* Current plus one, circular */
298 cp1c (casenumber current, struct datasheet *data)
300 casenumber next = current;
302 forward_wrap (&next, data);
308 /* Current minus one */
310 cm1 (casenumber current, struct datasheet *data)
313 return datasheet_get_n_rows (data);
318 /* Current minus one, circular */
320 cm1c (casenumber current, struct datasheet *data)
322 casenumber next = current;
325 return datasheet_get_n_rows (data);
327 backward_wrap (&next, data);
334 last (casenumber current, struct datasheet *data)
336 return datasheet_get_n_rows (data) ;
340 minus1 (casenumber current, struct datasheet *data)
348 /* An type to facilitate iterating through casenumbers */
349 struct casenum_iterator
351 /* returns the first case to access */
352 casenumber (*start) (casenumber, struct datasheet *);
354 /* Returns one past the last case to access */
355 casenumber (*end) (casenumber, struct datasheet *);
357 /* Sets the first arg to the next case to access */
358 void (*next) (casenumber *, struct datasheet *);
369 static const struct casenum_iterator ip[n_iterators] =
371 {cp1, last, forward},
372 {cp1c, cm1, forward_wrap},
373 {cm1, minus1, backward},
374 {cm1c, cp1, backward_wrap}
379 /* A factory returning an iterator according to the dialog box's settings */
380 static const struct casenum_iterator *
381 get_iteration_params (const struct find_dialog *fd)
383 gboolean wrap = gtk_toggle_button_get_active
384 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
386 gboolean reverse = gtk_toggle_button_get_active
387 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
392 return &ip[REVERSE_WRAP];
394 return &ip[FORWARD_WRAP];
406 enum string_cmp_flags
408 STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
410 STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
412 STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
417 /* An abstract base type for comparing union values against a reference */
420 const struct variable *var;
421 enum string_cmp_flags flags;
423 bool (*compare) (const struct comparator *,
424 const union value *);
426 void (*destroy) (struct comparator *);
430 /* A comparator which operates on the unadulterated union values */
431 struct value_comparator
433 struct comparator parent;
437 /* A comparator which matches string values or parts thereof */
438 struct string_comparator
440 struct comparator parent;
444 /* A comparator to match string values against a POSIX.2 regular expression */
445 struct regexp_comparator
447 struct comparator parent;
453 value_compare (const struct comparator *cmptr,
454 const union value *v)
456 const struct value_comparator *vc = (const struct value_comparator *) cmptr;
457 return 0 == value_compare_3way (v, &vc->pattern, var_get_width (cmptr->var));
461 /* Return true if the label of VAL matches the reference string*/
463 string_label_compare (const struct comparator *cmptr,
464 const union value *val)
466 const struct string_comparator *ssc =
467 (const struct string_comparator *) cmptr;
471 const char *text = var_lookup_value_label (cmptr->var, val);
475 width = strlen (text);
477 assert ( cmptr->flags & STR_CMP_LABELS);
479 g_return_val_if_fail (width > 0, false);
481 if ( cmptr->flags & STR_CMP_SUBSTR)
482 return (NULL != g_strstr_len (text, width, ssc->pattern));
484 return (0 == strncmp (text, ssc->pattern, width));
487 /* Return true if VAL matches the reference string*/
489 string_value_compare (const struct comparator *cmptr,
490 const union value *val)
494 const struct string_comparator *ssc =
495 (const struct string_comparator *) cmptr;
497 int width = var_get_width (cmptr->var);
498 g_return_val_if_fail (width > 0, false);
499 assert ( ! (cmptr->flags & STR_CMP_LABELS));
501 text = value_to_text (*val, cmptr->var);
503 if ( cmptr->flags & STR_CMP_SUBSTR)
504 found = (NULL != g_strstr_len (text, width, ssc->pattern));
506 found = (0 == strncmp (text, ssc->pattern, width));
514 /* Return true if VAL matched the regexp */
516 regexp_value_compare (const struct comparator *cmptr,
517 const union value *val)
521 const struct regexp_comparator *rec =
522 (const struct regexp_comparator *) cmptr;
524 int width = var_get_width (cmptr->var);
526 assert ( ! (cmptr->flags & STR_CMP_LABELS) );
528 g_return_val_if_fail (width > 0, false);
530 text = value_to_text (*val, cmptr->var);
531 /* We must remove trailing whitespace, otherwise $ will not match where
535 retval = (0 == regexec (&rec->re, text, 0, 0, 0));
542 /* Return true if the label of VAL matched the regexp */
544 regexp_label_compare (const struct comparator *cmptr,
545 const union value *val)
548 const struct regexp_comparator *rec =
549 (const struct regexp_comparator *) cmptr;
553 assert ( cmptr->flags & STR_CMP_LABELS);
555 text = var_lookup_value_label (cmptr->var, val);
556 width = strlen (text);
558 g_return_val_if_fail (width > 0, false);
560 return (0 == regexec (&rec->re, text, 0, 0, 0));
566 regexp_destroy (struct comparator *cmptr)
568 struct regexp_comparator *rec
569 = UP_CAST (cmptr, struct regexp_comparator, parent);
575 cmptr_value_destroy (struct comparator *cmptr)
577 struct value_comparator *vc
578 = UP_CAST (cmptr, struct value_comparator, parent);
579 value_destroy (&vc->pattern, var_get_width (cmptr->var));
583 static struct comparator *
584 value_comparator_create (const struct variable *var, const char *target)
586 struct value_comparator *vc = xzalloc (sizeof (*vc));
587 struct comparator *cmptr = &vc->parent;
591 cmptr->compare = value_compare ;
592 cmptr->destroy = cmptr_value_destroy;
594 text_to_value (target, var, &vc->pattern);
599 static struct comparator *
600 string_comparator_create (const struct variable *var, const char *target,
601 enum string_cmp_flags flags)
603 struct string_comparator *ssc = xzalloc (sizeof (*ssc));
604 struct comparator *cmptr = &ssc->parent;
606 cmptr->flags = flags;
609 if ( flags & STR_CMP_LABELS)
610 cmptr->compare = string_label_compare;
612 cmptr->compare = string_value_compare;
614 ssc->pattern = target;
620 static struct comparator *
621 regexp_comparator_create (const struct variable *var, const char *target,
622 enum string_cmp_flags flags)
625 struct regexp_comparator *rec = xzalloc (sizeof (*rec));
626 struct comparator *cmptr = &rec->parent;
628 cmptr->flags = flags;
630 cmptr->compare = (flags & STR_CMP_LABELS)
631 ? regexp_label_compare : regexp_value_compare ;
633 cmptr->destroy = regexp_destroy;
635 code = regcomp (&rec->re, target, 0);
639 size_t errbuf_size = regerror (code, &rec->re, errbuf, 0);
641 errbuf = xmalloc (errbuf_size);
643 regerror (code, &rec->re, errbuf, errbuf_size);
645 msg (ME, _("Bad regular expression: %s"), errbuf);
656 /* Compare V against CMPTR's reference */
658 comparator_compare (const struct comparator *cmptr,
659 const union value *v)
661 return cmptr->compare (cmptr, v);
666 comparator_destroy (struct comparator *cmptr)
671 if ( cmptr->destroy )
672 cmptr->destroy (cmptr);
678 static struct comparator *
679 comparator_factory (const struct variable *var, const char *str,
680 enum string_cmp_flags flags)
682 if ( flags & STR_CMP_REGEXP )
683 return regexp_comparator_create (var, str, flags);
685 if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
686 return string_comparator_create (var, str, flags);
688 return value_comparator_create (var, str);
692 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
693 After the function returns, *ROW contains the row and *COLUMN the column.
694 If no such case is found, then *ROW will be set to -1
697 find_value (const struct find_dialog *fd, casenumber current_row,
698 casenumber *row, int *column)
701 const struct variable *var;
702 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
703 const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
705 enum string_cmp_flags flags = 0;
707 var = dict_lookup_var (fd->dict->dict, var_name);
711 width = var_get_width (var);
713 *column = var_get_dict_index (var);
716 if ( gtk_toggle_button_get_active
717 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
718 flags |= STR_CMP_SUBSTR;
720 if ( gtk_toggle_button_get_active
721 (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
722 flags |= STR_CMP_REGEXP;
724 if ( gtk_toggle_button_get_active
725 (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
726 flags |= STR_CMP_LABELS;
731 const struct casenum_iterator *ip = get_iteration_params (fd);
732 struct comparator *cmptr =
733 comparator_factory (var, target_string, flags);
735 value_init (&val, width);
739 for (i = ip->start (current_row, fd->data);
740 i != ip->end (current_row, fd->data);
741 ip->next (&i, fd->data))
743 datasheet_get_value (fd->data, i, var_get_case_index (var), &val);
745 if ( comparator_compare (cmptr, &val))
753 comparator_destroy (cmptr);
754 value_destroy (&val, width);