Adapt to new sheet
[pspp] / src / ui / gui / find-dialog.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2009, 2011, 2012, 2015  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 <ctype.h>
24 #include <gtk/gtk.h>
25 #include <regex.h>
26 #include <stdlib.h>
27 #include <sys/types.h>
28
29 #include "data/data-in.h"
30 #include "data/datasheet.h"
31 #include "data/format.h"
32 #include "data/value.h"
33 #include "libpspp/cast.h"
34 #include "libpspp/message.h"
35 #include "ui/gui/builder-wrapper.h"
36 #include "ui/gui/dict-display.h"
37 #include "ui/gui/find-dialog.h"
38 #include "ui/gui/helper.h"
39 #include "ui/gui/psppire-data-store.h"
40 #include "ui/gui/psppire-data-window.h"
41 #include "ui/gui/psppire-dialog.h"
42 #include "ui/gui/psppire-selector.h"
43
44 #include "gl/xalloc.h"
45
46 #include <gettext.h>
47 #define _(msgid) gettext (msgid)
48 #define N_(msgid) msgid
49
50
51 struct find_dialog
52 {
53   GtkBuilder *xml;
54   PsppireDict *dict;
55   struct datasheet *data;
56   PsppireDataWindow *de;
57   GtkWidget *variable_entry;
58   GtkWidget *value_entry;
59   GtkWidget *value_labels_checkbox;
60   GtkWidget *match_regexp_checkbox;
61   GtkWidget *match_substring_checkbox;
62 };
63
64 static void
65 find_value (const struct find_dialog *fd, casenumber current_row,
66            casenumber *row, int *column);
67
68
69 /* A callback which occurs whenever the "Refresh" button is clicked,
70    and when the dialog pops up.
71    It restores the dialog to its default state.
72 */
73 static void
74 refresh (GObject *obj, const struct find_dialog *fd)
75 {
76   gtk_toggle_button_set_active
77     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")),
78      FALSE);
79
80   gtk_toggle_button_set_active
81     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")),
82      FALSE);
83
84   gtk_entry_set_text (GTK_ENTRY (fd->variable_entry), "");
85   gtk_entry_set_text (GTK_ENTRY (fd->value_entry), "");
86
87   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (fd->match_regexp_checkbox),
88                                 FALSE);
89
90   gtk_toggle_button_set_active
91     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
92
93
94   gtk_toggle_button_set_active
95     (GTK_TOGGLE_BUTTON (fd->match_substring_checkbox), FALSE);
96 }
97
98 /* Callback on the "Find" button */
99 static void
100 do_find (GObject *obj, const struct find_dialog *fd)
101 {
102   casenumber x = -1;
103   gint column = -1;
104   glong row;
105
106   
107   row = 10;
108
109   find_value (fd, row, &x, &column);
110
111   if ( x != -1)
112     {
113       gtk_notebook_set_current_page (GTK_NOTEBOOK (fd->de->data_editor),
114                                      PSPPIRE_DATA_EDITOR_DATA_VIEW);
115
116     }
117 }
118
119 /* Callback on the selector.
120    It gets invoked whenever a variable is selected */
121 static void
122 on_select (GtkEntry *entry, gpointer data)
123 {
124   struct find_dialog *fd = data;
125   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
126   struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
127   gboolean search_labels ;
128
129   g_return_if_fail (var);
130
131   gtk_widget_set_sensitive (fd->value_labels_checkbox,
132                             var_has_value_labels (var));
133
134   search_labels =
135     gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (fd->value_labels_checkbox));
136
137   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
138                             var_is_alpha (var) || search_labels);
139
140
141   gtk_widget_set_sensitive (fd->match_substring_checkbox,
142                             var_is_alpha (var) || search_labels);
143 }
144
145 /* Callback on the selector.
146    It gets invoked whenever a variable is unselected */
147 static void
148 on_deselect (GtkEntry *entry, gpointer data)
149 {
150   struct find_dialog *fd = data;
151
152   gtk_widget_set_sensitive (fd->value_labels_checkbox, FALSE);
153   gtk_widget_set_sensitive (fd->match_substring_checkbox, FALSE);
154   gtk_widget_set_sensitive (fd->match_regexp_checkbox, FALSE);
155 }
156
157 static void
158 value_labels_toggled (GtkToggleButton *tb, gpointer data)
159 {
160   struct find_dialog *fd = data;
161
162   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
163   const struct variable *var = dict_lookup_var (fd->dict->dict, var_name);
164
165   gboolean active = gtk_toggle_button_get_active  (tb) ;
166
167   gtk_widget_set_sensitive (fd->match_substring_checkbox,
168                             active || (var && var_is_alpha (var)));
169
170   gtk_widget_set_sensitive (fd->match_regexp_checkbox,
171                               active || (var && var_is_alpha (var)));
172 }
173
174 /* Pops up the Find dialog box
175  */
176 void
177 find_dialog (PsppireDataWindow *de)
178 {
179   struct find_dialog fd;
180
181   GtkWidget *dialog ;
182   GtkWidget *source ;
183   GtkWidget *selector;
184   GtkWidget *find_button;
185
186   GtkWidget *buttonbox;
187
188   PsppireDataStore *ds ;
189
190   fd.xml = builder_new ("find.ui");
191   fd.de = de;
192
193   find_button = gtk_button_new_with_label (_("Find"));
194   gtk_widget_show (find_button);
195
196   buttonbox = get_widget_assert (fd.xml, "find-buttonbox");
197
198   psppire_box_pack_start_defaults (GTK_BOX (buttonbox), find_button);
199   gtk_box_reorder_child (GTK_BOX (buttonbox), find_button, 0);
200
201   dialog = get_widget_assert (fd.xml, "find-dialog");
202   source = get_widget_assert (fd.xml, "find-variable-treeview");
203   selector = get_widget_assert (fd.xml, "find-selector");
204
205   g_object_get (de->data_editor,
206                 "dictionary", &fd.dict,
207                 "data-store", &ds,
208                 NULL);
209
210   fd.data = ds->datasheet;
211
212   fd.variable_entry        = get_widget_assert (fd.xml, "find-variable-entry");
213   fd.value_entry           = get_widget_assert (fd.xml, "find-value-entry");
214   fd.value_labels_checkbox =
215     get_widget_assert (fd.xml,
216                        "find-value-labels-checkbutton");
217
218   fd.match_regexp_checkbox =
219     get_widget_assert (fd.xml,
220                        "find-match-regexp-checkbutton");
221
222   fd.match_substring_checkbox =
223     get_widget_assert (fd.xml,
224                        "find-match-substring-checkbutton");
225
226
227
228   gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (de));
229
230
231   g_object_set (source, "model", fd.dict,
232         "selection-mode", GTK_SELECTION_SINGLE,
233         NULL);
234
235
236   psppire_selector_set_filter_func (PSPPIRE_SELECTOR (selector),
237                                     is_currently_in_entry);
238
239   g_signal_connect (dialog, "refresh", G_CALLBACK (refresh),  &fd);
240
241   g_signal_connect (find_button, "clicked", G_CALLBACK (do_find),  &fd);
242
243   g_signal_connect (selector, "selected",
244                     G_CALLBACK (on_select),  &fd);
245
246   g_signal_connect (selector, "de-selected",
247                     G_CALLBACK (on_deselect),  &fd);
248
249   g_signal_connect (fd.value_labels_checkbox, "toggled",
250                     G_CALLBACK (value_labels_toggled),  &fd);
251
252
253   psppire_dialog_run (PSPPIRE_DIALOG (dialog));
254
255   g_object_unref (fd.xml);
256 }
257
258 \f
259 /* Iterators */
260
261 static void
262 forward (casenumber *i, struct datasheet *data UNUSED)
263 {
264   ++*i;
265 }
266
267
268 static void
269 forward_wrap (casenumber *i, struct datasheet *data)
270 {
271   if ( ++*i >=  datasheet_get_n_rows (data) ) *i = 0;
272 }
273
274 static void
275 backward (casenumber *i, struct datasheet *data UNUSED)
276 {
277   --*i;
278 }
279
280
281 static void
282 backward_wrap (casenumber *i, struct datasheet *data)
283 {
284   if ( --*i < 0 )
285     *i = datasheet_get_n_rows (data) - 1;
286 }
287
288
289 /* Current plus one */
290 static casenumber
291 cp1 (casenumber current, struct datasheet *data)
292 {
293   return current + 1;
294 }
295
296 /* Current plus one, circular */
297 static casenumber
298 cp1c (casenumber current, struct datasheet *data)
299 {
300   casenumber next = current;
301
302   forward_wrap (&next, data);
303
304   return next;
305 }
306
307
308 /* Current minus one */
309 static casenumber
310 cm1 (casenumber current, struct datasheet *data)
311 {
312   if (current == -1)
313     return datasheet_get_n_rows (data);
314
315   return current - 1;
316 }
317
318 /* Current minus one, circular */
319 static casenumber
320 cm1c (casenumber current, struct datasheet *data)
321 {
322   casenumber next = current;
323
324   if (current == -1)
325     return datasheet_get_n_rows (data);
326
327   backward_wrap (&next, data);
328
329   return next;
330 }
331
332
333 static casenumber
334 last (casenumber current, struct datasheet *data)
335 {
336   return datasheet_get_n_rows (data) ;
337 }
338
339 static casenumber
340 minus1 (casenumber current, struct datasheet *data)
341 {
342   if (current == -1)
343     return 0;
344
345   return -1;
346 }
347
348 /* An type to facilitate iterating through casenumbers */
349 struct casenum_iterator
350 {
351   /* returns the first case to access */
352   casenumber (*start) (casenumber, struct datasheet *);
353
354   /* Returns one past the last case to access */
355   casenumber (*end) (casenumber, struct datasheet *);
356
357   /* Sets the first arg to the next case to access */
358   void (*next) (casenumber *, struct datasheet *);
359 };
360
361 enum iteration_type{
362   FORWARD = 0,
363   FORWARD_WRAP,
364   REVERSE,
365   REVERSE_WRAP,
366   n_iterators
367 };
368
369 static const struct casenum_iterator ip[n_iterators] =
370   {
371     {cp1, last, forward},
372     {cp1c, cm1, forward_wrap},
373     {cm1, minus1, backward},
374     {cm1c, cp1, backward_wrap}
375   };
376
377
378 \f
379 /* A factory returning an iterator according to the dialog box's settings */
380 static const struct casenum_iterator *
381 get_iteration_params (const struct find_dialog *fd)
382 {
383   gboolean wrap = gtk_toggle_button_get_active
384     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-wrap")));
385
386   gboolean reverse = gtk_toggle_button_get_active
387     (GTK_TOGGLE_BUTTON (get_widget_assert (fd->xml, "find-backwards")));
388
389   if ( wrap )
390     {
391       if ( reverse )
392         return &ip[REVERSE_WRAP];
393       else
394         return &ip[FORWARD_WRAP];
395     }
396   else
397     {
398       if ( reverse )
399         return &ip[REVERSE];
400       else
401         return &ip[FORWARD];
402     }
403 }
404
405
406 enum string_cmp_flags
407   {
408     STR_CMP_SUBSTR = 0x01, /* Find strings which are substrings of the
409                               values */
410     STR_CMP_REGEXP = 0x02, /* Match against a regular expression */
411
412     STR_CMP_LABELS = 0x04  /* Match against the values' labels instead
413                               of the data */
414   };
415
416
417 /* An abstract base type for comparing union values against a reference */
418 struct comparator
419 {
420   const struct variable *var;
421   enum string_cmp_flags flags;
422
423   bool (*compare) (const struct comparator *,
424                    const union value *);
425
426   void (*destroy) (struct comparator *);
427 };
428
429
430 /* A comparator which operates on the unadulterated union values */
431 struct value_comparator
432 {
433   struct comparator parent;
434   union value pattern;
435 };
436
437 /* A comparator which matches string values or parts thereof */
438 struct string_comparator
439 {
440   struct comparator parent;
441   const char *pattern;
442 };
443
444 /* A comparator to match string values against a POSIX.2 regular expression */
445 struct regexp_comparator
446 {
447   struct comparator parent;
448   regex_t re;
449 };
450
451
452 static bool
453 value_compare (const struct comparator *cmptr,
454                const union value *v)
455 {
456   const struct value_comparator *vc = (const struct value_comparator *) cmptr;
457   return 0 == value_compare_3way (v, &vc->pattern, var_get_width (cmptr->var));
458 }
459
460
461 /* Return true if the label of VAL matches the reference string*/
462 static bool
463 string_label_compare (const struct comparator *cmptr,
464                 const union value *val)
465 {
466   const struct string_comparator *ssc =
467     (const struct string_comparator *) cmptr;
468
469   int width;
470
471   const char *text = var_lookup_value_label (cmptr->var, val);
472   if (text == NULL)
473     return false;
474
475   width = strlen (text);
476
477   assert ( cmptr->flags & STR_CMP_LABELS);
478
479   g_return_val_if_fail (width > 0, false);
480
481   if ( cmptr->flags & STR_CMP_SUBSTR)
482     return (NULL != g_strstr_len (text, width, ssc->pattern));
483   else
484     return (0 == strncmp (text, ssc->pattern, width));
485 }
486
487 /* Return true if VAL matches the reference string*/
488 static bool
489 string_value_compare (const struct comparator *cmptr,
490                       const union value *val)
491 {
492   bool found;
493   char *text;
494   const struct string_comparator *ssc =
495     (const struct string_comparator *) cmptr;
496
497   int width = var_get_width (cmptr->var);
498   g_return_val_if_fail (width > 0, false);
499   assert ( ! (cmptr->flags & STR_CMP_LABELS));
500
501   text = value_to_text (*val, cmptr->var);
502
503   if ( cmptr->flags & STR_CMP_SUBSTR)
504     found =  (NULL != g_strstr_len (text, width, ssc->pattern));
505   else
506     found = (0 == strncmp (text, ssc->pattern, width));
507
508   free (text);
509   return found;
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   text = value_to_text (*val, cmptr->var);
531   /* We must remove trailing whitespace, otherwise $ will not match where
532      one would expect */
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
569     = UP_CAST (cmptr, struct regexp_comparator, parent);
570
571   regfree (&rec->re);
572 }
573
574 static void
575 cmptr_value_destroy (struct comparator *cmptr)
576 {
577   struct value_comparator *vc
578     = UP_CAST (cmptr, struct value_comparator, parent);
579   value_destroy (&vc->pattern, var_get_width (cmptr->var));
580 }
581
582
583 static struct comparator *
584 value_comparator_create (const struct variable *var, const char *target)
585 {
586   struct value_comparator *vc = xzalloc (sizeof (*vc));
587   struct comparator *cmptr = &vc->parent;
588
589   cmptr->flags = 0;
590   cmptr->var = var;
591   cmptr->compare  = value_compare ;
592   cmptr->destroy = cmptr_value_destroy;
593
594   text_to_value (target, var, &vc->pattern);
595
596   return cmptr;
597 }
598
599 static struct comparator *
600 string_comparator_create (const struct variable *var, const char *target,
601                           enum string_cmp_flags flags)
602 {
603   struct string_comparator *ssc = xzalloc (sizeof (*ssc));
604   struct comparator *cmptr = &ssc->parent;
605
606   cmptr->flags = flags;
607   cmptr->var = var;
608
609   if ( flags & STR_CMP_LABELS)
610     cmptr->compare = string_label_compare;
611   else
612     cmptr->compare = string_value_compare;
613
614   ssc->pattern = target;
615
616   return cmptr;
617 }
618
619
620 static struct comparator *
621 regexp_comparator_create (const struct variable *var, const char *target,
622                           enum string_cmp_flags flags)
623 {
624   int code;
625   struct regexp_comparator *rec = xzalloc (sizeof (*rec));
626   struct comparator *cmptr = &rec->parent;
627
628   cmptr->flags = flags;
629   cmptr->var = var;
630   cmptr->compare  = (flags & STR_CMP_LABELS)
631     ? regexp_label_compare : regexp_value_compare ;
632
633   cmptr->destroy  = regexp_destroy;
634
635   code = regcomp (&rec->re, target, 0);
636   if ( code != 0 )
637     {
638       char *errbuf = NULL;
639       size_t errbuf_size = regerror (code, &rec->re, errbuf,  0);
640
641       errbuf = xmalloc (errbuf_size);
642
643       regerror (code, &rec->re, errbuf, errbuf_size);
644
645       msg (ME, _("Bad regular expression: %s"), errbuf);
646
647       free ( cmptr);
648       free (errbuf);
649       return NULL;
650     }
651
652   return cmptr;
653 }
654
655
656 /* Compare V against CMPTR's reference */
657 static bool
658 comparator_compare (const struct comparator *cmptr,
659                     const union value *v)
660 {
661   return cmptr->compare (cmptr, v);
662 }
663
664 /* Destroy CMPTR */
665 static void
666 comparator_destroy (struct comparator *cmptr)
667 {
668   if ( ! cmptr )
669     return ;
670
671   if ( cmptr->destroy )
672     cmptr->destroy (cmptr);
673
674   free (cmptr);
675 }
676
677
678 static struct comparator *
679 comparator_factory (const struct variable *var, const char *str,
680                     enum string_cmp_flags flags)
681 {
682   if ( flags & STR_CMP_REGEXP )
683     return regexp_comparator_create (var, str, flags);
684
685   if ( flags & (STR_CMP_SUBSTR | STR_CMP_LABELS) )
686     return string_comparator_create (var, str, flags);
687
688   return value_comparator_create (var, str);
689 }
690
691
692 /* Find the row and column specified by the dialog FD, starting at CURRENT_ROW.
693    After the function returns, *ROW contains the row and *COLUMN the column.
694    If no such case is found, then *ROW will be set to -1
695  */
696 static void
697 find_value (const struct find_dialog *fd, casenumber current_row,
698            casenumber *row, int *column)
699 {
700   int width;
701   const struct variable *var;
702   const char *var_name = gtk_entry_get_text (GTK_ENTRY (fd->variable_entry));
703   const char *target_string = gtk_entry_get_text (GTK_ENTRY (fd->value_entry));
704
705   enum string_cmp_flags flags = 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 }