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