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
)
104 SswSheet
*sheet
= SSW_SHEET (fd
->de
->data_editor
->data_sheet
);
109 ssw_sheet_get_active_cell (sheet
, &unused
, &row
);
111 find_value (fd
, row
, &x
, &column
);
115 gtk_notebook_set_current_page (GTK_NOTEBOOK (fd
->de
->data_editor
),
116 PSPPIRE_DATA_EDITOR_DATA_VIEW
);
118 ssw_sheet_scroll_to (sheet
, column
, x
);
119 ssw_sheet_set_active_cell (sheet
, column
, x
, NULL
);
123 /* Callback on the selector.
124 It gets invoked whenever a variable is selected */
126 on_select (GtkEntry
*entry
, gpointer data
)
128 struct find_dialog
*fd
= data
;
129 const char *var_name
= gtk_entry_get_text (GTK_ENTRY (fd
->variable_entry
));
130 struct variable
*var
= dict_lookup_var (fd
->dict
->dict
, var_name
);
131 gboolean search_labels
;
133 g_return_if_fail (var
);
135 gtk_widget_set_sensitive (fd
->value_labels_checkbox
,
136 var_has_value_labels (var
));
139 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd
->value_labels_checkbox
));
141 gtk_widget_set_sensitive (fd
->match_regexp_checkbox
,
142 var_is_alpha (var
) || search_labels
);
145 gtk_widget_set_sensitive (fd
->match_substring_checkbox
,
146 var_is_alpha (var
) || search_labels
);
149 /* Callback on the selector.
150 It gets invoked whenever a variable is unselected */
152 on_deselect (GtkEntry
*entry
, gpointer data
)
154 struct find_dialog
*fd
= data
;
156 gtk_widget_set_sensitive (fd
->value_labels_checkbox
, FALSE
);
157 gtk_widget_set_sensitive (fd
->match_substring_checkbox
, FALSE
);
158 gtk_widget_set_sensitive (fd
->match_regexp_checkbox
, FALSE
);
162 value_labels_toggled (GtkToggleButton
*tb
, gpointer data
)
164 struct find_dialog
*fd
= data
;
166 const char *var_name
= gtk_entry_get_text (GTK_ENTRY (fd
->variable_entry
));
167 const struct variable
*var
= dict_lookup_var (fd
->dict
->dict
, var_name
);
169 gboolean active
= gtk_toggle_button_get_active (tb
) ;
171 gtk_widget_set_sensitive (fd
->match_substring_checkbox
,
172 active
|| (var
&& var_is_alpha (var
)));
174 gtk_widget_set_sensitive (fd
->match_regexp_checkbox
,
175 active
|| (var
&& var_is_alpha (var
)));
178 /* Pops up the Find dialog box
181 find_dialog (PsppireDataWindow
*de
)
183 struct find_dialog fd
;
188 GtkWidget
*find_button
;
190 GtkWidget
*buttonbox
;
192 PsppireDataStore
*ds
;
194 fd
.xml
= builder_new ("find.ui");
197 find_button
= gtk_button_new_with_label (_("Find"));
198 gtk_widget_show (find_button
);
200 buttonbox
= get_widget_assert (fd
.xml
, "find-buttonbox");
202 psppire_box_pack_start_defaults (GTK_BOX (buttonbox
), find_button
);
203 gtk_box_reorder_child (GTK_BOX (buttonbox
), find_button
, 0);
205 dialog
= get_widget_assert (fd
.xml
, "find-dialog");
206 source
= get_widget_assert (fd
.xml
, "find-variable-treeview");
207 selector
= get_widget_assert (fd
.xml
, "find-selector");
209 g_object_get (de
->data_editor
,
210 "dictionary", &fd
.dict
,
214 fd
.data
= ds
->datasheet
;
216 fd
.variable_entry
= get_widget_assert (fd
.xml
, "find-variable-entry");
217 fd
.value_entry
= get_widget_assert (fd
.xml
, "find-value-entry");
218 fd
.value_labels_checkbox
=
219 get_widget_assert (fd
.xml
,
220 "find-value-labels-checkbutton");
222 fd
.match_regexp_checkbox
=
223 get_widget_assert (fd
.xml
,
224 "find-match-regexp-checkbutton");
226 fd
.match_substring_checkbox
=
227 get_widget_assert (fd
.xml
,
228 "find-match-substring-checkbutton");
232 gtk_window_set_transient_for (GTK_WINDOW (dialog
), GTK_WINDOW (de
));
235 g_object_set (source
, "model", fd
.dict
,
236 "selection-mode", GTK_SELECTION_SINGLE
,
240 psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector
),
241 is_currently_in_entry
);
243 g_signal_connect (dialog
, "refresh", G_CALLBACK (refresh
), &fd
);
245 g_signal_connect (find_button
, "clicked", G_CALLBACK (do_find
), &fd
);
247 g_signal_connect (selector
, "selected",
248 G_CALLBACK (on_select
), &fd
);
250 g_signal_connect (selector
, "de-selected",
251 G_CALLBACK (on_deselect
), &fd
);
253 g_signal_connect (fd
.value_labels_checkbox
, "toggled",
254 G_CALLBACK (value_labels_toggled
), &fd
);
257 psppire_dialog_run (PSPPIRE_DIALOG (dialog
));
259 g_object_unref (fd
.xml
);
266 forward (casenumber
*i
, struct datasheet
*data UNUSED
)
273 forward_wrap (casenumber
*i
, struct datasheet
*data
)
275 if (++*i
>= datasheet_get_n_rows (data
)) *i
= 0;
279 backward (casenumber
*i
, struct datasheet
*data UNUSED
)
286 backward_wrap (casenumber
*i
, struct datasheet
*data
)
289 *i
= datasheet_get_n_rows (data
) - 1;
293 /* Current plus one */
295 cp1 (casenumber current
, struct datasheet
*data
)
300 /* Current plus one, circular */
302 cp1c (casenumber current
, struct datasheet
*data
)
304 casenumber next
= current
;
306 forward_wrap (&next
, data
);
312 /* Current minus one */
314 cm1 (casenumber current
, struct datasheet
*data
)
317 return datasheet_get_n_rows (data
) - 1;
322 /* Current minus one, circular */
324 cm1c (casenumber current
, struct datasheet
*data
)
326 casenumber next
= current
;
328 backward_wrap (&next
, data
);
335 last (casenumber current
, struct datasheet
*data
)
337 return datasheet_get_n_rows (data
) ;
341 minus1 (casenumber current
, struct datasheet
*data
)
346 /* A type to facilitate iterating through casenumbers */
347 struct casenum_iterator
349 /* returns the first case to access */
350 casenumber (*start
) (casenumber
, struct datasheet
*);
352 /* Returns one past the last case to access */
353 casenumber (*end
) (casenumber
, struct datasheet
*);
355 /* Sets the first arg to the next case to access */
356 void (*next
) (casenumber
*, struct datasheet
*);
367 static const struct casenum_iterator case_iterator
[n_iterators
] =
369 /* Forward iterator (linear) */
370 {cp1
, last
, forward
},
372 /* Forward iterator (circular) */
373 {cp1c
, cm1
, forward_wrap
},
375 /* Reverse iterator (linear) */
376 {cm1
, minus1
, backward
},
378 /* Reverse iterator (circular */
379 {cm1c
, cp1
, backward_wrap
}
384 /* A factory returning an iterator according to the dialog box's settings */
385 static const struct casenum_iterator
*
386 get_iteration_params (const struct find_dialog
*fd
)
388 gboolean wrap
= gtk_toggle_button_get_active
389 (GTK_TOGGLE_BUTTON (get_widget_assert (fd
->xml
, "find-wrap")));
391 gboolean reverse
= gtk_toggle_button_get_active
392 (GTK_TOGGLE_BUTTON (get_widget_assert (fd
->xml
, "find-backwards")));
397 return &case_iterator
[REVERSE_WRAP
];
399 return &case_iterator
[FORWARD_WRAP
];
404 return &case_iterator
[REVERSE
];
406 return &case_iterator
[FORWARD
];
411 enum string_cmp_flags
413 STR_CMP_SUBSTR
= 0x01, /* Find strings which are substrings of the
415 STR_CMP_REGEXP
= 0x02, /* Match against a regular expression */
417 STR_CMP_LABELS
= 0x04 /* Match against the values' labels instead
422 /* An abstract base type for comparing union values against a reference */
425 const struct variable
*var
;
426 enum string_cmp_flags flags
;
428 bool (*compare
) (const struct comparator
*,
429 const union value
*);
431 void (*destroy
) (struct comparator
*);
435 /* A comparator which operates on the numerical values,
436 rounded to the number of decimal places indicated by
437 the variable's format. */
438 struct numeric_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
;
458 /* Returns 10 raised to the power of X.
459 X must be a non-negative integer. */
472 value_compare (const struct comparator
*cmptr
,
473 const union value
*v
)
475 const struct numeric_comparator
*nc
= (const struct numeric_comparator
*) cmptr
;
476 struct fmt_spec fs
= var_get_print_format (cmptr
->var
);
478 double c
= nearbyint (v
->f
* int_pow10 (fs
.d
));
480 return c
== nc
->rounded_ref
;
484 /* Return true if the label of VAL matches the reference string*/
486 string_label_compare (const struct comparator
*cmptr
,
487 const union value
*val
)
489 const struct string_comparator
*ssc
=
490 (const struct string_comparator
*) cmptr
;
494 const char *text
= var_lookup_value_label (cmptr
->var
, val
);
498 width
= strlen (text
);
500 assert (cmptr
->flags
& STR_CMP_LABELS
);
502 g_return_val_if_fail (width
> 0, false);
504 if (cmptr
->flags
& STR_CMP_SUBSTR
)
505 return (NULL
!= g_strstr_len (text
, width
, ssc
->pattern
));
507 return (0 == strncmp (text
, ssc
->pattern
, width
));
510 /* Return true if VAL matches the reference string*/
512 string_value_compare (const struct comparator
*cmptr
,
513 const union value
*val
)
517 const struct string_comparator
*ssc
=
518 (const struct string_comparator
*) cmptr
;
520 int width
= var_get_width (cmptr
->var
);
521 g_return_val_if_fail (width
> 0, false);
522 assert (! (cmptr
->flags
& STR_CMP_LABELS
));
524 text
= value_to_text (*val
, cmptr
->var
);
526 if (cmptr
->flags
& STR_CMP_SUBSTR
)
527 found
= (NULL
!= g_strstr_len (text
, width
, ssc
->pattern
));
529 found
= (0 == strncmp (text
, ssc
->pattern
, width
));
537 /* Return true if VAL matched the regexp */
539 regexp_value_compare (const struct comparator
*cmptr
,
540 const union value
*val
)
544 const struct regexp_comparator
*rec
=
545 (const struct regexp_comparator
*) cmptr
;
547 int width
= var_get_width (cmptr
->var
);
549 assert (! (cmptr
->flags
& STR_CMP_LABELS
));
551 g_return_val_if_fail (width
> 0, false);
553 text
= value_to_text (*val
, cmptr
->var
);
554 /* We must remove trailing whitespace, otherwise $ will not match where
558 retval
= (0 == regexec (&rec
->re
, text
, 0, 0, 0));
565 /* Return true if the label of VAL matched the regexp */
567 regexp_label_compare (const struct comparator
*cmptr
,
568 const union value
*val
)
571 const struct regexp_comparator
*rec
=
572 (const struct regexp_comparator
*) cmptr
;
576 assert (cmptr
->flags
& STR_CMP_LABELS
);
578 text
= var_lookup_value_label (cmptr
->var
, val
);
579 width
= strlen (text
);
581 g_return_val_if_fail (width
> 0, false);
583 return (0 == regexec (&rec
->re
, text
, 0, 0, 0));
589 regexp_destroy (struct comparator
*cmptr
)
591 struct regexp_comparator
*rec
592 = UP_CAST (cmptr
, struct regexp_comparator
, parent
);
597 static struct comparator
*
598 numeric_comparator_create (const struct variable
*var
, const char *target
)
600 struct numeric_comparator
*nc
= XZALLOC (struct numeric_comparator
);
601 struct comparator
*cmptr
= &nc
->parent
;
605 cmptr
->compare
= value_compare
;
606 struct fmt_spec fs
= var_get_write_format (var
);
609 text_to_value (target
, var
, &val
);
610 nc
->rounded_ref
= nearbyint (val
.f
* int_pow10 (fs
.d
));
611 value_destroy (&val
, var_get_width (var
));
616 static struct comparator
*
617 string_comparator_create (const struct variable
*var
, const char *target
,
618 enum string_cmp_flags flags
)
620 struct string_comparator
*ssc
= XZALLOC (struct string_comparator
);
621 struct comparator
*cmptr
= &ssc
->parent
;
623 cmptr
->flags
= flags
;
626 if (flags
& STR_CMP_LABELS
)
627 cmptr
->compare
= string_label_compare
;
629 cmptr
->compare
= string_value_compare
;
631 ssc
->pattern
= target
;
637 static struct comparator
*
638 regexp_comparator_create (const struct variable
*var
, const char *target
,
639 enum string_cmp_flags flags
)
642 struct regexp_comparator
*rec
= XZALLOC (struct regexp_comparator
);
643 struct comparator
*cmptr
= &rec
->parent
;
645 cmptr
->flags
= flags
;
647 cmptr
->compare
= (flags
& STR_CMP_LABELS
)
648 ? regexp_label_compare
: regexp_value_compare
;
650 cmptr
->destroy
= regexp_destroy
;
652 code
= regcomp (&rec
->re
, target
, 0);
656 size_t errbuf_size
= regerror (code
, &rec
->re
, errbuf
, 0);
658 errbuf
= xmalloc (errbuf_size
);
660 regerror (code
, &rec
->re
, errbuf
, errbuf_size
);
662 msg (ME
, _("Bad regular expression: %s"), errbuf
);
673 /* Compare V against CMPTR's reference */
675 comparator_compare (const struct comparator
*cmptr
,
676 const union value
*v
)
678 return cmptr
->compare (cmptr
, v
);
683 comparator_destroy (struct comparator
*cmptr
)
689 cmptr
->destroy (cmptr
);
695 static struct comparator
*
696 comparator_factory (const struct variable
*var
, const char *str
,
697 enum string_cmp_flags flags
)
699 if (flags
& STR_CMP_REGEXP
)
700 return regexp_comparator_create (var
, str
, flags
);
702 if (flags
& (STR_CMP_SUBSTR
| STR_CMP_LABELS
))
703 return string_comparator_create (var
, str
, flags
);
705 return numeric_comparator_create (var
, str
);
709 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
710 After the function returns, *ROW contains the row and *COLUMN the column.
711 If no such case is found, then *ROW will be set to -1
714 find_value (const struct find_dialog
*fd
, casenumber current_row
,
715 casenumber
*row
, int *column
)
718 const struct variable
*var
;
719 const char *var_name
= gtk_entry_get_text (GTK_ENTRY (fd
->variable_entry
));
720 const char *target_string
= gtk_entry_get_text (GTK_ENTRY (fd
->value_entry
));
722 enum string_cmp_flags flags
= 0;
724 var
= dict_lookup_var (fd
->dict
->dict
, var_name
);
728 width
= var_get_width (var
);
730 *column
= var_get_dict_index (var
);
733 if (gtk_toggle_button_get_active
734 (GTK_TOGGLE_BUTTON (fd
->match_substring_checkbox
)))
735 flags
|= STR_CMP_SUBSTR
;
737 if (gtk_toggle_button_get_active
738 (GTK_TOGGLE_BUTTON (fd
->match_regexp_checkbox
)))
739 flags
|= STR_CMP_REGEXP
;
741 if (gtk_toggle_button_get_active
742 (GTK_TOGGLE_BUTTON (fd
->value_labels_checkbox
)))
743 flags
|= STR_CMP_LABELS
;
748 const struct casenum_iterator
*ip
= get_iteration_params (fd
);
749 struct comparator
*cmptr
=
750 comparator_factory (var
, target_string
, flags
);
752 value_init (&val
, width
);
756 for (i
= ip
->start (current_row
, fd
->data
);
757 i
!= ip
->end (current_row
, fd
->data
);
758 ip
->next (&i
, fd
->data
))
760 datasheet_get_value (fd
->data
, i
, var_get_dict_index (var
), &val
);
762 if (comparator_compare (cmptr
, &val
))
770 comparator_destroy (cmptr
);
771 value_destroy (&val
, width
);