Fix crash in find dialog and make code less horrible.
[pspp-builds.git] / src / ui / gui / find-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009  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 "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"
34 #include <ctype.h>
35 #include <sys/types.h>
36 #include <regex.h>
37 #include <libpspp/message.h>
38
39 #include <gtk/gtk.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 "psppire-var-store.h"
51
52 struct find_dialog
53 {
54   GtkBuilder *xml;
55   PsppireDict *dict;
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;
63 };
64
65 static void
66 find_value (const struct find_dialog *fd, casenumber current_row,
67            casenumber *row, int *column);
68
69
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.
73 */
74 static void
75 refresh (GObject *obj, const struct find_dialog *fd)
76 {
77   gtk_toggle_button_set_active
78     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
79      FALSE);
80
81   gtk_toggle_button_set_active
82     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
83      FALSE);
84
85   gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
86   gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
87
88   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
89                                 FALSE);
90
91   gtk_toggle_button_set_active
92     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
93
94
95   gtk_toggle_button_set_active
96     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
97 }
98
99 /* Callback on the "Find" button */
100 static void
101 do_find (GObject *obj, const struct find_dialog *fd)
102 {
103   casenumber x = -1;
104   gint column = -1;
105   glong row;
106
107   g_object_get (fd->de->data_editor, "current-case", &row, NULL);
108
109   if ( row < 0 )
110     row = 0;
111
112   find_value (fd, row, &x, &column);
113
114
115   if ( x != -1)
116     {
117       gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
118                                      PSPPIRE_DATA_EDITOR_DATA_VIEW);
119
120       g_object_set (fd->de->data_editor,
121                     "current-case", x,
122                     "current-variable", column,
123                     NULL);
124     }
125
126 }
127
128 /* Callback on the selector.
129    It gets invoked whenever a variable is selected */
130 static void
131 on_select (GtkEntry *entry, gpointer data)
132 {
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 ;
137
138   g_return_if_fail (var);
139
140   gtk_widget_set_sensitive (fd->value_labels_checkbox,
141                             var_has_value_labels (var));
142
143   search_labels =
144     gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
145
146   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
147                             var_is_alpha (var) || search_labels);
148
149
150   gtk_widget_set_sensitive (fd->match_substring_checkbox,
151                             var_is_alpha (var) || search_labels);
152 }
153
154 /* Callback on the selector.
155    It gets invoked whenever a variable is unselected */
156 static void
157 on_deselect (GtkEntry *entry, gpointer data)
158 {
159   struct find_dialog *fd = data;
160
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);
164 }
165
166 static void
167 value_labels_toggled (GtkToggleButton *tb, gpointer data)
168 {
169   struct find_dialog *fd = data;
170
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);
173
174   gboolean active = gtk_toggle_button_get_active  (tb) ;
175
176   gtk_widget_set_sensitive (fd->match_substring_checkbox,
177                             active || (var && var_is_alpha (var)));
178
179   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
180                               active || (var && var_is_alpha (var)));
181 }
182
183 /* Pops up the Find dialog box
184  */
185 void
186 find_dialog (GObject *o, gpointer data)
187 {
188   PsppireDataWindow *de = PSPPIRE_DATA_WINDOW (data);
189
190   struct find_dialog fd;
191
192   GtkWidget *dialog ;
193   GtkWidget *source ;
194   GtkWidget *selector;
195   GtkWidget *find_button;
196
197   GtkWidget *buttonbox;
198
199   PsppireVarStore *vs ;
200   PsppireDataStore *ds ;
201
202   fd.xml = builder_new ("find.ui");
203   fd.de = de;
204
205   find_button = gtk_button_new_from_stock  (GTK_STOCK_FIND);
206   gtk_widget_show (find_button);
207
208   buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
209
210   gtk_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
211   gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
212
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");
216
217   g_object_get (de->data_editor,
218                 "var-store", &vs,
219                 "data-store", &ds,
220                 NULL);
221
222   fd.dict = vs->dict;
223   fd.data = ds->datasheet;
224
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");
230
231   fd.match_regexp_checkbox =
232     get_widget_assert (fd.xml,
233                        "find-match-regexp-checkbutton");
234
235   fd.match_substring_checkbox =
236     get_widget_assert (fd.xml,
237                        "find-match-substring-checkbutton");
238
239
240
241   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
242
243
244   g_object_set (source, "dictionary", fd.dict,
245         "selection-mode", GTK_SELECTION_SINGLE,
246         NULL);
247
248   psppire_selector_set_subjects (PSPPIRE_SELECTOR (selector),
249                                  source,
250                                  fd.variable_entry,
251                                  insert_source_row_into_entry,
252                                  is_currently_in_entry,
253                                  NULL
254                                  );
255
256   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &fd);
257
258   g_signal_connect (find_button, "clicked", G_CALLBACK (do_find),  &fd);
259
260   g_signal_connect (selector, "selected",
261                     G_CALLBACK (on_select),  &fd);
262
263   g_signal_connect (selector, "de-selected",
264                     G_CALLBACK (on_deselect),  &fd);
265
266   g_signal_connect (fd.value_labels_checkbox, "toggled",
267                     G_CALLBACK (value_labels_toggled),  &fd);
268
269
270   psppire_dialog_run (PSPPIRE_DIALOG (dialog));
271
272   g_object_unref (fd.xml);
273 }
274
275 \f
276 /* Iterators */
277
278 static void
279 forward (casenumber *i, struct datasheet *data UNUSED)
280 {
281   ++*i;
282 }
283
284
285 static void
286 forward_wrap (casenumber *i, struct datasheet *data)
287 {
288   if ( ++*i >=  datasheet_get_n_rows (data) ) *i = 0;
289 }
290
291 static void
292 backward (casenumber *i, struct datasheet *data UNUSED)
293 {
294   --*i;
295 }
296
297
298 static void
299 backward_wrap (casenumber *i, struct datasheet *data)
300 {
301   if ( --*i < 0 )
302     *i = datasheet_get_n_rows (data) - 1;
303 }
304
305
306 /* Current plus one */
307 static casenumber
308 cp1 (casenumber current, struct datasheet *data)
309 {
310   return current + 1;
311 }
312
313 /* Current plus one, circular */
314 static casenumber
315 cp1c (casenumber current, struct datasheet *data)
316 {
317   casenumber next = current;
318
319   forward_wrap (&next, data);
320
321   return next;
322 }
323
324
325 /* Current minus one */
326 static casenumber
327 cm1 (casenumber current, struct datasheet *data)
328 {
329   return current - 1;
330 }
331
332 /* Current minus one, circular */
333 static casenumber
334 cm1c (casenumber current, struct datasheet *data)
335 {
336   casenumber next = current;
337
338   backward_wrap (&next, data);
339
340   return next;
341 }
342
343
344 static casenumber
345 last (casenumber current, struct datasheet *data)
346 {
347   return datasheet_get_n_rows (data) ;
348 }
349
350 static casenumber
351 minus1 (casenumber current, struct datasheet *data)
352 {
353   return -1;
354 }
355
356 /* An type to facilitate iterating through casenumbers */
357 struct casenum_iterator
358 {
359   /* returns the first case to access */
360   casenumber (*start) (casenumber, struct datasheet *);
361
362   /* Returns one past the last case to access */
363   casenumber (*end) (casenumber, struct datasheet *);
364
365   /* Sets the first arg to the next case to access */
366   void (*next) (casenumber *, struct datasheet *);
367 };
368
369 enum iteration_type{
370   FORWARD = 0,
371   FORWARD_WRAP,
372   REVERSE,
373   REVERSE_WRAP,
374   n_iterators
375 };
376
377 static const struct casenum_iterator ip[n_iterators] =
378   {
379     {cp1, last, forward},
380     {cp1c, cm1, forward_wrap},
381     {cm1, minus1, backward},
382     {cm1c, cp1, backward_wrap}
383   };
384
385
386 \f
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)
390 {
391   gboolean wrap = gtk_toggle_button_get_active
392     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
393
394   gboolean reverse = gtk_toggle_button_get_active
395     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
396
397   if ( wrap )
398     {
399       if ( reverse )
400         return &ip[REVERSE_WRAP];
401       else
402         return &ip[FORWARD_WRAP];
403     }
404   else
405     {
406       if ( reverse )
407         return &ip[REVERSE];
408       else
409         return &ip[FORWARD];
410     }
411 }
412
413
414 enum string_cmp_flags
415   {
416     STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
417                               values */
418     STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
419
420     STR_CMP_LABELS = 0x04  /* Match against the values' labels instead
421                               of the data */
422   };
423
424
425 /* An abstract base type for comparing union values against a reference */
426 struct comparator
427 {
428   const struct variable *var;
429   enum string_cmp_flags flags;
430   const PsppireDict *dict;
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 == value_compare_3way (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   bool found;
497   char *text;
498   const struct string_comparator *ssc =
499     (const struct string_comparator *) cmptr;
500
501   int width = var_get_width (cmptr->var);
502   g_return_val_if_fail (width > 0, false);
503   assert ( ! (cmptr->flags & STR_CMP_LABELS));
504
505   text = value_to_text (*val, cmptr->dict, *var_get_write_format (cmptr->var));
506
507   if ( cmptr->flags & STR_CMP_SUBSTR)
508     found =  (NULL != g_strstr_len (text, width, ssc->pattern));
509   else
510     found = (0 == strncmp (text, ssc->pattern, width));
511
512   free (text);
513   return found;
514 }
515
516
517
518 /* Return true if VAL matched the regexp */
519 static bool
520 regexp_value_compare (const struct comparator *cmptr,
521                 const union value *val)
522 {
523   char *text;
524   bool retval;
525   const struct regexp_comparator *rec =
526     (const struct regexp_comparator *) cmptr;
527
528   int width = var_get_width (cmptr->var);
529
530   assert  ( ! (cmptr->flags & STR_CMP_LABELS) );
531
532   g_return_val_if_fail (width > 0, false);
533
534   text = value_to_text (*val, cmptr->dict, *var_get_write_format (cmptr->var));
535   /* We must remove trailing whitespace, otherwise $ will not match where
536      one would expect */
537   g_strchomp (text);
538
539   retval = (0 == regexec (&rec->re, text, 0, 0, 0));
540
541   g_free (text);
542
543   return retval;
544 }
545
546 /* Return true if the label of VAL matched the regexp */
547 static bool
548 regexp_label_compare (const struct comparator *cmptr,
549                       const union value *val)
550 {
551   const char *text;
552   const struct regexp_comparator *rec =
553     (const struct regexp_comparator *) cmptr;
554
555   int width ;
556
557   assert ( cmptr->flags & STR_CMP_LABELS);
558
559   text = var_lookup_value_label (cmptr->var, val);
560   width = strlen (text);
561
562   g_return_val_if_fail (width > 0, false);
563
564   return (0 == regexec (&rec->re, text, 0, 0, 0));
565 }
566
567
568
569 static void
570 regexp_destroy (struct comparator *cmptr)
571 {
572   struct regexp_comparator *rec = (struct regexp_comparator *) cmptr;
573
574   regfree (&rec->re);
575 }
576
577 static void
578 cmptr_value_destroy (struct comparator *cmptr)
579 {
580   struct value_comparator *vc = (struct value_comparator *) cmptr;
581   value_destroy (&vc->pattern, var_get_width (cmptr->var));
582 }
583
584
585 static struct comparator *
586 value_comparator_create (const struct variable *var, const PsppireDict *dict, const char *target)
587 {
588   const struct fmt_spec *fmt;
589   int width ;
590   struct value_comparator *vc = xzalloc (sizeof (*vc));
591   struct comparator *cmptr = (struct comparator *) vc;
592
593   cmptr->flags = 0;
594   cmptr->var = var;
595   cmptr->compare  = value_compare ;
596   cmptr->destroy = cmptr_value_destroy;
597   cmptr->dict = dict;
598
599   width = var_get_width (var);
600   fmt = var_get_write_format (var);
601
602   value_init (&vc->pattern, width);
603
604   text_to_value (target, &vc->pattern, dict, *var_get_write_format (var) );
605
606   return cmptr;
607 }
608
609 static struct comparator *
610 string_comparator_create (const struct variable *var, const PsppireDict *dict, 
611                           const char *target,
612                           enum string_cmp_flags flags)
613 {
614   struct string_comparator *ssc = xzalloc (sizeof (*ssc));
615   struct comparator *cmptr = (struct comparator *) ssc;
616
617   cmptr->flags = flags;
618   cmptr->var = var;
619   cmptr->dict = dict;
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 PsppireDict *dict, 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->dict = dict;
643   cmptr->compare  = (flags & STR_CMP_LABELS)
644     ? regexp_label_compare : regexp_value_compare ;
645
646   cmptr->destroy  = regexp_destroy;
647
648   code = regcomp (&rec->re, target, 0);
649   if ( code != 0 )
650     {
651       char *errbuf = NULL;
652       size_t errbuf_size = regerror (code, &rec->re, errbuf,  0);
653
654       errbuf = xmalloc (errbuf_size);
655
656       regerror (code, &rec->re, errbuf, errbuf_size);
657
658       msg (ME, _("Bad regular expression: %s"), errbuf);
659
660       free ( cmptr);
661       free (errbuf);
662       return NULL;
663     }
664
665   return cmptr;
666 }
667
668
669 /* Compare V against CMPTR's reference */
670 static bool
671 comparator_compare (const struct comparator *cmptr,
672                     const union value *v)
673 {
674   return cmptr->compare (cmptr, v);
675 }
676
677 /* Destroy CMPTR */
678 static void
679 comparator_destroy (struct comparator *cmptr)
680 {
681   if ( ! cmptr )
682     return ;
683
684   if ( cmptr->destroy )
685     cmptr->destroy (cmptr);
686
687   free (cmptr);
688 }
689
690
691 static struct comparator *
692 comparator_factory (const struct variable *var, const PsppireDict *dict, const char *str,
693                     enum string_cmp_flags flags)
694 {
695   if ( flags & STR_CMP_REGEXP )
696     return regexp_comparator_create (var, dict, str, flags);
697
698   if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
699     return string_comparator_create (var, dict, str, flags);
700
701   return value_comparator_create (var, dict, str);
702 }
703
704
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
708  */
709 static void
710 find_value (const struct find_dialog *fd, casenumber current_row,
711            casenumber *row, int *column)
712 {
713   int width;
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));
717
718   enum string_cmp_flags flags = 0;
719   g_assert (current_row >= 0);
720
721   var = dict_lookup_var (fd->dict->dict, var_name);
722   if ( ! var )
723     return ;
724
725   width = var_get_width (var);
726
727   *column = var_get_dict_index (var);
728   *row = -1;
729
730   if ( gtk_toggle_button_get_active
731        (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox)))
732     flags |= STR_CMP_SUBSTR;
733
734   if ( gtk_toggle_button_get_active
735        (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox)))
736     flags |= STR_CMP_REGEXP;
737
738   if ( gtk_toggle_button_get_active
739        (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox)))
740     flags |= STR_CMP_LABELS;
741
742   {
743     union value val;
744     casenumber i;
745     const struct casenum_iterator *ip = get_iteration_params (fd);
746     struct comparator *cmptr =
747       comparator_factory (var, fd->dict, target_string, flags);
748
749     value_init (&val, width);
750     if ( ! cmptr)
751       goto finish;
752
753     for (i = ip->start (current_row, fd->data);
754          i != ip->end (current_row, fd->data);
755          ip->next (&i, fd->data))
756       {
757         datasheet_get_value (fd->data, i, var_get_case_index (var), &val);
758
759         if ( comparator_compare (cmptr, &val))
760           {
761             *row = i;
762             break;
763           }
764       }
765
766   finish:
767     comparator_destroy (cmptr);
768     value_destroy (&val, width);
769   }
770 }