xalloc.h-instead-of-alloc.h.patch from patch #6230.
[pspp-builds.git] / src / ui / gui / find-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007  Free Software Foundation
3
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.
8
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.
13
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/>. */
16
17
18 /* This module implements the "Find" dialog; a dialog box to locate cases
19 which match particular strings */
20
21 #include <config.h>
22
23 #include "find-dialog.h"
24 #include "psppire-selector.h"
25 #include "psppire-dialog.h"
26 #include "helper.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"
33 #include <ctype.h>
34 #include <sys/types.h>
35 #include <regex.h>
36 #include <libpspp/message.h>
37
38 #include <gtk/gtk.h>
39 #include <glade/glade.h>
40 #include <stdlib.h>
41
42 #include "xalloc.h"
43
44 #include <gettext.h>
45 #define _(msgid) gettext (msgid)
46 #define N_(msgid) msgid
47
48
49 /* FIXME: These shouldn't be here */
50 #include <gtksheet/gtksheet.h>
51 #include "psppire-var-store.h"
52
53 struct find_dialog
54 {
55   GladeXML *xml;
56   PsppireDict *dict;
57   GtkSheet *data_sheet;
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;
65 };
66
67 static void
68 find_value (const struct find_dialog *fd, casenumber current_row,
69            casenumber *row, int *column);
70
71
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.
75 */
76 static void
77 refresh (GObject *obj, const struct find_dialog *fd)
78 {
79   gtk_toggle_button_set_active
80     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
81      FALSE);
82
83   gtk_toggle_button_set_active
84     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
85      FALSE);
86
87   gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
88   gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
89
90   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
91                                 FALSE);
92
93   gtk_toggle_button_set_active
94     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
95
96
97   gtk_toggle_button_set_active
98     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
99 }
100
101 /* Callback on the "Find" button */
102 static void
103 do_find (GObject *obj, const struct find_dialog *fd)
104 {
105   casenumber x = -1;
106   gint column = -1;
107   gint row;
108   gtk_sheet_get_active_cell (fd->data_sheet, &row, NULL);
109
110   if ( row < 0 )
111     row = 0;
112
113   find_value (fd, row, &x, &column);
114
115   if ( x != -1)
116     {
117       data_editor_select_sheet (fd->de, PAGE_DATA_SHEET);
118
119       gtk_sheet_moveto (fd->data_sheet, x, column, 0.5, 0.5);
120
121       gtk_sheet_set_active_cell (fd->data_sheet, x, column);
122     }
123 }
124
125 /* Callback on the selector.
126    It gets invoked whenever a variable is selected */
127 static void
128 on_select (GtkEntry *entry, gpointer data)
129 {
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 ;
134
135   g_return_if_fail (var);
136
137   gtk_widget_set_sensitive (fd->value_labels_checkbox,
138                             var_has_value_labels (var));
139
140   search_labels =
141     gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
142
143   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
144                             var_is_alpha (var) || search_labels);
145
146
147   gtk_widget_set_sensitive (fd->match_substring_checkbox,
148                             var_is_alpha (var) || search_labels);
149 }
150
151 /* Callback on the selector.
152    It gets invoked whenever a variable is unselected */
153 static void
154 on_deselect (GtkEntry *entry, gpointer data)
155 {
156   struct find_dialog *fd = data;
157
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);
161 }
162
163 static void
164 value_labels_toggled (GtkToggleButton *tb, gpointer data)
165 {
166   struct find_dialog *fd = data;
167
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);
170
171   gboolean active = gtk_toggle_button_get_active  (tb) ;
172
173   gtk_widget_set_sensitive (fd->match_substring_checkbox,
174                             active || (var && var_is_alpha (var)));
175
176   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
177                               active || (var && var_is_alpha (var)));
178 }
179
180 /* Pops up the Find dialog box
181  */
182 void
183 find_dialog (GObject *o, gpointer data)
184 {
185   struct data_editor *de = data;
186
187   struct find_dialog fd;
188
189   GtkWidget *dialog ;
190   GtkWidget *source ;
191   GtkWidget *selector;
192   GtkWidget *find_button;
193
194   GtkSheet *var_sheet;
195   GtkSheet *data_sheet ;
196   GtkWidget *buttonbox;
197
198   PsppireVarStore *vs  ;
199   PsppireDataStore *ds ;
200
201   fd.xml = XML_NEW ("psppire.glade");
202   fd.de = de;
203
204   find_button = gtk_button_new_from_stock  (GTK_STOCK_FIND);
205   gtk_widget_show (find_button);
206
207   buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
208
209   gtk_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
210   gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
211
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");
215
216   var_sheet = GTK_SHEET (get_widget_assert (de->xml, "variable_sheet"));
217   data_sheet = GTK_SHEET (get_widget_assert (de->xml, "data_sheet"));
218
219   vs = PSPPIRE_VAR_STORE (gtk_sheet_get_model (var_sheet));
220   ds = PSPPIRE_DATA_STORE (gtk_sheet_get_model (data_sheet));
221
222   fd.dict = vs->dict;
223   fd.data = ds->case_file->datasheet;
224   fd.data_sheet = data_sheet;
225
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");
231
232   fd.match_regexp_checkbox =
233     get_widget_assert (fd.xml,
234                        "find-match-regexp-checkbutton");
235
236   fd.match_substring_checkbox =
237     get_widget_assert (fd.xml,
238                        "find-match-substring-checkbutton");
239
240
241
242   gtk_window_set_transient_for (GTK_WINDOW (dialog), de->parent.window);
243
244
245   attach_dictionary_to_treeview (GTK_TREE_VIEW (source),
246                                  fd.dict,
247                                  GTK_SELECTION_SINGLE,
248                                  NULL);
249
250   psppire_selector_set_subjects (PSPPIRE_SELECTOR (selector),
251                                  source,
252                                  fd.variable_entry,
253                                  insert_source_row_into_entry,
254                                  is_currently_in_entry
255                                  );
256
257   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &fd);
258
259   g_signal_connect (find_button, "clicked", G_CALLBACK (do_find),  &fd);
260
261   g_signal_connect (selector, "selected",
262                     G_CALLBACK (on_select),  &fd);
263
264   g_signal_connect (selector, "de-selected",
265                     G_CALLBACK (on_deselect),  &fd);
266
267   g_signal_connect (fd.value_labels_checkbox, "toggled",
268                     G_CALLBACK (value_labels_toggled),  &fd);
269
270
271   psppire_dialog_run (PSPPIRE_DIALOG (dialog));
272
273   g_object_unref (fd.xml);
274 }
275
276 \f
277 /* Iterators */
278
279 static void
280 forward (casenumber *i, struct datasheet *data UNUSED)
281 {
282   ++*i;
283 }
284
285
286 static void
287 forward_wrap (casenumber *i, struct datasheet *data)
288 {
289   if ( ++*i >=  datasheet_get_row_cnt (data) ) *i = 0;
290 }
291
292 static void
293 backward (casenumber *i, struct datasheet *data UNUSED)
294 {
295   --*i;
296 }
297
298
299 static void
300 backward_wrap (casenumber *i, struct datasheet *data)
301 {
302   if ( --*i < 0 )
303     *i = datasheet_get_row_cnt (data) - 1;
304 }
305
306
307 /* Current plus one */
308 static casenumber
309 cp1 (casenumber current, struct datasheet *data)
310 {
311   return current + 1;
312 }
313
314 /* Current plus one, circular */
315 static casenumber
316 cp1c (casenumber current, struct datasheet *data)
317 {
318   casenumber next = current;
319
320   forward_wrap (&next, data);
321
322   return next;
323 }
324
325
326 /* Current minus one */
327 static casenumber
328 cm1 (casenumber current, struct datasheet *data)
329 {
330   return current - 1;
331 }
332
333 /* Current minus one, circular */
334 static casenumber
335 cm1c (casenumber current, struct datasheet *data)
336 {
337   casenumber next = current;
338
339   backward_wrap (&next, data);
340
341   return next;
342 }
343
344
345 static casenumber
346 last (casenumber current, struct datasheet *data)
347 {
348   return datasheet_get_row_cnt (data) ;
349 }
350
351 static casenumber
352 minus1 (casenumber current, struct datasheet *data)
353 {
354   return -1;
355 }
356
357 /* An type to facilitate iterating through casenumbers */
358 struct casenum_iterator
359 {
360   /* returns the first case to access */
361   casenumber (*start) (casenumber, struct datasheet *);
362
363   /* Returns one past the last case to access */
364   casenumber (*end) (casenumber, struct datasheet *);
365
366   /* Sets the first arg to the next case to access */
367   void (*next) (casenumber *, struct datasheet *);
368 };
369
370 enum iteration_type{
371   FORWARD = 0,
372   FORWARD_WRAP,
373   REVERSE,
374   REVERSE_WRAP,
375   n_iterators
376 };
377
378 static struct casenum_iterator ip[n_iterators] =
379   {
380     {cp1, last, forward},
381     {cp1c, cm1, forward_wrap},
382     {cm1, minus1, backward},
383     {cm1c, cp1, backward_wrap}
384   };
385
386
387 \f
388 /* A factory returning an iterator according to the dialog box's settings */
389 static struct casenum_iterator *
390 get_iteration_params (const struct find_dialog *fd)
391 {
392   gboolean wrap = gtk_toggle_button_get_active
393     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
394
395   gboolean reverse = gtk_toggle_button_get_active
396     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
397
398   if ( wrap )
399     {
400       if ( reverse )
401         return &ip[REVERSE_WRAP];
402       else
403         return &ip[FORWARD_WRAP];
404     }
405   else
406     {
407       if ( reverse )
408         return &ip[REVERSE];
409       else
410         return &ip[FORWARD];
411     }
412 }
413
414
415 enum string_cmp_flags
416   {
417     STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
418                               values */
419     STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
420
421     STR_CMP_LABELS = 0x04  /* Match against the values' labels instead
422                               of the data */
423   };
424
425
426 /* An abstract base type for comparing union values against a reference */
427 struct comparator
428 {
429   const struct variable *var;
430   enum string_cmp_flags flags;
431
432   bool (*compare) (const struct comparator *,
433                    const union value *);
434
435   void (*destroy) (struct comparator *);
436 };
437
438
439 /* A comparator which operates on the unadulterated union values */
440 struct value_comparator
441 {
442   struct comparator parent;
443   union value *pattern;
444 };
445
446 /* A comparator which matches string values or parts thereof */
447 struct string_comparator
448 {
449   struct comparator parent;
450   const char *pattern;
451 };
452
453 /* A comparator to match string values against a POSIX.2 regular expression */
454 struct regexp_comparator
455 {
456   struct comparator parent;
457   regex_t re;
458 };
459
460
461 static bool
462 value_compare (const struct comparator *cmptr,
463                const union value *v)
464 {
465   const struct value_comparator *vc = (const struct value_comparator *) cmptr;
466   return 0 == compare_values (v, vc->pattern, var_get_width (cmptr->var));
467 }
468
469
470 /* Return true if the label of VAL matches the reference string*/
471 static bool
472 string_label_compare (const struct comparator *cmptr,
473                 const union value *val)
474 {
475   const struct string_comparator *ssc =
476     (const struct string_comparator *) cmptr;
477
478   const char *text = var_lookup_value_label (cmptr->var, val);
479   int width = strlen (text);
480
481   assert ( cmptr->flags & STR_CMP_LABELS);
482
483   g_return_val_if_fail (width > 0, false);
484
485   if ( cmptr->flags & STR_CMP_SUBSTR)
486     return (NULL != g_strstr_len (text, width, ssc->pattern));
487   else
488     return (0 == strncmp (text, ssc->pattern, width));
489 }
490
491 /* Return true if VAL matches the reference string*/
492 static bool
493 string_value_compare (const struct comparator *cmptr,
494                       const union value *val)
495 {
496   const struct string_comparator *ssc =
497     (const struct string_comparator *) cmptr;
498
499   const char *text = val->s;
500   int width = var_get_width (cmptr->var);
501
502   assert ( ! (cmptr->flags & STR_CMP_LABELS));
503
504   g_return_val_if_fail (width > 0, false);
505
506   if ( cmptr->flags & STR_CMP_SUBSTR)
507     return (NULL != g_strstr_len (text, width, ssc->pattern));
508   else
509     return (0 == strncmp (text, ssc->pattern, width));
510 }
511
512
513
514 /* Return true if VAL matched the regexp */
515 static bool
516 regexp_value_compare (const struct comparator *cmptr,
517                 const union value *val)
518 {
519   char *text;
520   bool retval;
521   const struct regexp_comparator *rec =
522     (const struct regexp_comparator *) cmptr;
523
524   int width = var_get_width (cmptr->var);
525
526   assert  ( ! (cmptr->flags & STR_CMP_LABELS) );
527
528   g_return_val_if_fail (width > 0, false);
529
530   /* We must remove trailing whitespace, otherwise $ will not match where
531      one would expect */
532   text = g_strndup (val->s, width);
533   g_strchomp (text);
534
535   retval = (0 == regexec (&rec->re, text, 0, 0, 0));
536
537   g_free (text);
538
539   return retval;
540 }
541
542 /* Return true if the label of VAL matched the regexp */
543 static bool
544 regexp_label_compare (const struct comparator *cmptr,
545                       const union value *val)
546 {
547   const char *text;
548   const struct regexp_comparator *rec =
549     (const struct regexp_comparator *) cmptr;
550
551   int width ;
552
553   assert ( cmptr->flags & STR_CMP_LABELS);
554
555   text = var_lookup_value_label (cmptr->var, val);
556   width = strlen (text);
557
558   g_return_val_if_fail (width > 0, false);
559
560   return (0 == regexec (&rec->re, text, 0, 0, 0));
561 }
562
563
564
565 static void
566 regexp_destroy (struct comparator *cmptr)
567 {
568   struct regexp_comparator *rec = (struct regexp_comparator *) cmptr;
569
570   regfree (&rec->re);
571 }
572
573 static void
574 value_destroy (struct comparator *cmptr)
575 {
576   struct value_comparator *vc = (struct value_comparator *) cmptr;
577   free (vc->pattern);
578 }
579
580
581 static struct comparator *
582 value_comparator_create (const struct variable *var, const char *target)
583 {
584   const struct fmt_spec *fmt;
585   int width ;
586   struct value_comparator *vc = xzalloc (sizeof (*vc));
587   struct comparator *cmptr = (struct comparator *) vc;
588
589   cmptr->flags = 0;
590   cmptr->var = var;
591   cmptr->compare  = value_compare ;
592   cmptr->destroy = value_destroy;
593
594   width = var_get_width (var);
595   fmt = var_get_write_format (var);
596
597   vc->pattern = value_create (width);
598
599   if ( ! data_in (ss_cstr (target),
600                   fmt->type,
601                   0, 0,
602                   vc->pattern, width) )
603     {
604       free (vc);
605       return NULL;
606     }
607
608   return cmptr;
609 }
610
611 static struct comparator *
612 string_comparator_create (const struct variable *var, const char *target,
613                           enum string_cmp_flags flags)
614 {
615   struct string_comparator *ssc = xzalloc (sizeof (*ssc));
616   struct comparator *cmptr = (struct comparator *) ssc;
617
618   cmptr->flags = flags;
619   cmptr->var = var;
620
621   if ( flags & STR_CMP_LABELS)
622     cmptr->compare = string_label_compare;
623   else
624     cmptr->compare = string_value_compare;
625
626   ssc->pattern = target;
627
628   return cmptr;
629 }
630
631
632 static struct comparator *
633 regexp_comparator_create (const struct variable *var, const char *target,
634                           enum string_cmp_flags flags)
635 {
636   int code;
637   struct regexp_comparator *rec = xzalloc (sizeof (*rec));
638   struct comparator *cmptr = (struct comparator *) rec;
639
640   cmptr->flags = flags;
641   cmptr->var = var;
642   cmptr->compare  = (flags & STR_CMP_LABELS)
643     ? regexp_label_compare : regexp_value_compare ;
644
645   cmptr->destroy  = regexp_destroy;
646
647   code = regcomp (&rec->re, target, 0);
648   if ( code != 0 )
649     {
650       char *errbuf = NULL;
651       size_t errbuf_size = regerror (code, &rec->re, errbuf,  0);
652
653       errbuf = xmalloc (errbuf_size);
654
655       regerror (code, &rec->re, errbuf, errbuf_size);
656
657       msg (ME, _("Bad regular expression: %s"), errbuf);
658
659       free ( cmptr);
660       free (errbuf);
661       return NULL;
662     }
663
664   return cmptr;
665 }
666
667
668 /* Compare V against CMPTR's reference */
669 static bool
670 comparator_compare (const struct comparator *cmptr,
671                     const union value *v)
672 {
673   return cmptr->compare (cmptr, v);
674 }
675
676 /* Destroy CMPTR */
677 static void
678 comparator_destroy (struct comparator *cmptr)
679 {
680   if ( ! cmptr )
681     return ;
682
683   if ( cmptr->destroy )
684     cmptr->destroy (cmptr);
685
686   free (cmptr);
687 }
688
689
690 static struct comparator *
691 comparator_factory (const struct variable *var, const char *str,
692                     enum string_cmp_flags flags)
693 {
694   if ( flags & STR_CMP_REGEXP )
695     return regexp_comparator_create (var, str, flags);
696
697   if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
698     return string_comparator_create (var, str, flags);
699
700   return value_comparator_create (var, str);
701 }
702
703
704 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
705    After the function returns, *ROW contains the row and *COLUMN the column.
706    If no such case is found, then *ROW will be set to -1
707  */
708 static void
709 find_value (const struct find_dialog *fd, casenumber current_row,
710            casenumber *row, int *column)
711 {
712   int width;
713   const struct variable *var;
714   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
715   const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
716
717   enum string_cmp_flags flags = 0;
718   g_assert (current_row >= 0);
719
720   var = dict_lookup_var (fd->dict->dict, var_name);
721   if ( ! var )
722     return ;
723
724   width = var_get_width (var);
725
726   *column = var_get_dict_index (var);
727   *row = -1;
728
729   if ( gtk_toggle_button_get_active
730        (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
731     flags |= STR_CMP_SUBSTR;
732
733   if ( gtk_toggle_button_get_active
734        (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
735     flags |= STR_CMP_REGEXP;
736
737   if ( gtk_toggle_button_get_active
738        (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
739     flags |= STR_CMP_LABELS;
740
741   {
742     union value *val = value_create (width);
743     casenumber i;
744     struct casenum_iterator *ip = get_iteration_params (fd);
745     struct comparator *cmptr =
746       comparator_factory (var, target_string, flags);
747
748     if ( ! cmptr)
749       goto finish;
750
751     for (i = ip->start (current_row, fd->data);
752          i != ip->end (current_row, fd->data);
753          ip->next (&i, fd->data))
754       {
755         datasheet_get_value (fd->data, i, var_get_case_index (var),
756                              val, width);
757
758         if ( comparator_compare (cmptr, val))
759           {
760             *row = i;
761             break;
762           }
763       }
764
765   finish:
766     comparator_destroy (cmptr);
767     free (val);
768   }
769 }