b232ef309226ca052846f449d92f9fcfebc9ff2d
[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, sizeof tf->cache_starts);
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 static void psppire_delimited_text_finalize        (GObject           *object);
173 static void psppire_delimited_text_dispose        (GObject           *object);
174
175 static GObjectClass *parent_class = NULL;
176
177 static gint
178 n_lines (PsppireDelimitedText *file)
179 {
180   PsppireTextFile *child = PSPPIRE_TEXT_FILE (file->child);
181
182   return child->maximum_lines;
183 }
184
185 static gboolean
186 __tree_get_iter (GtkTreeModel *tree_model,
187                  GtkTreeIter *iter,
188                  GtkTreePath *path)
189 {
190   PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
191   if (path == NULL)
192     return FALSE;
193
194
195   gint *indices = gtk_tree_path_get_indices (path);
196
197   if (!indices)
198     return FALSE;
199
200   gint n = *indices;
201
202   gint children = n_lines (file);
203
204   if (n >= children - file->first_line)
205     return FALSE;
206
207
208   iter->user_data = GINT_TO_POINTER (n);
209   iter->stamp = file->stamp;
210
211   return TRUE;
212 }
213
214
215 static gboolean
216 __tree_iter_next (GtkTreeModel *tree_model,
217                   GtkTreeIter *iter)
218 {
219   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
220   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
221
222   gint n = GPOINTER_TO_INT (iter->user_data);
223
224
225   gint children = n_lines (file);
226
227   if (n + 1 >= children - file->first_line)
228     return FALSE;
229
230   iter->user_data = GINT_TO_POINTER (n + 1);
231
232   return TRUE;
233 }
234
235
236 static GType
237 __tree_get_column_type (GtkTreeModel *tree_model,
238                         gint          index)
239 {
240   if (index == 0)
241     return G_TYPE_INT;
242
243   return G_TYPE_STRING;
244 }
245
246 static gboolean
247 __iter_has_child (GtkTreeModel *tree_model,
248                   GtkTreeIter  *iter)
249 {
250   return 0;
251 }
252
253
254 static gboolean
255 __iter_parent     (GtkTreeModel *tree_model,
256                    GtkTreeIter  *iter,
257                    GtkTreeIter  *child)
258 {
259   return 0;
260 }
261
262 static GtkTreePath *
263 __tree_get_path (GtkTreeModel *tree_model,
264                  GtkTreeIter  *iter)
265 {
266   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
267   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
268
269   gint n = GPOINTER_TO_INT (iter->user_data);
270
271   gint children = n_lines (file);
272
273   if (n >= children - file->first_line)
274     return NULL;
275
276   return gtk_tree_path_new_from_indices (n, -1);
277 }
278
279
280 static gboolean
281 __iter_children (GtkTreeModel *tree_model,
282                               GtkTreeIter *iter,
283                               GtkTreeIter *parent)
284 {
285   return 0;
286 }
287
288
289 static gint
290 __tree_model_iter_n_children (GtkTreeModel *tree_model,
291                               GtkTreeIter *iter)
292 {
293   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
294   g_assert (iter == NULL);
295
296   gint children = n_lines (file);
297
298   return children - file->first_line;
299 }
300
301 static GtkTreeModelFlags
302 __tree_model_get_flags (GtkTreeModel *model)
303 {
304   g_return_val_if_fail (PSPPIRE_IS_DELIMITED_TEXT (model), (GtkTreeModelFlags) 0);
305
306   return GTK_TREE_MODEL_LIST_ONLY;
307 }
308
309 static gint
310 __tree_model_get_n_columns (GtkTreeModel *tree_model)
311 {
312   PsppireDelimitedText *tf  = PSPPIRE_DELIMITED_TEXT (tree_model);
313
314   /* + 1 for the trailing field and +1 for the leading line number column */
315   return tf->max_delimiters + 1 + 1;
316 }
317
318
319 static gboolean
320 __iter_nth_child (GtkTreeModel *tree_model,
321                   GtkTreeIter *iter,
322                   GtkTreeIter *parent,
323                   gint n)
324 {
325   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
326
327   g_assert (parent == NULL);
328
329   g_return_val_if_fail (file, FALSE);
330
331   gint children = gtk_tree_model_iter_n_children (file->child, NULL);
332
333   if (n >= children - file->first_line)
334     {
335       iter->stamp = -1;
336       iter->user_data = NULL;
337       return FALSE;
338     }
339
340   iter->user_data = GINT_TO_POINTER (n);
341   iter->stamp = file->stamp;
342
343   return TRUE;
344 }
345
346
347 static void
348 nullify_char (struct substring cs)
349 {
350   int char_len = ss_first_mblen (cs);
351   while (char_len > 0)
352     {
353       cs.string[char_len - 1] = '\0';
354       char_len--;
355     }
356 }
357
358
359 /* Split row N into it's delimited fields (if it is not already cached)
360    and set this row as the current cache. */
361 static void
362 split_row_into_fields (PsppireDelimitedText *file, gint n)
363 {
364   if (n == file->cache_row)  /* Cache hit */
365     {
366       return;
367     }
368
369   memset (file->cache_starts, 0, sizeof file->cache_starts);
370   /* Cache miss */
371   if (file->const_cache.string)
372     {
373       ss_dealloc (&file->const_cache);
374     }
375   ss_alloc_substring_pool (&file->const_cache,
376                            PSPPIRE_TEXT_FILE (file->child)->lines[n], NULL);
377   struct substring cs = file->const_cache;
378   int field = 0;
379   file->cache_starts[0] = cs.string;
380   gint enc = -1;
381   for (;
382        UINT32_MAX != ss_first_mb (cs);
383        ss_get_mb (&cs))
384     {
385       ucs4_t character = ss_first_mb (cs);
386       gboolean char_is_quote = FALSE;
387       if (enc == -1)
388         {
389           gint i;
390           for (i = 0; i < 3; ++i)
391             {
392               if (character == enclosures[i].opening)
393                 {
394                   enc = i;
395                   char_is_quote = TRUE;
396                   file->cache_starts[field] += ss_first_mblen (cs);
397                   break;
398                 }
399             }
400         }
401       else if (character == enclosures[enc].closing)
402         {
403           char_is_quote = TRUE;
404           nullify_char (cs);
405           enc = -1;
406         }
407
408       if (enc == -1 && char_is_quote == FALSE)
409         {
410           GSList *del;
411           for (del = file->delimiters; del; del = g_slist_next (del))
412             {
413               if (character == GPOINTER_TO_INT (del->data))
414                 {
415                   field++;
416                   int char_len = ss_first_mblen (cs);
417                   file->cache_starts[field] = cs.string + char_len;
418                   nullify_char (cs);
419                   break;
420                 }
421             }
422         }
423     }
424
425   file->cache_row = n;
426 }
427
428 const gchar *
429 psppire_delimited_text_get_header_title (PsppireDelimitedText *file, gint column)
430 {
431   if (file->first_line <= 0)
432     return NULL;
433
434   split_row_into_fields (file, file->first_line - 1);
435
436   return file->cache_starts [column];
437 }
438
439 static void
440 __get_value (GtkTreeModel *tree_model,
441              GtkTreeIter *iter,
442              gint column,
443              GValue *value)
444 {
445   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
446
447   g_return_if_fail (iter->stamp == file->stamp);
448
449   gint n = GPOINTER_TO_INT (iter->user_data) + file->first_line;
450
451
452   if (column == 0)
453     {
454       g_value_init (value, G_TYPE_INT);
455       g_value_set_int (value, n + 1);
456       return;
457     }
458
459   g_value_init (value, G_TYPE_STRING);
460
461   split_row_into_fields (file, n);
462
463   g_value_set_string (value, file->cache_starts [column - 1]);
464 }
465
466
467 static void
468 __tree_model_init (GtkTreeModelIface *iface)
469 {
470   iface->get_flags       = __tree_model_get_flags;
471   iface->get_n_columns   = __tree_model_get_n_columns ;
472   iface->get_column_type = __tree_get_column_type;
473   iface->get_iter        = __tree_get_iter;
474   iface->iter_next       = __tree_iter_next;
475   iface->get_path        = __tree_get_path;
476   iface->get_value       = __get_value;
477
478   iface->iter_children   = __iter_children;
479   iface->iter_has_child  = __iter_has_child;
480   iface->iter_n_children = __tree_model_iter_n_children;
481   iface->iter_nth_child  = __iter_nth_child;
482   iface->iter_parent     = __iter_parent;
483 }
484
485 G_DEFINE_TYPE_WITH_CODE (PsppireDelimitedText, psppire_delimited_text, G_TYPE_OBJECT,
486                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
487                                                 __tree_model_init))
488
489 static void
490 psppire_delimited_text_class_init (PsppireDelimitedTextClass *class)
491 {
492   GObjectClass *object_class;
493
494   parent_class = g_type_class_peek_parent (class);
495   object_class = G_OBJECT_CLASS (class);
496
497   GParamSpec *first_line_spec =
498     g_param_spec_int ("first-line",
499                       "First Line",
500                       P_("The first line to be considered."),
501                       0, 1000, 0,
502                       G_PARAM_READWRITE);
503
504   GParamSpec *delimiters_spec =
505     g_param_spec_pointer ("delimiters",
506                           "Field Delimiters",
507                           P_("A GSList of gunichars which delimit the fields."),
508                           G_PARAM_READWRITE);
509
510   GParamSpec *child_spec =
511     g_param_spec_object ("child",
512                          "Child Model",
513                          P_("The GtkTextModel which this object wraps."),
514                          GTK_TYPE_TREE_MODEL,
515                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
516
517   object_class->set_property = psppire_delimited_text_set_property;
518   object_class->get_property = psppire_delimited_text_get_property;
519
520   g_object_class_install_property (object_class,
521                                    PROP_CHILD,
522                                    child_spec);
523
524   g_object_class_install_property (object_class,
525                                    PROP_DELIMITERS,
526                                    delimiters_spec);
527
528   g_object_class_install_property (object_class,
529                                    PROP_FIRST_LINE,
530                                    first_line_spec);
531
532   object_class->finalize = psppire_delimited_text_finalize;
533   object_class->dispose = psppire_delimited_text_dispose;
534 }
535
536
537 static void
538 psppire_delimited_text_init (PsppireDelimitedText *text_file)
539 {
540   text_file->child = NULL;
541   text_file->first_line = 0;
542   text_file->delimiters = g_slist_prepend (NULL, GINT_TO_POINTER (':'));
543
544   text_file->const_cache.string = NULL;
545   text_file->const_cache.length = 0;
546   text_file->cache_row = -1;
547   memset (text_file->cache_starts, 0, sizeof text_file->cache_starts);
548
549   text_file->max_delimiters = 0;
550
551   text_file->dispose_has_run = FALSE;
552   text_file->stamp = g_random_int ();
553 }
554
555
556 PsppireDelimitedText *
557 psppire_delimited_text_new (GtkTreeModel *child)
558 {
559   return
560     g_object_new (PSPPIRE_TYPE_DELIMITED_TEXT,
561                   "child", child,
562                   NULL);
563 }
564
565 static void
566 psppire_delimited_text_finalize (GObject *object)
567 {
568   PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
569
570   g_slist_free (tf->delimiters);
571
572   ss_dealloc (&tf->const_cache);
573
574   /* must chain up */
575   (* parent_class->finalize) (object);
576 }
577
578
579 static void
580 psppire_delimited_text_dispose (GObject *object)
581 {
582   PsppireDelimitedText *ds = PSPPIRE_DELIMITED_TEXT (object);
583
584   if (ds->dispose_has_run)
585     return;
586
587   /* must chain up */
588   (* parent_class->dispose) (object);
589
590   ds->dispose_has_run = TRUE;
591 }