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