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