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