1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007, 2009 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 */
23 #include "find-dialog.h"
24 #include "psppire-selector.h"
25 #include "psppire-dialog.h"
27 #include "psppire-data-window.h"
28 #include "dict-display.h"
29 #include <data/value.h>
30 #include <data/format.h>
31 #include <data/datasheet.h>
32 #include <data/data-in.h>
33 #include "psppire-data-store.h"
35 #include <sys/types.h>
37 #include <libpspp/message.h>
45 #define _(msgid) gettext (msgid)
46 #define N_(msgid) msgid
49 /* FIXME: These shouldn't be here */
50 #include "psppire-var-store.h"
56 struct datasheet *data;
57 PsppireDataWindow *de;
58 GtkWidget *variable_entry;
59 GtkWidget *value_entry;
60 GtkWidget *value_labels_checkbox;
61 GtkWidget *match_regexp_checkbox;
62 GtkWidget *match_substring_checkbox;
66 find_value (const struct find_dialog *fd, casenumber current_row,
67 casenumber *row, int *column);
70 /* A callback which occurs whenever the "Refresh" button is clicked,
71 and when the dialog pops up.
72 It restores the dialog to its default state.
75 refresh (GObject *obj, const struct find_dialog *fd)
77 gtk_toggle_button_set_active
78 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
81 gtk_toggle_button_set_active
82 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
85 gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
86 gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
88 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
91 gtk_toggle_button_set_active
92 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
95 gtk_toggle_button_set_active
96 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
99 /* Callback on the "Find" button */
101 do_find (GObject *obj, const struct find_dialog *fd)
107 g_object_get (fd->de->data_editor, "current-case", &row, NULL);
112 find_value (fd, row, &x, &column);
117 gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
118 PSPPIRE_DATA_EDITOR_DATA_VIEW);
120 g_object_set (fd->de->data_editor,
122 "current-variable", column,
128 /* Callback on the selector.
129 It gets invoked whenever a variable is selected */
131 on_select (GtkEntry *entry, gpointer data)
133 struct find_dialog *fd = data;
134 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
135 struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
136 gboolean search_labels ;
138 g_return_if_fail (var);
140 gtk_widget_set_sensitive (fd->value_labels_checkbox,
141 var_has_value_labels (var));
144 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
146 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
147 var_is_alpha (var) || search_labels);
150 gtk_widget_set_sensitive (fd->match_substring_checkbox,
151 var_is_alpha (var) || search_labels);
154 /* Callback on the selector.
155 It gets invoked whenever a variable is unselected */
157 on_deselect (GtkEntry *entry, gpointer data)
159 struct find_dialog *fd = data;
161 gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
162 gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
163 gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
167 value_labels_toggled (GtkToggleButton *tb, gpointer data)
169 struct find_dialog *fd = data;
171 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
172 const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
174 gboolean active = gtk_toggle_button_get_active (tb) ;
176 gtk_widget_set_sensitive (fd->match_substring_checkbox,
177 active || (var && var_is_alpha (var)));
179 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
180 active || (var && var_is_alpha (var)));
183 /* Pops up the Find dialog box
186 find_dialog (GObject *o, gpointer data)
188 PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
190 struct find_dialog fd;
195 GtkWidget *find_button;
197 GtkWidget *buttonbox;
199 PsppireVarStore *vs ;
200 PsppireDataStore *ds ;
202 fd.xml = builder_new ("find.ui");
205 find_button = gtk_button_new_from_stock (GTK_STOCK_FIND);
206 gtk_widget_show (find_button);
208 buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
210 gtk_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
211 gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
213 dialog = get_widget_assert (fd.xml, "find-dialog");
214 source = get_widget_assert (fd.xml, "find-variable-treeview");
215 selector = get_widget_assert (fd.xml, "find-selector");
217 g_object_get (de->data_editor,
223 fd.data = ds->datasheet;
225 fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
226 fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
227 fd.value_labels_checkbox =
228 get_widget_assert (fd.xml,
229 "find-value-labels-checkbutton");
231 fd.match_regexp_checkbox =
232 get_widget_assert (fd.xml,
233 "find-match-regexp-checkbutton");
235 fd.match_substring_checkbox =
236 get_widget_assert (fd.xml,
237 "find-match-substring-checkbutton");
241 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
244 g_object_set (source, "dictionary", fd.dict,
245 "selection-mode", GTK_SELECTION_SINGLE,
248 psppire_selector_set_subjects (PSPPIRE_SELECTOR (selector),
251 insert_source_row_into_entry,
252 is_currently_in_entry,
256 g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
258 g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
260 g_signal_connect (selector, "selected",
261 G_CALLBACK (on_select), &fd);
263 g_signal_connect (selector, "de-selected",
264 G_CALLBACK (on_deselect), &fd);
266 g_signal_connect (fd.value_labels_checkbox, "toggled",
267 G_CALLBACK (value_labels_toggled), &fd);
270 psppire_dialog_run (PSPPIRE_DIALOG (dialog));
272 g_object_unref (fd.xml);
279 forward (casenumber *i, struct datasheet *data UNUSED)
286 forward_wrap (casenumber *i, struct datasheet *data)
288 if ( ++*i >= datasheet_get_n_rows (data) ) *i = 0;
292 backward (casenumber *i, struct datasheet *data UNUSED)
299 backward_wrap (casenumber *i, struct datasheet *data)
302 *i = datasheet_get_n_rows (data) - 1;
306 /* Current plus one */
308 cp1 (casenumber current, struct datasheet *data)
313 /* Current plus one, circular */
315 cp1c (casenumber current, struct datasheet *data)
317 casenumber next = current;
319 forward_wrap (&next, data);
325 /* Current minus one */
327 cm1 (casenumber current, struct datasheet *data)
332 /* Current minus one, circular */
334 cm1c (casenumber current, struct datasheet *data)
336 casenumber next = current;
338 backward_wrap (&next, data);
345 last (casenumber current, struct datasheet *data)
347 return datasheet_get_n_rows (data) ;
351 minus1 (casenumber current, struct datasheet *data)
356 /* An type to facilitate iterating through casenumbers */
357 struct casenum_iterator
359 /* returns the first case to access */
360 casenumber (*start) (casenumber, struct datasheet *);
362 /* Returns one past the last case to access */
363 casenumber (*end) (casenumber, struct datasheet *);
365 /* Sets the first arg to the next case to access */
366 void (*next) (casenumber *, struct datasheet *);
377 static const struct casenum_iterator ip[n_iterators] =
379 {cp1, last, forward},
380 {cp1c, cm1, forward_wrap},
381 {cm1, minus1, backward},
382 {cm1c, cp1, backward_wrap}
387 /* A factory returning an iterator according to the dialog box's settings */
388 static const struct casenum_iterator *
389 get_iteration_params (const struct find_dialog *fd)
391 gboolean wrap = gtk_toggle_button_get_active
392 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
394 gboolean reverse = gtk_toggle_button_get_active
395 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
400 return &ip[REVERSE_WRAP];
402 return &ip[FORWARD_WRAP];
414 enum string_cmp_flags
416 STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
418 STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
420 STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
425 /* An abstract base type for comparing union values against a reference */
428 const struct variable *var;
429 enum string_cmp_flags flags;
431 bool (*compare) (const struct comparator *,
432 const union value *);
434 void (*destroy) (struct comparator *);
438 /* A comparator which operates on the unadulterated union values */
439 struct value_comparator
441 struct comparator parent;
445 /* A comparator which matches string values or parts thereof */
446 struct string_comparator
448 struct comparator parent;
452 /* A comparator to match string values against a POSIX.2 regular expression */
453 struct regexp_comparator
455 struct comparator parent;
461 value_compare (const struct comparator *cmptr,
462 const union value *v)
464 const struct value_comparator *vc = (const struct value_comparator *) cmptr;
465 return 0 == value_compare_3way (v, &vc->pattern, var_get_width (cmptr->var));
469 /* Return true if the label of VAL matches the reference string*/
471 string_label_compare (const struct comparator *cmptr,
472 const union value *val)
474 const struct string_comparator *ssc =
475 (const struct string_comparator *) cmptr;
477 const char *text = var_lookup_value_label (cmptr->var, val);
478 int width = strlen (text);
480 assert ( cmptr->flags & STR_CMP_LABELS);
482 g_return_val_if_fail (width > 0, false);
484 if ( cmptr->flags & STR_CMP_SUBSTR)
485 return (NULL != g_strstr_len (text, width, ssc->pattern));
487 return (0 == strncmp (text, ssc->pattern, width));
490 /* Return true if VAL matches the reference string*/
492 string_value_compare (const struct comparator *cmptr,
493 const union value *val)
495 const struct string_comparator *ssc =
496 (const struct string_comparator *) cmptr;
498 int width = var_get_width (cmptr->var);
499 const char *text = value_str (val, width);
501 assert ( ! (cmptr->flags & STR_CMP_LABELS));
503 g_return_val_if_fail (width > 0, false);
505 if ( cmptr->flags & STR_CMP_SUBSTR)
506 return (NULL != g_strstr_len (text, width, ssc->pattern));
508 return (0 == strncmp (text, ssc->pattern, width));
513 /* Return true if VAL matched the regexp */
515 regexp_value_compare (const struct comparator *cmptr,
516 const union value *val)
520 const struct regexp_comparator *rec =
521 (const struct regexp_comparator *) cmptr;
523 int width = var_get_width (cmptr->var);
525 assert ( ! (cmptr->flags & STR_CMP_LABELS) );
527 g_return_val_if_fail (width > 0, false);
529 /* We must remove trailing whitespace, otherwise $ will not match where
531 text = g_strndup (value_str (val, width), width);
534 retval = (0 == regexec (&rec->re, text, 0, 0, 0));
541 /* Return true if the label of VAL matched the regexp */
543 regexp_label_compare (const struct comparator *cmptr,
544 const union value *val)
547 const struct regexp_comparator *rec =
548 (const struct regexp_comparator *) cmptr;
552 assert ( cmptr->flags & STR_CMP_LABELS);
554 text = var_lookup_value_label (cmptr->var, val);
555 width = strlen (text);
557 g_return_val_if_fail (width > 0, false);
559 return (0 == regexec (&rec->re, text, 0, 0, 0));
565 regexp_destroy (struct comparator *cmptr)
567 struct regexp_comparator *rec = (struct regexp_comparator *) cmptr;
573 cmptr_value_destroy (struct comparator *cmptr)
575 struct value_comparator *vc = (struct value_comparator *) cmptr;
576 value_destroy (&vc->pattern, var_get_width (cmptr->var));
580 static struct comparator *
581 value_comparator_create (const struct variable *var, const char *target)
583 const struct fmt_spec *fmt;
585 struct value_comparator *vc = xzalloc (sizeof (*vc));
586 struct comparator *cmptr = (struct comparator *) vc;
590 cmptr->compare = value_compare ;
591 cmptr->destroy = cmptr_value_destroy;
593 width = var_get_width (var);
594 fmt = var_get_write_format (var);
596 value_init (&vc->pattern, width);
598 if ( ! data_in (ss_cstr (target),
602 &vc->pattern, width) )
604 value_destroy (&vc->pattern, width);
612 static struct comparator *
613 string_comparator_create (const struct variable *var, const char *target,
614 enum string_cmp_flags flags)
616 struct string_comparator *ssc = xzalloc (sizeof (*ssc));
617 struct comparator *cmptr = (struct comparator *) ssc;
619 cmptr->flags = flags;
622 if ( flags & STR_CMP_LABELS)
623 cmptr->compare = string_label_compare;
625 cmptr->compare = string_value_compare;
627 ssc->pattern = target;
633 static struct comparator *
634 regexp_comparator_create (const struct variable *var, const char *target,
635 enum string_cmp_flags flags)
638 struct regexp_comparator *rec = xzalloc (sizeof (*rec));
639 struct comparator *cmptr = (struct comparator *) rec;
641 cmptr->flags = flags;
643 cmptr->compare = (flags & STR_CMP_LABELS)
644 ? regexp_label_compare : regexp_value_compare ;
646 cmptr->destroy = regexp_destroy;
648 code = regcomp (&rec->re, target, 0);
652 size_t errbuf_size = regerror (code, &rec->re, errbuf, 0);
654 errbuf = xmalloc (errbuf_size);
656 regerror (code, &rec->re, errbuf, errbuf_size);
658 msg (ME, _("Bad regular expression: %s"), errbuf);
669 /* Compare V against CMPTR's reference */
671 comparator_compare (const struct comparator *cmptr,
672 const union value *v)
674 return cmptr->compare (cmptr, v);
679 comparator_destroy (struct comparator *cmptr)
684 if ( cmptr->destroy )
685 cmptr->destroy (cmptr);
691 static struct comparator *
692 comparator_factory (const struct variable *var, const char *str,
693 enum string_cmp_flags flags)
695 if ( flags & STR_CMP_REGEXP )
696 return regexp_comparator_create (var, str, flags);
698 if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
699 return string_comparator_create (var, str, flags);
701 return value_comparator_create (var, str);
705 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
706 After the function returns, *ROW contains the row and *COLUMN the column.
707 If no such case is found, then *ROW will be set to -1
710 find_value (const struct find_dialog *fd, casenumber current_row,
711 casenumber *row, int *column)
714 const struct variable *var;
715 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
716 const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
718 enum string_cmp_flags flags = 0;
719 g_assert (current_row >= 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);