refactor
[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 "language/data-io/data-parser.h"
25 #include "libpspp/str.h"
26 #include "libpspp/string-array.h"
27 #include "libpspp/i18n.h"
28
29 #include <gtk/gtk.h>
30
31 /* Properties */
32 enum
33   {
34     PROP_0,
35     PROP_CHILD,
36     PROP_DELIMITERS,
37     PROP_QUOTE,
38     PROP_FIRST_LINE
39   };
40
41 static struct data_parser *
42 make_data_parser (PsppireDelimitedText *tf)
43 {
44   struct data_parser *parser = data_parser_create ();
45   data_parser_set_type (parser, DP_DELIMITED);
46   data_parser_set_span (parser, false);
47   data_parser_set_quotes (parser, ss_empty ());
48   data_parser_set_quote_escape (parser, true);
49   data_parser_set_empty_line_has_field (parser, true);
50
51   bool space = false;
52   struct string hard_delimiters = DS_EMPTY_INITIALIZER;
53   GSList *del;
54   for (del = tf->delimiters; del; del = g_slist_next (del))
55     {
56       gunichar c = GPOINTER_TO_INT (del->data);
57       if (c == ' ')
58         space = true;
59       else
60         ds_put_unichar (&hard_delimiters, c);
61     }
62   data_parser_set_soft_delimiters (parser, ss_cstr (space ? " " : ""));
63   data_parser_set_hard_delimiters (parser, ds_ss (&hard_delimiters));
64   ds_destroy (&hard_delimiters);
65
66   if (tf->quote)
67     {
68       struct string quote = DS_EMPTY_INITIALIZER;
69       ds_put_unichar (&quote, tf->quote);
70       data_parser_set_quotes (parser, ds_ss (&quote));
71       ds_destroy (&quote);
72     }
73   return parser;
74 }
75
76 static void
77 count_delims (PsppireDelimitedText *tf)
78 {
79   if (tf->child == NULL)
80     return;
81
82   struct data_parser *parser = make_data_parser (tf);
83
84   tf->max_fields = 0;
85   GtkTreeIter iter;
86   gboolean valid;
87   for (valid = gtk_tree_model_get_iter_first (tf->child, &iter);
88        valid;
89        valid = gtk_tree_model_iter_next (tf->child, &iter))
90     {
91       gchar *line = NULL;
92       gtk_tree_model_get (tf->child, &iter, 1, &line, -1);
93       size_t n_fields = data_parser_split (parser, ss_cstr (line), NULL);
94       if (n_fields > tf->max_fields)
95         tf->max_fields = n_fields;
96       g_free (line);
97     }
98
99   data_parser_destroy (parser);
100 }
101
102 static void
103 cache_invalidate (PsppireDelimitedText *tf)
104 {
105   tf->cache_row = -1;
106   data_parser_destroy (tf->parser);
107   tf->parser = make_data_parser (tf);
108 }
109
110 static void
111 psppire_delimited_text_set_property (GObject         *object,
112                                 guint            prop_id,
113                                 const GValue    *value,
114                                 GParamSpec      *pspec)
115 {
116   PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
117
118   switch (prop_id)
119     {
120     case PROP_FIRST_LINE:
121       tf->first_line = g_value_get_int (value);
122       break;
123     case PROP_CHILD:
124       tf->child = g_value_get_object (value);
125       g_return_if_fail (PSPPIRE_IS_TEXT_FILE (tf->child));
126       break;
127     case PROP_DELIMITERS:
128       g_slist_free (tf->delimiters);
129       tf->delimiters =  g_slist_copy (g_value_get_pointer (value));
130       break;
131     case PROP_QUOTE:
132       tf->quote = g_value_get_uint (value);
133       break;
134     default:
135       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
136       break;
137     };
138
139   cache_invalidate (tf);
140   count_delims (tf);
141 }
142
143 static void
144 psppire_delimited_text_get_property (GObject         *object,
145                                 guint            prop_id,
146                                 GValue          *value,
147                                 GParamSpec      *pspec)
148 {
149   PsppireDelimitedText *text_file = PSPPIRE_DELIMITED_TEXT (object);
150
151   switch (prop_id)
152     {
153     case PROP_FIRST_LINE:
154       g_value_set_int (value, text_file->first_line);
155       break;
156     case PROP_DELIMITERS:
157       g_value_set_pointer (value, text_file->delimiters);
158       break;
159     case PROP_QUOTE:
160       g_value_set_uint (value, text_file->quote);
161       break;
162     default:
163       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
164       break;
165     };
166 }
167
168 static void psppire_delimited_text_finalize        (GObject           *object);
169 static void psppire_delimited_text_dispose        (GObject           *object);
170
171 static GObjectClass *parent_class = NULL;
172
173 static gint
174 n_lines (PsppireDelimitedText *file)
175 {
176   PsppireTextFile *child = PSPPIRE_TEXT_FILE (file->child);
177
178   return child->maximum_lines;
179 }
180
181 static gboolean
182 __tree_get_iter (GtkTreeModel *tree_model,
183                  GtkTreeIter *iter,
184                  GtkTreePath *path)
185 {
186   PsppireDelimitedText *file = PSPPIRE_DELIMITED_TEXT (tree_model);
187   if (path == NULL)
188     return FALSE;
189
190
191   gint *indices = gtk_tree_path_get_indices (path);
192
193   if (!indices)
194     return FALSE;
195
196   gint n = *indices;
197
198   gint children = n_lines (file);
199
200   if (n >= children - file->first_line)
201     return FALSE;
202
203
204   iter->user_data = GINT_TO_POINTER (n);
205   iter->stamp = file->stamp;
206
207   return TRUE;
208 }
209
210
211 static gboolean
212 __tree_iter_next (GtkTreeModel *tree_model,
213                   GtkTreeIter *iter)
214 {
215   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
216   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
217
218   gint n = GPOINTER_TO_INT (iter->user_data);
219
220
221   gint children = n_lines (file);
222
223   if (n + 1 >= children - file->first_line)
224     return FALSE;
225
226   iter->user_data = GINT_TO_POINTER (n + 1);
227
228   return TRUE;
229 }
230
231
232 static GType
233 __tree_get_column_type (GtkTreeModel *tree_model,
234                         gint          index)
235 {
236   if (index == 0)
237     return G_TYPE_INT;
238
239   return G_TYPE_STRING;
240 }
241
242 static gboolean
243 __iter_has_child (GtkTreeModel *tree_model,
244                   GtkTreeIter  *iter)
245 {
246   return 0;
247 }
248
249
250 static gboolean
251 __iter_parent     (GtkTreeModel *tree_model,
252                    GtkTreeIter  *iter,
253                    GtkTreeIter  *child)
254 {
255   return 0;
256 }
257
258 static GtkTreePath *
259 __tree_get_path (GtkTreeModel *tree_model,
260                  GtkTreeIter  *iter)
261 {
262   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
263   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
264
265   gint n = GPOINTER_TO_INT (iter->user_data);
266
267   gint children = n_lines (file);
268
269   if (n >= children - file->first_line)
270     return NULL;
271
272   return gtk_tree_path_new_from_indices (n, -1);
273 }
274
275
276 static gboolean
277 __iter_children (GtkTreeModel *tree_model,
278                               GtkTreeIter *iter,
279                               GtkTreeIter *parent)
280 {
281   return 0;
282 }
283
284
285 static gint
286 __tree_model_iter_n_children (GtkTreeModel *tree_model,
287                               GtkTreeIter *iter)
288 {
289   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
290   g_assert (iter == NULL);
291
292   gint children = n_lines (file);
293
294   return children - file->first_line;
295 }
296
297 static GtkTreeModelFlags
298 __tree_model_get_flags (GtkTreeModel *model)
299 {
300   g_return_val_if_fail (PSPPIRE_IS_DELIMITED_TEXT (model), (GtkTreeModelFlags) 0);
301
302   return GTK_TREE_MODEL_LIST_ONLY;
303 }
304
305 static gint
306 __tree_model_get_n_columns (GtkTreeModel *tree_model)
307 {
308   PsppireDelimitedText *tf  = PSPPIRE_DELIMITED_TEXT (tree_model);
309
310   /* +1 for the leading line number column */
311   return tf->max_fields + 1;
312 }
313
314
315 static gboolean
316 __iter_nth_child (GtkTreeModel *tree_model,
317                   GtkTreeIter *iter,
318                   GtkTreeIter *parent,
319                   gint n)
320 {
321   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
322
323   g_assert (parent == NULL);
324
325   g_return_val_if_fail (file, FALSE);
326
327   gint children = gtk_tree_model_iter_n_children (file->child, NULL);
328
329   if (n >= children - file->first_line)
330     {
331       iter->stamp = -1;
332       iter->user_data = NULL;
333       return FALSE;
334     }
335
336   iter->user_data = GINT_TO_POINTER (n);
337   iter->stamp = file->stamp;
338
339   return TRUE;
340 }
341
342 /* Split row N into it's delimited fields (if it is not already cached)
343    and set this row as the current cache. */
344 static void
345 split_row_into_fields (PsppireDelimitedText *file, gint n)
346 {
347   if (n == file->cache_row)  /* Cache hit */
348     return;
349   if (!file->parser)
350     file->parser = make_data_parser (file);
351
352   string_array_clear (&file->cache);
353   data_parser_split (file->parser, PSPPIRE_TEXT_FILE (file->child)->lines[n],
354                      &file->cache);
355   file->cache_row = n;
356 }
357
358 const gchar *
359 psppire_delimited_text_get_header_title (PsppireDelimitedText *file, gint column)
360 {
361   if (file->first_line <= 0)
362     return NULL;
363
364   split_row_into_fields (file, file->first_line - 1);
365
366   return column < file->cache.n ? file->cache.strings[column] : "";
367 }
368
369 static void
370 __get_value (GtkTreeModel *tree_model,
371              GtkTreeIter *iter,
372              gint column,
373              GValue *value)
374 {
375   PsppireDelimitedText *file  = PSPPIRE_DELIMITED_TEXT (tree_model);
376
377   g_return_if_fail (iter->stamp == file->stamp);
378
379   gint n = GPOINTER_TO_INT (iter->user_data) + file->first_line;
380
381
382   if (column == 0)
383     {
384       g_value_init (value, G_TYPE_INT);
385       g_value_set_int (value, n + 1);
386       return;
387     }
388
389   g_value_init (value, G_TYPE_STRING);
390
391   split_row_into_fields (file, n);
392
393   size_t idx = column - 1;
394   const char *s = idx < file->cache.n ? file->cache.strings[idx] : "";
395   g_value_set_string (value, s);
396 }
397
398
399 static void
400 __tree_model_init (GtkTreeModelIface *iface)
401 {
402   iface->get_flags       = __tree_model_get_flags;
403   iface->get_n_columns   = __tree_model_get_n_columns ;
404   iface->get_column_type = __tree_get_column_type;
405   iface->get_iter        = __tree_get_iter;
406   iface->iter_next       = __tree_iter_next;
407   iface->get_path        = __tree_get_path;
408   iface->get_value       = __get_value;
409
410   iface->iter_children   = __iter_children;
411   iface->iter_has_child  = __iter_has_child;
412   iface->iter_n_children = __tree_model_iter_n_children;
413   iface->iter_nth_child  = __iter_nth_child;
414   iface->iter_parent     = __iter_parent;
415 }
416
417 G_DEFINE_TYPE_WITH_CODE (PsppireDelimitedText, psppire_delimited_text, G_TYPE_OBJECT,
418                          G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL,
419                                                 __tree_model_init))
420
421 static void
422 psppire_delimited_text_class_init (PsppireDelimitedTextClass *class)
423 {
424   GObjectClass *object_class;
425
426   parent_class = g_type_class_peek_parent (class);
427   object_class = G_OBJECT_CLASS (class);
428
429   GParamSpec *first_line_spec =
430     g_param_spec_int ("first-line",
431                       "First Line",
432                       P_("The first line to be considered."),
433                       0, 1000, 0,
434                       G_PARAM_READWRITE);
435
436   GParamSpec *delimiters_spec =
437     g_param_spec_pointer ("delimiters",
438                           "Field Delimiters",
439                           P_("A GSList of gunichars which delimit the fields."),
440                           G_PARAM_READWRITE);
441
442   GParamSpec *quote_spec =
443     g_param_spec_unichar ("quote",
444                          "Quote Character",
445                          P_("A character that quotes the field, or 0 to disable quoting."),
446                          0,
447                          G_PARAM_READWRITE);
448
449   GParamSpec *child_spec =
450     g_param_spec_object ("child",
451                          "Child Model",
452                          P_("The GtkTextModel which this object wraps."),
453                          GTK_TYPE_TREE_MODEL,
454                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
455
456   object_class->set_property = psppire_delimited_text_set_property;
457   object_class->get_property = psppire_delimited_text_get_property;
458
459   g_object_class_install_property (object_class,
460                                    PROP_CHILD,
461                                    child_spec);
462
463   g_object_class_install_property (object_class,
464                                    PROP_DELIMITERS,
465                                    delimiters_spec);
466
467   g_object_class_install_property (object_class,
468                                    PROP_QUOTE,
469                                    quote_spec);
470
471   g_object_class_install_property (object_class,
472                                    PROP_FIRST_LINE,
473                                    first_line_spec);
474
475   object_class->finalize = psppire_delimited_text_finalize;
476   object_class->dispose = psppire_delimited_text_dispose;
477 }
478
479
480 static void
481 psppire_delimited_text_init (PsppireDelimitedText *text_file)
482 {
483   text_file->child = NULL;
484   text_file->first_line = 0;
485   text_file->delimiters = g_slist_prepend (NULL, GINT_TO_POINTER (':'));
486
487   text_file->cache_row = -1;
488   string_array_init (&text_file->cache);
489   text_file->parser = NULL;
490
491   text_file->max_fields = 0;
492
493   text_file->quote = 0;
494
495   text_file->dispose_has_run = FALSE;
496   text_file->stamp = g_random_int ();
497 }
498
499
500 PsppireDelimitedText *
501 psppire_delimited_text_new (GtkTreeModel *child)
502 {
503   return
504     g_object_new (PSPPIRE_TYPE_DELIMITED_TEXT,
505                   "child", child,
506                   NULL);
507 }
508
509 static void
510 psppire_delimited_text_finalize (GObject *object)
511 {
512   PsppireDelimitedText *tf = PSPPIRE_DELIMITED_TEXT (object);
513
514   g_slist_free (tf->delimiters);
515   string_array_destroy (&tf->cache);
516   data_parser_destroy (tf->parser);
517
518   /* must chain up */
519   (* parent_class->finalize) (object);
520 }
521
522
523 static void
524 psppire_delimited_text_dispose (GObject *object)
525 {
526   PsppireDelimitedText *ds = PSPPIRE_DELIMITED_TEXT (object);
527
528   if (ds->dispose_has_run)
529     return;
530
531   /* must chain up */
532   (* parent_class->dispose) (object);
533
534   ds->dispose_has_run = TRUE;
535 }