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