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/cast.h>
38 #include <libpspp/message.h>
46 #define _(msgid) gettext (msgid)
47 #define N_(msgid) msgid
50 /* FIXME: These shouldn't be here */
51 #include "psppire-var-store.h"
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 g_object_get (fd->de->data_editor, "current-case", &row, NULL);
113 find_value (fd, row, &x, &column);
118 gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
119 PSPPIRE_DATA_EDITOR_DATA_VIEW);
121 g_object_set (fd->de->data_editor,
123 "current-variable", column,
129 /* Callback on the selector.
130 It gets invoked whenever a variable is selected */
132 on_select (GtkEntry *entry, gpointer data)
134 struct find_dialog *fd = data;
135 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
136 struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
137 gboolean search_labels ;
139 g_return_if_fail (var);
141 gtk_widget_set_sensitive (fd->value_labels_checkbox,
142 var_has_value_labels (var));
145 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
147 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
148 var_is_alpha (var) || search_labels);
151 gtk_widget_set_sensitive (fd->match_substring_checkbox,
152 var_is_alpha (var) || search_labels);
155 /* Callback on the selector.
156 It gets invoked whenever a variable is unselected */
158 on_deselect (GtkEntry *entry, gpointer data)
160 struct find_dialog *fd = data;
162 gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
163 gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
164 gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
168 value_labels_toggled (GtkToggleButton *tb, gpointer data)
170 struct find_dialog *fd = data;
172 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
173 const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
175 gboolean active = gtk_toggle_button_get_active (tb) ;
177 gtk_widget_set_sensitive (fd->match_substring_checkbox,
178 active || (var && var_is_alpha (var)));
180 gtk_widget_set_sensitive (fd->match_regexp_checkbox,
181 active || (var && var_is_alpha (var)));
184 /* Pops up the Find dialog box
187 find_dialog (GObject *o, gpointer data)
189 PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
191 struct find_dialog fd;
196 GtkWidget *find_button;
198 GtkWidget *buttonbox;
200 PsppireVarStore *vs ;
201 PsppireDataStore *ds ;
203 fd.xml = builder_new ("find.ui");
206 find_button = gtk_button_new_from_stock (GTK_STOCK_FIND);
207 gtk_widget_show (find_button);
209 buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
211 psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
212 gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
214 dialog = get_widget_assert (fd.xml, "find-dialog");
215 source = get_widget_assert (fd.xml, "find-variable-treeview");
216 selector = get_widget_assert (fd.xml, "find-selector");
218 g_object_get (de->data_editor,
223 g_object_get (vs, "dictionary", &fd.dict, NULL);
225 fd.data = ds->datasheet;
227 fd.variable_entry = get_widget_assert (fd.xml, "find-variable-entry");
228 fd.value_entry = get_widget_assert (fd.xml, "find-value-entry");
229 fd.value_labels_checkbox =
230 get_widget_assert (fd.xml,
231 "find-value-labels-checkbutton");
233 fd.match_regexp_checkbox =
234 get_widget_assert (fd.xml,
235 "find-match-regexp-checkbutton");
237 fd.match_substring_checkbox =
238 get_widget_assert (fd.xml,
239 "find-match-substring-checkbutton");
243 gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
246 g_object_set (source, "model", fd.dict,
247 "selection-mode", GTK_SELECTION_SINGLE,
251 psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
252 is_currently_in_entry);
254 g_signal_connect (dialog, "refresh", G_CALLBACK (refresh), &fd);
256 g_signal_connect (find_button, "clicked", G_CALLBACK (do_find), &fd);
258 g_signal_connect (selector, "selected",
259 G_CALLBACK (on_select), &fd);
261 g_signal_connect (selector, "de-selected",
262 G_CALLBACK (on_deselect), &fd);
264 g_signal_connect (fd.value_labels_checkbox, "toggled",
265 G_CALLBACK (value_labels_toggled), &fd);
268 psppire_dialog_run (PSPPIRE_DIALOG (dialog));
270 g_object_unref (fd.xml);
277 forward (casenumber *i, struct datasheet *data UNUSED)
284 forward_wrap (casenumber *i, struct datasheet *data)
286 if ( ++*i >= datasheet_get_n_rows (data) ) *i = 0;
290 backward (casenumber *i, struct datasheet *data UNUSED)
297 backward_wrap (casenumber *i, struct datasheet *data)
300 *i = datasheet_get_n_rows (data) - 1;
304 /* Current plus one */
306 cp1 (casenumber current, struct datasheet *data)
311 /* Current plus one, circular */
313 cp1c (casenumber current, struct datasheet *data)
315 casenumber next = current;
317 forward_wrap (&next, data);
323 /* Current minus one */
325 cm1 (casenumber current, struct datasheet *data)
330 /* Current minus one, circular */
332 cm1c (casenumber current, struct datasheet *data)
334 casenumber next = current;
336 backward_wrap (&next, data);
343 last (casenumber current, struct datasheet *data)
345 return datasheet_get_n_rows (data) ;
349 minus1 (casenumber current, struct datasheet *data)
354 /* An type to facilitate iterating through casenumbers */
355 struct casenum_iterator
357 /* returns the first case to access */
358 casenumber (*start) (casenumber, struct datasheet *);
360 /* Returns one past the last case to access */
361 casenumber (*end) (casenumber, struct datasheet *);
363 /* Sets the first arg to the next case to access */
364 void (*next) (casenumber *, struct datasheet *);
375 static const struct casenum_iterator ip[n_iterators] =
377 {cp1, last, forward},
378 {cp1c, cm1, forward_wrap},
379 {cm1, minus1, backward},
380 {cm1c, cp1, backward_wrap}
385 /* A factory returning an iterator according to the dialog box's settings */
386 static const struct casenum_iterator *
387 get_iteration_params (const struct find_dialog *fd)
389 gboolean wrap = gtk_toggle_button_get_active
390 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
392 gboolean reverse = gtk_toggle_button_get_active
393 (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
398 return &ip[REVERSE_WRAP];
400 return &ip[FORWARD_WRAP];
412 enum string_cmp_flags
414 STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
416 STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
418 STR_CMP_LABELS = 0x04 /* Match against the values' labels instead
423 /* An abstract base type for comparing union values against a reference */
426 const struct variable *var;
427 enum string_cmp_flags flags;
428 const PsppireDict *dict;
430 bool (*compare) (const struct comparator *,
431 const union value *);
433 void (*destroy) (struct comparator *);
437 /* A comparator which operates on the unadulterated union values */
438 struct value_comparator
440 struct comparator parent;
444 /* A comparator which matches string values or parts thereof */
445 struct string_comparator
447 struct comparator parent;
451 /* A comparator to match string values against a POSIX.2 regular expression */
452 struct regexp_comparator
454 struct comparator parent;
460 value_compare (const struct comparator *cmptr,
461 const union value *v)
463 const struct value_comparator *vc = (const struct value_comparator *) cmptr;
464 return 0 == value_compare_3way (v, &vc->pattern, var_get_width (cmptr->var));
468 /* Return true if the label of VAL matches the reference string*/
470 string_label_compare (const struct comparator *cmptr,
471 const union value *val)
473 const struct string_comparator *ssc =
474 (const struct string_comparator *) cmptr;
476 const char *text = var_lookup_value_label (cmptr->var, val);
477 int width = strlen (text);
479 assert ( cmptr->flags & STR_CMP_LABELS);
481 g_return_val_if_fail (width > 0, false);
483 if ( cmptr->flags & STR_CMP_SUBSTR)
484 return (NULL != g_strstr_len (text, width, ssc->pattern));
486 return (0 == strncmp (text, ssc->pattern, width));
489 /* Return true if VAL matches the reference string*/
491 string_value_compare (const struct comparator *cmptr,
492 const union value *val)
496 const struct string_comparator *ssc =
497 (const struct string_comparator *) cmptr;
499 int width = var_get_width (cmptr->var);
500 g_return_val_if_fail (width > 0, false);
501 assert ( ! (cmptr->flags & STR_CMP_LABELS));
503 text = value_to_text (*val, cmptr->dict, *var_get_write_format (cmptr->var));
505 if ( cmptr->flags & STR_CMP_SUBSTR)
506 found = (NULL != g_strstr_len (text, width, ssc->pattern));
508 found = (0 == strncmp (text, ssc->pattern, width));
516 /* Return true if VAL matched the regexp */
518 regexp_value_compare (const struct comparator *cmptr,
519 const union value *val)
523 const struct regexp_comparator *rec =
524 (const struct regexp_comparator *) cmptr;
526 int width = var_get_width (cmptr->var);
528 assert ( ! (cmptr->flags & STR_CMP_LABELS) );
530 g_return_val_if_fail (width > 0, false);
532 text = value_to_text (*val, cmptr->dict, *var_get_write_format (cmptr->var));
533 /* We must remove trailing whitespace, otherwise $ will not match where
537 retval = (0 == regexec (&rec->re, text, 0, 0, 0));
544 /* Return true if the label of VAL matched the regexp */
546 regexp_label_compare (const struct comparator *cmptr,
547 const union value *val)
550 const struct regexp_comparator *rec =
551 (const struct regexp_comparator *) cmptr;
555 assert ( cmptr->flags & STR_CMP_LABELS);
557 text = var_lookup_value_label (cmptr->var, val);
558 width = strlen (text);
560 g_return_val_if_fail (width > 0, false);
562 return (0 == regexec (&rec->re, text, 0, 0, 0));
568 regexp_destroy (struct comparator *cmptr)
570 struct regexp_comparator *rec
571 = UP_CAST (cmptr, struct regexp_comparator, parent);
577 cmptr_value_destroy (struct comparator *cmptr)
579 struct value_comparator *vc
580 = UP_CAST (cmptr, struct value_comparator, parent);
581 value_destroy (&vc->pattern, var_get_width (cmptr->var));
585 static struct comparator *
586 value_comparator_create (const struct variable *var, const PsppireDict *dict, const char *target)
588 struct value_comparator *vc = xzalloc (sizeof (*vc));
589 struct comparator *cmptr = &vc->parent;
593 cmptr->compare = value_compare ;
594 cmptr->destroy = cmptr_value_destroy;
597 text_to_value (target, dict, var, &vc->pattern);
602 static struct comparator *
603 string_comparator_create (const struct variable *var, const PsppireDict *dict,
605 enum string_cmp_flags flags)
607 struct string_comparator *ssc = xzalloc (sizeof (*ssc));
608 struct comparator *cmptr = &ssc->parent;
610 cmptr->flags = flags;
614 if ( flags & STR_CMP_LABELS)
615 cmptr->compare = string_label_compare;
617 cmptr->compare = string_value_compare;
619 ssc->pattern = target;
625 static struct comparator *
626 regexp_comparator_create (const struct variable *var, const PsppireDict *dict, const char *target,
627 enum string_cmp_flags flags)
630 struct regexp_comparator *rec = xzalloc (sizeof (*rec));
631 struct comparator *cmptr = &rec->parent;
633 cmptr->flags = flags;
636 cmptr->compare = (flags & STR_CMP_LABELS)
637 ? regexp_label_compare : regexp_value_compare ;
639 cmptr->destroy = regexp_destroy;
641 code = regcomp (&rec->re, target, 0);
645 size_t errbuf_size = regerror (code, &rec->re, errbuf, 0);
647 errbuf = xmalloc (errbuf_size);
649 regerror (code, &rec->re, errbuf, errbuf_size);
651 msg (ME, _("Bad regular expression: %s"), errbuf);
662 /* Compare V against CMPTR's reference */
664 comparator_compare (const struct comparator *cmptr,
665 const union value *v)
667 return cmptr->compare (cmptr, v);
672 comparator_destroy (struct comparator *cmptr)
677 if ( cmptr->destroy )
678 cmptr->destroy (cmptr);
684 static struct comparator *
685 comparator_factory (const struct variable *var, const PsppireDict *dict, const char *str,
686 enum string_cmp_flags flags)
688 if ( flags & STR_CMP_REGEXP )
689 return regexp_comparator_create (var, dict, str, flags);
691 if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
692 return string_comparator_create (var, dict, str, flags);
694 return value_comparator_create (var, dict, str);
698 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
699 After the function returns, *ROW contains the row and *COLUMN the column.
700 If no such case is found, then *ROW will be set to -1
703 find_value (const struct find_dialog *fd, casenumber current_row,
704 casenumber *row, int *column)
707 const struct variable *var;
708 const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
709 const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
711 enum string_cmp_flags flags = 0;
712 g_assert (current_row >= 0);
714 var = dict_lookup_var (fd->dict->dict, var_name);
718 width = var_get_width (var);
720 *column = var_get_dict_index (var);
723 if ( gtk_toggle_button_get_active
724 (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
725 flags |= STR_CMP_SUBSTR;
727 if ( gtk_toggle_button_get_active
728 (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
729 flags |= STR_CMP_REGEXP;
731 if ( gtk_toggle_button_get_active
732 (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
733 flags |= STR_CMP_LABELS;
738 const struct casenum_iterator *ip = get_iteration_params (fd);
739 struct comparator *cmptr =
740 comparator_factory (var, fd->dict, target_string, flags);
742 value_init (&val, width);
746 for (i = ip->start (current_row, fd->data);
747 i != ip->end (current_row, fd->data);
748 ip->next (&i, fd->data))
750 datasheet_get_value (fd->data, i, var_get_case_index (var), &val);
752 if ( comparator_compare (cmptr, &val))
760 comparator_destroy (cmptr);
761 value_destroy (&val, width);