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