1 /* PSPPIRE - a graphical user interface for PSPP.
2 Copyright (C) 2007 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 "data-editor.h"
28 #include "dict-display.h"
29 #include <data/value.h>
30 #include <data/datasheet.h>
31 #include <data/data-in.h>
32 #include "psppire-data-store.h"
34 #include <sys/types.h>
36 #include <libpspp/message.h>
39 #include <glade/glade.h>
45 #define _(msgid) gettext (msgid)
46 #define N_(msgid) msgid
49 /* FIXME: These shouldn't be here */
50 #include <gtksheet/gtksheet.h>
51 #include "psppire-var-store.h"
58 struct datasheet *data;
59 struct data_editor *de;
60 GtkWidget *variable_entry;
61 GtkWidget *value_entry;
62 GtkWidget *value_labels_checkbox;
63 GtkWidget *match_regexp_checkbox;
64 GtkWidget *match_substring_checkbox;
68 find_value (const struct find_dialog *fd, casenumber current_row,
69 casenumber *row, int *column);
72 /* A callback which occurs whenever the "Refresh" button is clicked,
73 and when the dialog pops up.
74 It restores the dialog to its default state.
77 refresh (GObject *obj, const struct find_dialog *fd)
79 gtk_toggle_button_set_active
80 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
83 gtk_toggle_button_set_active
84 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
87 gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
88 gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
90 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
93 gtk_toggle_button_set_active
94 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
97 gtk_toggle_button_set_active
98 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
101 /* Callback on the "Find" button */
103 do_find (GObject *obj, const struct find_dialog *fd)
108 gtk_sheet_get_active_cell (fd->data_sheet, &row, NULL);
113 find_value (fd, row, &x, &column);
117 data_editor_select_sheet (fd->de, PAGE_DATA_SHEET);
119 gtk_sheet_moveto (fd->data_sheet, x, column, 0.5, 0.5);
121 gtk_sheet_set_active_cell (fd->data_sheet, x, column);
125 /* Callback on the selector.
126 It gets invoked whenever a variable is selected */
128 on_select (GtkEntry *entry, gpointer data)
130 struct find_dialog *fd = data;
131 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
132 struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
133 gboolean search_labels ;
135 g_return_if_fail (var);
137 gtk_widget_set_sensitive (fd->value_labels_checkbox,
138 var_has_value_labels (var));
141 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
143 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
144 var_is_alpha (var) || search_labels);
147 gtk_widget_set_sensitive (fd->match_substring_checkbox,
148 var_is_alpha (var) || search_labels);
151 /* Callback on the selector.
152 It gets invoked whenever a variable is unselected */
154 on_deselect (GtkEntry *entry, gpointer data)
156 struct find_dialog *fd = data;
158 gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
159 gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
160 gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
164 value_labels_toggled (GtkToggleButton *tb, gpointer data)
166 struct find_dialog *fd = data;
168 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
169 const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
171 gboolean active = gtk_toggle_button_get_active (tb) ;
173 gtk_widget_set_sensitive (fd->match_substring_checkbox,
174 active || (var && var_is_alpha (var)));
176 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
177 active || (var && var_is_alpha (var)));
180 /* Pops up the Find dialog box
183 find_dialog (GObject *o, gpointer data)
185 struct data_editor *de = data;
187 struct find_dialog fd;
192 GtkWidget *find_button;
195 GtkSheet *data_sheet ;
196 GtkWidget *buttonbox;
198 PsppireVarStore *vs ;
199 PsppireDataStore *ds ;
201 fd.xml = XML_NEW ("psppire.glade");
204 find_button = gtk_button_new_from_stock (GTK_STOCK_FIND);
205 gtk_widget_show (find_button);
207 buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
209 gtk_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
210 gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
212 dialog = get_widget_assert (fd.xml, "find-dialog");
213 source = get_widget_assert (fd.xml, "find-variable-treeview");
214 selector = get_widget_assert (fd.xml, "find-selector");
216 var_sheet = GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
217 data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
219 vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
220 ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
223 fd.data = ds->case_file->datasheet;
224 fd.data_sheet = data_sheet;
226 fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
227 fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
228 fd.value_labels_checkbox =
229 get_widget_assert (fd.xml,
230 "find-value-labels-checkbutton");
232 fd.match_regexp_checkbox =
233 get_widget_assert (fd.xml,
234 "find-match-regexp-checkbutton");
236 fd.match_substring_checkbox =
237 get_widget_assert (fd.xml,
238 "find-match-substring-checkbutton");
242 gtk_window_set_transient_for (GTK_WINDOW (dialog), de->parent.window);
245 attach_dictionary_to_treeview (GTK_TREE_VIEW (source),
247 GTK_SELECTION_SINGLE,
250 psppire_selector_set_subjects (PSPPIRE_SELECTOR (selector),
253 insert_source_row_into_entry,
254 is_currently_in_entry,
258 g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
260 g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
262 g_signal_connect (selector, "selected",
263 G_CALLBACK (on_select), &fd);
265 g_signal_connect (selector, "de-selected",
266 G_CALLBACK (on_deselect), &fd);
268 g_signal_connect (fd.value_labels_checkbox, "toggled",
269 G_CALLBACK (value_labels_toggled), &fd);
272 psppire_dialog_run (PSPPIRE_DIALOG (dialog));
274 g_object_unref (fd.xml);
281 forward (casenumber *i, struct datasheet *data UNUSED)
288 forward_wrap (casenumber *i, struct datasheet *data)
290 if ( ++*i >= datasheet_get_row_cnt (data) ) *i = 0;
294 backward (casenumber *i, struct datasheet *data UNUSED)
301 backward_wrap (casenumber *i, struct datasheet *data)
304 *i = datasheet_get_row_cnt (data) - 1;
308 /* Current plus one */
310 cp1 (casenumber current, struct datasheet *data)
315 /* Current plus one, circular */
317 cp1c (casenumber current, struct datasheet *data)
319 casenumber next = current;
321 forward_wrap (&next, data);
327 /* Current minus one */
329 cm1 (casenumber current, struct datasheet *data)
334 /* Current minus one, circular */
336 cm1c (casenumber current, struct datasheet *data)
338 casenumber next = current;
340 backward_wrap (&next, data);
347 last (casenumber current, struct datasheet *data)
349 return datasheet_get_row_cnt (data) ;
353 minus1 (casenumber current, struct datasheet *data)
358 /* An type to facilitate iterating through casenumbers */
359 struct casenum_iterator
361 /* returns the first case to access */
362 casenumber (*start) (casenumber, struct datasheet *);
364 /* Returns one past the last case to access */
365 casenumber (*end) (casenumber, struct datasheet *);
367 /* Sets the first arg to the next case to access */
368 void (*next) (casenumber *, struct datasheet *);
379 static const struct casenum_iterator ip[n_iterators] =
381 {cp1, last, forward},
382 {cp1c, cm1, forward_wrap},
383 {cm1, minus1, backward},
384 {cm1c, cp1, backward_wrap}
389 /* A factory returning an iterator according to the dialog box's settings */
390 static const struct casenum_iterator *
391 get_iteration_params (const struct find_dialog *fd)
393 gboolean wrap = gtk_toggle_button_get_active
394 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
396 gboolean reverse = gtk_toggle_button_get_active
397 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
402 return &ip[REVERSE_WRAP];
404 return &ip[FORWARD_WRAP];
416 enum string_cmp_flags
418 STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
420 STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
422 STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
427 /* An abstract base type for comparing union values against a reference */
430 const struct variable *var;
431 enum string_cmp_flags flags;
433 bool (*compare) (const struct comparator *,
434 const union value *);
436 void (*destroy) (struct comparator *);
440 /* A comparator which operates on the unadulterated union values */
441 struct value_comparator
443 struct comparator parent;
444 union value *pattern;
447 /* A comparator which matches string values or parts thereof */
448 struct string_comparator
450 struct comparator parent;
454 /* A comparator to match string values against a POSIX.2 regular expression */
455 struct regexp_comparator
457 struct comparator parent;
463 value_compare (const struct comparator *cmptr,
464 const union value *v)
466 const struct value_comparator *vc = (const struct value_comparator *) cmptr;
467 return 0 == compare_values (v, vc->pattern, var_get_width (cmptr->var));
471 /* Return true if the label of VAL matches the reference string*/
473 string_label_compare (const struct comparator *cmptr,
474 const union value *val)
476 const struct string_comparator *ssc =
477 (const struct string_comparator *) cmptr;
479 const char *text = var_lookup_value_label (cmptr->var, val);
480 int width = strlen (text);
482 assert ( cmptr->flags & STR_CMP_LABELS);
484 g_return_val_if_fail (width > 0, false);
486 if ( cmptr->flags & STR_CMP_SUBSTR)
487 return (NULL != g_strstr_len (text, width, ssc->pattern));
489 return (0 == strncmp (text, ssc->pattern, width));
492 /* Return true if VAL matches the reference string*/
494 string_value_compare (const struct comparator *cmptr,
495 const union value *val)
497 const struct string_comparator *ssc =
498 (const struct string_comparator *) cmptr;
500 const char *text = val->s;
501 int width = var_get_width (cmptr->var);
503 assert ( ! (cmptr->flags & STR_CMP_LABELS));
505 g_return_val_if_fail (width > 0, false);
507 if ( cmptr->flags & STR_CMP_SUBSTR)
508 return (NULL != g_strstr_len (text, width, ssc->pattern));
510 return (0 == strncmp (text, ssc->pattern, width));
515 /* Return true if VAL matched the regexp */
517 regexp_value_compare (const struct comparator *cmptr,
518 const union value *val)
522 const struct regexp_comparator *rec =
523 (const struct regexp_comparator *) cmptr;
525 int width = var_get_width (cmptr->var);
527 assert ( ! (cmptr->flags & STR_CMP_LABELS) );
529 g_return_val_if_fail (width > 0, false);
531 /* We must remove trailing whitespace, otherwise $ will not match where
533 text = g_strndup (val->s, width);
536 retval = (0 == regexec (&rec->re, text, 0, 0, 0));
543 /* Return true if the label of VAL matched the regexp */
545 regexp_label_compare (const struct comparator *cmptr,
546 const union value *val)
549 const struct regexp_comparator *rec =
550 (const struct regexp_comparator *) cmptr;
554 assert ( cmptr->flags & STR_CMP_LABELS);
556 text = var_lookup_value_label (cmptr->var, val);
557 width = strlen (text);
559 g_return_val_if_fail (width > 0, false);
561 return (0 == regexec (&rec->re, text, 0, 0, 0));
567 regexp_destroy (struct comparator *cmptr)
569 struct regexp_comparator *rec = (struct regexp_comparator *) cmptr;
575 value_destroy (struct comparator *cmptr)
577 struct value_comparator *vc = (struct value_comparator *) cmptr;
582 static struct comparator *
583 value_comparator_create (const struct variable *var, const char *target)
585 const struct fmt_spec *fmt;
587 struct value_comparator *vc = xzalloc (sizeof (*vc));
588 struct comparator *cmptr = (struct comparator *) vc;
592 cmptr->compare = value_compare ;
593 cmptr->destroy = value_destroy;
595 width = var_get_width (var);
596 fmt = var_get_write_format (var);
598 vc->pattern = value_create (width);
600 if ( ! data_in (ss_cstr (target),
604 vc->pattern, width) )
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 = (struct comparator *) ssc;
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 = (struct comparator *) rec;
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)
685 if ( cmptr->destroy )
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 value_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;
720 g_assert (current_row >= 0);
722 var = dict_lookup_var (fd->dict->dict, var_name);
726 width = var_get_width (var);
728 *column = var_get_dict_index (var);
731 if ( gtk_toggle_button_get_active
732 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
733 flags |= STR_CMP_SUBSTR;
735 if ( gtk_toggle_button_get_active
736 (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
737 flags |= STR_CMP_REGEXP;
739 if ( gtk_toggle_button_get_active
740 (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
741 flags |= STR_CMP_LABELS;
744 union value *val = value_create (width);
746 const struct casenum_iterator *ip = get_iteration_params (fd);
747 struct comparator *cmptr =
748 comparator_factory (var, target_string, flags);
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),
760 if ( comparator_compare (cmptr, val))
768 comparator_destroy (cmptr);