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