b02a0ac9eabcf867d4575bdfed661b8d2f6faa7a
[pspp] / src / ui / gui / psppire-delimited-text.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2017 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 #include <config.h>
18 #include <gettext.h>
19 #define _(msgid) gettext (msgid)
20 #define P_(msgid) msgid
21
22 #include "psppire-delimited-text.h"
23 #include "psppire-text-file.h"
24 #include "libpspp/str.h"
25 #include "libpspp/i18n.h"
26
27 #include <gtk/gtk.h>
28
29 /* Properties */
30 enum
31   {
32     PROP_0,
33     PROP_CHILD,
34     PROP_DELIMITERS,
35     PROP_FIRST_LINE
36   };
37
38 struct enclosure
39 {
40   gunichar opening;
41   gunichar closing;
42 };
43
44 static const struct enclosure enclosures[3] =
45   {
46     {'(',   ')'},
47     {'"',   '"'},
48     {'\'',  '\''}
49   };
50
51 static void
52 count_delims (PsppireDelimitedText *tf)
53 {
54   if (tf->child == NULL)
55     return;
56
57   tf->max_delimiters = 0;
58   GtkTreeIter iter;
59   gboolean valid;
60   for (valid = gtk_tree_model_get_iter_first (tf->child, &iter);
61        valid;
62        valid = gtk_tree_model_iter_next (tf->child, &iter))
63     {
64       gint enc = -1;
65       // FIXME: Box these lines to avoid constant allocation/deallocation
66       gchar *foo = 0;
67       gtk_tree_model_get (tf->child, &iter, 1, &foo, -1);
68       {
69         char *line = foo;
70         gint count = 0;
71         while (*line)
72           {
73             const gunichar c = *line; //FIXME: Not multibyte safe!
74             if (enc == -1)
75               {
76                 gint i;
77                 for (i = 0; i < 3; ++i)
78                   {
79                     if (c == enclosures[i].opening)
80                       {
81                         enc = i;
82                         break;
83                       }
84                   }
85               }
86             else if (c == enclosures[enc].closing)
87               {
88                 enc = -1;
89               }
90             if (enc == -1)
91               {
92                 GSList *del;
93                 for (del = tf->delimiters; del; del = g_slist_next (del))
94                   {
95                     if (c == GPOINTER_TO_INT (del->data))
96                       count++;
97                   }
98               }
99             line++;
100           }
101         tf->max_delimiters = MAX (tf->max_delimiters, count);
102       }
103       g_free (foo);
104     }
105 }
106
107 static void
108 cache_invalidate (PsppireDelimitedText *tf)
109 {
110   memset (tf->cache_starts, 0, 512);
111   if (tf->const_cache.string)
112     {
113       ss_dealloc (&tf->const_cache);
114       tf->const_cache.string = NULL;
115       tf->cache_row = -1;
116     }
117 }
118
119 static void
120 psppire_delimited_text_set_property (GObject         *object,
121                                 guint            prop_id,
122                                 const GValue    *value,
123                                 GParamSpec      *pspec)
124 {
125   PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
126
127   switch (prop_id)
128     {
129     case PROP_FIRST_LINE:
130       tf->first_line = g_value_get_int (value);
131       break;
132     case PROP_CHILD:
133       tf->child = g_value_get_object (value);
134       g_return_if_fail (PSPPIRE_IS_TEXT_FILE (tf->child));
135       break;
136     case PROP_DELIMITERS:
137       g_slist_free (tf->delimiters);
138       tf->delimiters =  g_slist_copy (g_value_get_pointer (value));
139       break;
140     default:
141       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
142       break;
143     };
144
145   cache_invalidate (tf);
146   count_delims (tf);
147 }
148
149 static void
150 psppire_delimited_text_get_property (GObject         *object,
151                                 guint            prop_id,
152                                 GValue          *value,
153                                 GParamSpec      *pspec)
154 {
155   PsppireDelimitedText *text_file = PSPPIRE_DELIMITED_TEXT (object);
156
157   switch (prop_id)
158     {
159     case PROP_FIRST_LINE:
160       g_value_set_int (value, text_file->first_line);
161       break;
162     case PROP_DELIMITERS:
163       g_value_set_pointer (value, text_file->delimiters);
164       break;
165     default:
166       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
167       break;
168     };
169 }
170
171
172 static void psppire_delimited_text_init            (PsppireDelimitedText      *text_file);
173 static void psppire_delimited_text_class_init      (PsppireDelimitedTextClass *class);
174
175 static void psppire_delimited_text_finalize        (GObject           *object);
176 static void psppire_delimited_text_dispose        (GObject           *object);
177
178 static GObjectClass *parent_class = NULL;
179
180 static gint
181 n_lines (PsppireDelimitedText *file)
182 {
183   PsppireTextFile *child = PSPPIRE_TEXT_FILE (file->child);
184
185   return child->maximum_lines;
186 }
187
188 static gboolean
189 __tree_get_iter (GtkTreeModel *tree_model,
190                  GtkTreeIter *iter,
191                  GtkTreePath *path)
192 {
193   PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
194   if (path == NULL)
195     return FALSE;
196
197
198   gint *indices = gtk_tree_path_get_indices (path);
199
200   if (!indices)
201     return FALSE;
202
203   gint n = *indices;
204
205   gint children = n_lines (file);
206
207   if (n >= children - file->first_line)
208     return FALSE;
209
210
211   iter->user_data = GINT_TO_POINTER (n);
212   iter->stamp = file->stamp;
213
214   return TRUE;
215 }
216
217
218 static gboolean
219 __tree_iter_next (GtkTreeModel *tree_model,
220                   GtkTreeIter *iter)
221 {
222   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
223   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
224
225   gint n = GPOINTER_TO_INT (iter->user_data);
226
227
228   gint children = n_lines (file);
229
230   if (n + 1 >= children - file->first_line)
231     return FALSE;
232
233   iter->user_data = GINT_TO_POINTER (n + 1);
234
235   return TRUE;
236 }
237
238
239 static GType
240 __tree_get_column_type (GtkTreeModel *tree_model,
241                         gint          index)
242 {
243   if (index == 0)
244     return G_TYPE_INT;
245
246   return G_TYPE_STRING;
247 }
248
249 static gboolean
250 __iter_has_child (GtkTreeModel *tree_model,
251                   GtkTreeIter  *iter)
252 {
253   return 0;
254 }
255
256
257 static gboolean
258 __iter_parent     (GtkTreeModel *tree_model,
259                    GtkTreeIter  *iter,
260                    GtkTreeIter  *child)
261 {
262   return 0;
263 }
264
265 static GtkTreePath *
266 __tree_get_path (GtkTreeModel *tree_model,
267                  GtkTreeIter  *iter)
268 {
269   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
270   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
271
272   gint n = GPOINTER_TO_INT (iter->user_data);
273
274   gint children = n_lines (file);
275
276   if (n >= children - file->first_line)
277     return NULL;
278
279   return gtk_tree_path_new_from_indices (n, -1);
280 }
281
282
283 static gboolean
284 __iter_children (GtkTreeModel *tree_model,
285                               GtkTreeIter *iter,
286                               GtkTreeIter *parent)
287 {
288   return 0;
289 }
290
291
292 static gint
293 __tree_model_iter_n_children (GtkTreeModel *tree_model,
294                               GtkTreeIter *iter)
295 {
296   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
297   g_assert (iter == NULL);
298
299   gint children = n_lines (file);
300
301   return children - file->first_line;
302 }
303
304 static GtkTreeModelFlags
305 __tree_model_get_flags (GtkTreeModel *model)
306 {
307   g_return_val_if_fail (PSPPIRE_IS_DELIMITED_TEXT (model), (GtkTreeModelFlags) 0);
308
309   return GTK_TREE_MODEL_LIST_ONLY;
310 }
311
312 static gint
313 __tree_model_get_n_columns (GtkTreeModel *tree_model)
314 {
315   PsppireDelimitedText *tf  = PSPPIRE_DELIMITED_TEXT (tree_model);
316
317   /* + 1 for the trailing field and +1 for the leading line number column */
318   return tf->max_delimiters + 1 + 1;
319 }
320
321
322 static gboolean
323 __iter_nth_child (GtkTreeModel *tree_model,
324                   GtkTreeIter *iter,
325                   GtkTreeIter *parent,
326                   gint n)
327 {
328   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
329
330   g_assert (parent == NULL);
331
332   g_return_val_if_fail (file, FALSE);
333
334   gint children = gtk_tree_model_iter_n_children (file->child, NULL);
335
336   if (n >= children - file->first_line)
337     {
338       iter->stamp = -1;
339       iter->user_data = NULL;
340       return FALSE;
341     }
342
343   iter->user_data = GINT_TO_POINTER (n);
344   iter->stamp = file->stamp;
345
346   return TRUE;
347 }
348
349
350 static void
351 nullify_char (struct substring cs)
352 {
353   int char_len = ss_first_mblen (cs);
354   while (char_len > 0)
355     {
356       cs.string[char_len - 1] = '\0';
357       char_len--;
358     }
359 }
360
361
362 /* Split row N into it's delimited fields (if it is not already cached)
363    and set this row as the current cache. */
364 static void
365 split_row_into_fields (PsppireDelimitedText *file, gint n)
366 {
367   if (n == file->cache_row)  /* Cache hit */
368     {
369       return;
370     }
371
372   memset (file->cache_starts, 0, 512);
373   /* Cache miss */
374   if (file->const_cache.string)
375     {
376       ss_dealloc (&file->const_cache);
377     }
378   ss_alloc_substring_pool (&file->const_cache,
379                            PSPPIRE_TEXT_FILE (file->child)->lines[n], NULL);
380   struct substring cs = file->const_cache;
381   int field = 0;
382   file->cache_starts[0] = cs.string;
383   gint enc = -1;
384   for (;
385        UINT32_MAX != ss_first_mb (cs);
386        ss_get_mb (&cs))
387     {
388       ucs4_t character = ss_first_mb (cs);
389       gboolean char_is_quote = FALSE;
390       if (enc == -1)
391         {
392           gint i;
393           for (i = 0; i < 3; ++i)
394             {
395               if (character == enclosures[i].opening)
396                 {
397                   enc = i;
398                   char_is_quote = TRUE;
399                   file->cache_starts[field] += ss_first_mblen (cs);
400                   break;
401                 }
402             }
403         }
404       else if (character == enclosures[enc].closing)
405         {
406           char_is_quote = TRUE;
407           nullify_char (cs);
408           enc = -1;
409         }
410
411       if (enc == -1 && char_is_quote == FALSE)
412         {
413           GSList *del;
414           for (del = file->delimiters; del; del = g_slist_next (del))
415             {
416               if (character == GPOINTER_TO_INT (del->data))
417                 {
418                   field++;
419                   int char_len = ss_first_mblen (cs);
420                   file->cache_starts[field] = cs.string + char_len;
421                   nullify_char (cs);
422                   break;
423                 }
424             }
425         }
426     }
427
428   file->cache_row = n;
429 }
430
431 const gchar *
432 psppire_delimited_text_get_header_title (PsppireDelimitedText *file, gint column)
433 {
434   if (file->first_line <= 0)
435     return NULL;
436
437   split_row_into_fields (file, file->first_line - 1);
438
439   return file->cache_starts [column];
440 }
441
442 static void
443 __get_value (GtkTreeModel *tree_model,
444              GtkTreeIter *iter,
445              gint column,
446              GValue *value)
447 {
448   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
449
450   g_return_if_fail (iter->stamp == file->stamp);
451
452   gint n = GPOINTER_TO_INT (iter->user_data) + file->first_line;
453
454
455   if (column == 0)
456     {
457       g_value_init (value, G_TYPE_INT);
458       g_value_set_int (value, n + 1);
459       return;
460     }
461
462   g_value_init (value, G_TYPE_STRING);
463
464   split_row_into_fields (file, n);
465
466   g_value_set_string (value, file->cache_starts [column - 1]);
467 }
468
469
470 static void
471 __tree_model_init (GtkTreeModelIface *iface)
472 {
473   iface->get_flags       = __tree_model_get_flags;
474   iface->get_n_columns   = __tree_model_get_n_columns ;
475   iface->get_column_type = __tree_get_column_type;
476   iface->get_iter        = __tree_get_iter;
477   iface->iter_next       = __tree_iter_next;
478   iface->get_path        = __tree_get_path;
479   iface->get_value       = __get_value;
480
481   iface->iter_children   = __iter_children;
482   iface->iter_has_child  = __iter_has_child;
483   iface->iter_n_children = __tree_model_iter_n_children;
484   iface->iter_nth_child  = __iter_nth_child;
485   iface->iter_parent     = __iter_parent;
486 }
487
488
489 GType
490 psppire_delimited_text_get_type (void)
491 {
492   static GType text_file_type = 0;
493
494   if (!text_file_type)
495     {
496       static const GTypeInfo text_file_info =
497         {
498           sizeof (PsppireDelimitedTextClass),
499           NULL,         /* base_init */
500           NULL,         /* base_finalize */
501           (GClassInitFunc) psppire_delimited_text_class_init,
502           NULL,         /* class_finalize */
503           NULL,         /* class_data */
504           sizeof (PsppireDelimitedText),
505           0,
506           (GInstanceInitFunc) psppire_delimited_text_init,
507         };
508
509       static const GInterfaceInfo tree_model_info = {
510         (GInterfaceInitFunc) __tree_model_init,
511         NULL,
512         NULL
513       };
514
515       text_file_type = g_type_register_static (G_TYPE_OBJECT,
516                                                "PsppireDelimitedText",
517                                                &text_file_info, 0);
518
519       g_type_add_interface_static (text_file_type, GTK_TYPE_TREE_MODEL,
520                                    &tree_model_info);
521     }
522
523   return text_file_type;
524 }
525
526
527 static void
528 psppire_delimited_text_class_init (PsppireDelimitedTextClass *class)
529 {
530   GObjectClass *object_class;
531
532   parent_class = g_type_class_peek_parent (class);
533   object_class = (GObjectClass*) class;
534
535   GParamSpec *first_line_spec =
536     g_param_spec_int ("first-line",
537                       "First Line",
538                       P_("The first line to be considered."),
539                       0, 1000, 0,
540                       G_PARAM_READWRITE);
541
542   GParamSpec *delimiters_spec =
543     g_param_spec_pointer ("delimiters",
544                           "Field Delimiters",
545                           P_("A GSList of gunichars which delimit the fields."),
546                           G_PARAM_READWRITE);
547
548   GParamSpec *child_spec =
549     g_param_spec_object ("child",
550                          "Child Model",
551                          P_("The GtkTextModel which this object wraps."),
552                          GTK_TYPE_TREE_MODEL,
553                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
554
555   object_class->set_property = psppire_delimited_text_set_property;
556   object_class->get_property = psppire_delimited_text_get_property;
557
558   g_object_class_install_property (object_class,
559                                    PROP_CHILD,
560                                    child_spec);
561
562   g_object_class_install_property (object_class,
563                                    PROP_DELIMITERS,
564                                    delimiters_spec);
565
566   g_object_class_install_property (object_class,
567                                    PROP_FIRST_LINE,
568                                    first_line_spec);
569
570   object_class->finalize = psppire_delimited_text_finalize;
571   object_class->dispose = psppire_delimited_text_dispose;
572 }
573
574
575 static void
576 psppire_delimited_text_init (PsppireDelimitedText *text_file)
577 {
578   text_file->child = NULL;
579   text_file->first_line = 0;
580   text_file->delimiters = g_slist_prepend (NULL, GINT_TO_POINTER (':'));
581
582   text_file->const_cache.string = NULL;
583   text_file->const_cache.length = 0;
584   text_file->cache_row = -1;
585   memset (text_file->cache_starts, 0, 512);
586
587   text_file->max_delimiters = 0;
588
589   text_file->dispose_has_run = FALSE;
590   text_file->stamp = g_random_int ();
591 }
592
593
594 GtkTreeModel *
595 psppire_delimited_text_new (GtkTreeModel *child)
596 {
597   PsppireDelimitedText *retval =
598     g_object_new (PSPPIRE_TYPE_DELIMITED_TEXT,
599                   "child", child,
600                   NULL);
601
602   return GTK_TREE_MODEL (retval);
603 }
604
605 static void
606 psppire_delimited_text_finalize (GObject *object)
607 {
608   PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
609
610   g_slist_free (tf->delimiters);
611
612   ss_dealloc (&tf->const_cache);
613
614   /* must chain up */
615   (* parent_class->finalize) (object);
616 }
617
618
619 static void
620 psppire_delimited_text_dispose (GObject *object)
621 {
622   PsppireDelimitedText *ds = PSPPIRE_DELIMITED_TEXT (object);
623
624   if (ds->dispose_has_run)
625     return;
626
627   /* must chain up */
628   (* parent_class->dispose) (object);
629
630   ds->dispose_has_run = TRUE;
631 }