Merge remote-tracking branch 'origin/master' into sheet
[pspp] / src / ui / gui / psppire-text-file.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-text-file.h"
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26 #include <stdlib.h>
27 #include "libpspp/line-reader.h"
28 #include "libpspp/message.h"
29 #include "libpspp/str.h"
30 #include "libpspp/i18n.h"
31
32 #include <gtk/gtk.h>
33
34 /* Properties */
35 enum
36   {
37     PROP_0,
38     PROP_FILE_NAME,
39     PROP_ENCODING
40   };
41
42 enum {MAX_LINE_LEN = 16384};  /* Max length of an acceptable line. */
43
44
45 static void
46 read_lines (PsppireTextFile *tf)
47 {
48   if (tf->file_name && 0 != g_strcmp0 ("unset", tf->encoding))
49     {
50       struct line_reader *reader = line_reader_for_file (tf->encoding, tf->file_name, O_RDONLY);
51
52       if (reader == NULL)
53         {
54           msg_error (errno, _("Could not open `%s'"),  tf->file_name);
55           return;
56         }
57
58       struct string input;
59       ds_init_empty (&input);
60       for (tf->line_cnt = 0; tf->line_cnt < MAX_PREVIEW_LINES; tf->line_cnt++)
61         {
62           ds_clear (&input);
63           if (!line_reader_read (reader, &input, MAX_LINE_LEN + 1)
64               || ds_length (&input) > MAX_LINE_LEN)
65             {
66               int i;
67               if (line_reader_eof (reader))
68                 break;
69               else if (line_reader_error (reader))
70                 msg (ME, _("Error reading `%s': %s"),
71                      tf->file_name, strerror (line_reader_error (reader)));
72               else
73                 msg (ME, _("Failed to read `%s', because it contains a line "
74                            "over %d bytes long and therefore appears not to be "
75                            "a text file."),
76                      tf->file_name, MAX_LINE_LEN);
77               line_reader_close (reader);
78               for (i = 0; i < tf->line_cnt; i++)
79                 g_free (&tf->lines[i]);
80               tf->line_cnt = 0;
81               ds_destroy (&input);
82               return;
83             }
84
85           tf->lines[tf->line_cnt]
86             = recode_substring_pool ("UTF-8",
87                                      line_reader_get_encoding (reader),
88                                      input.ss, NULL);
89         }
90       ds_destroy (&input);
91
92       if (tf->line_cnt == 0)
93         {
94           int i;
95           msg (ME, _("`%s' is empty."), tf->file_name);
96           line_reader_close (reader);
97           for (i = 0; i < tf->line_cnt; i++)
98             g_free (&tf->lines[i]);
99           tf->line_cnt = 0;
100           goto done;
101         }
102
103       if (tf->line_cnt < MAX_PREVIEW_LINES)
104         {
105           tf->total_lines = tf->line_cnt;
106           tf->total_is_exact = true;
107         }
108       else
109         {
110           /* Estimate the number of lines in the file. */
111           struct stat s;
112           off_t position = line_reader_tell (reader);
113           if (fstat (line_reader_fileno (reader), &s) == 0 && position > 0)
114             {
115               tf->total_lines = (double) tf->line_cnt / position * s.st_size;
116               tf->total_is_exact = false;
117             }
118           else
119             {
120               tf->total_lines = 0;
121               tf->total_is_exact = true;
122             }
123         }
124     done:
125       line_reader_close (reader);
126     }
127
128 }
129
130 static void
131 psppire_text_file_set_property (GObject         *object,
132                                 guint            prop_id,
133                                 const GValue    *value,
134                                 GParamSpec      *pspec)
135 {
136   PsppireTextFile *tf = PSPPIRE_TEXT_FILE (object);
137
138   switch (prop_id)
139     {
140     case PROP_FILE_NAME:
141       tf->file_name = g_value_dup_string (value);
142       read_lines (tf);
143       break;
144     case PROP_ENCODING:
145       g_free (tf->encoding);
146       tf->encoding = g_value_dup_string (value);
147       read_lines (tf);
148       break;
149     default:
150       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
151       break;
152     };
153
154 }
155
156 static void
157 psppire_text_file_get_property (GObject         *object,
158                                 guint            prop_id,
159                                 GValue          *value,
160                                 GParamSpec      *pspec)
161 {
162   PsppireTextFile *text_file = PSPPIRE_TEXT_FILE (object);
163
164   switch (prop_id)
165     {
166     case PROP_FILE_NAME:
167       g_value_set_string (value, text_file->file_name);
168       break;
169     case PROP_ENCODING:
170       g_value_set_string (value, text_file->encoding);
171       break;
172     default:
173       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
174       break;
175     };
176 }
177
178
179 static void psppire_text_file_init            (PsppireTextFile      *text_file);
180 static void psppire_text_file_class_init      (PsppireTextFileClass *class);
181
182 static void psppire_text_file_finalize        (GObject           *object);
183 static void psppire_text_file_dispose        (GObject           *object);
184
185 static GObjectClass *parent_class = NULL;
186
187
188 static gboolean
189 __tree_get_iter (GtkTreeModel *tree_model,
190                  GtkTreeIter *iter,
191                  GtkTreePath *path)
192 {
193   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
194
195   if (path == NULL)
196     return FALSE;
197
198   gint *indices = gtk_tree_path_get_indices (path);
199
200   gint n = *indices;
201
202   if (n >= file->line_cnt)
203     return FALSE;
204
205   iter->user_data = GINT_TO_POINTER (n);
206   iter->stamp = file->stamp;
207
208   return TRUE;
209 }
210
211
212 static gboolean
213 __tree_iter_next (GtkTreeModel *tree_model,
214                   GtkTreeIter *iter)
215 {
216   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
217   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
218
219   gint n = GPOINTER_TO_INT (iter->user_data) + 1;
220
221   if (n >= file->line_cnt)
222     return FALSE;
223
224   iter->user_data = GINT_TO_POINTER (n);
225
226   return TRUE;
227 }
228
229
230 static GType
231 __tree_get_column_type (GtkTreeModel *tree_model,
232                         gint          index)
233 {
234   if (index == 0)
235     return G_TYPE_INT;
236
237   return G_TYPE_STRING;
238 }
239
240 static gboolean
241 __iter_has_child (GtkTreeModel *tree_model,
242                   GtkTreeIter  *iter)
243 {
244   return 0;
245 }
246
247
248 static gboolean
249 __iter_parent     (GtkTreeModel *tree_model,
250                    GtkTreeIter  *iter,
251                    GtkTreeIter  *child)
252 {
253   return 0;
254 }
255
256 static GtkTreePath *
257 __tree_get_path (GtkTreeModel *tree_model,
258                  GtkTreeIter  *iter)
259 {
260   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
261   g_return_val_if_fail (file->stamp == iter->stamp, FALSE);
262
263   gint n = GPOINTER_TO_INT (iter->user_data);
264
265   return gtk_tree_path_new_from_indices (n, -1);
266 }
267
268
269 static gboolean
270 __iter_children (GtkTreeModel *tree_model,
271                               GtkTreeIter *iter,
272                               GtkTreeIter *parent)
273 {
274   return 0;
275 }
276
277
278 static gint
279 __tree_model_iter_n_children (GtkTreeModel *tree_model,
280                               GtkTreeIter *iter)
281 {
282   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
283   g_assert (iter == NULL);
284   return file->line_cnt;
285 }
286
287 static GtkTreeModelFlags
288 __tree_model_get_flags (GtkTreeModel *model)
289 {
290   g_return_val_if_fail (PSPPIRE_IS_TEXT_FILE (model), (GtkTreeModelFlags) 0);
291
292   return GTK_TREE_MODEL_LIST_ONLY;
293 }
294
295 static gint
296 __tree_model_get_n_columns (GtkTreeModel *tree_model)
297 {
298   PsppireTextFile *tf  = PSPPIRE_TEXT_FILE (tree_model);
299   return 2;
300 }
301
302
303 static gboolean
304 __iter_nth_child (GtkTreeModel *tree_model,
305                   GtkTreeIter *iter,
306                   GtkTreeIter *parent,
307                   gint n)
308 {
309   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
310
311   g_assert (parent == NULL);
312
313   g_return_val_if_fail (file, FALSE);
314
315   if (n >= file->line_cnt)
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 static void
330 __get_value (GtkTreeModel *tree_model,
331              GtkTreeIter *iter,
332              gint column,
333              GValue *value)
334 {
335   PsppireTextFile *file  = PSPPIRE_TEXT_FILE (tree_model);
336
337   g_return_if_fail (iter->stamp == file->stamp);
338
339   gint n = GPOINTER_TO_INT (iter->user_data);
340
341   g_return_if_fail (n < file->line_cnt);
342
343   if (column == 0)
344     {
345       g_value_init (value, G_TYPE_INT);
346       g_value_set_int (value, n + 1);
347       return;
348     }
349
350   g_value_init (value, G_TYPE_STRING);
351
352   if (column == 1)
353     {
354       char *s = ss_xstrdup (file->lines[n]);
355       g_value_set_string (value, s);
356       free (s);
357       return;
358     }
359
360   g_assert_not_reached ();
361 }
362
363
364 static void
365 __tree_model_init (GtkTreeModelIface *iface)
366 {
367   iface->get_flags       = __tree_model_get_flags;
368   iface->get_n_columns   = __tree_model_get_n_columns ;
369   iface->get_column_type = __tree_get_column_type;
370   iface->get_iter        = __tree_get_iter;
371   iface->iter_next       = __tree_iter_next;
372   iface->get_path        = __tree_get_path;
373   iface->get_value       = __get_value;
374
375   iface->iter_children   = __iter_children;
376   iface->iter_has_child  = __iter_has_child;
377   iface->iter_n_children = __tree_model_iter_n_children;
378   iface->iter_nth_child  = __iter_nth_child;
379   iface->iter_parent     = __iter_parent;
380 }
381
382
383 GType
384 psppire_text_file_get_type (void)
385 {
386   static GType text_file_type = 0;
387
388   if (!text_file_type)
389     {
390       static const GTypeInfo text_file_info =
391         {
392           sizeof (PsppireTextFileClass),
393           NULL,         /* base_init */
394           NULL,         /* base_finalize */
395           (GClassInitFunc) psppire_text_file_class_init,
396           NULL,         /* class_finalize */
397           NULL,         /* class_data */
398           sizeof (PsppireTextFile),
399           0,
400           (GInstanceInitFunc) psppire_text_file_init,
401         };
402
403       static const GInterfaceInfo tree_model_info = {
404         (GInterfaceInitFunc) __tree_model_init,
405         NULL,
406         NULL
407       };
408
409       text_file_type = g_type_register_static (G_TYPE_OBJECT,
410                                                "PsppireTextFile",
411                                                &text_file_info, 0);
412
413       g_type_add_interface_static (text_file_type, GTK_TYPE_TREE_MODEL,
414                                    &tree_model_info);
415     }
416
417   return text_file_type;
418 }
419
420
421 static void
422 psppire_text_file_class_init (PsppireTextFileClass *class)
423 {
424   GObjectClass *object_class;
425
426   parent_class = g_type_class_peek_parent (class);
427   object_class = (GObjectClass*) class;
428
429   GParamSpec *file_name_spec =
430     g_param_spec_string ("file-name",
431                          "File Name",
432                          P_("The name of the file from which this object was constructed"),
433                          NULL,
434                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
435
436   GParamSpec *encoding_spec =
437     g_param_spec_string ("encoding",
438                          "Character Encoding",
439                          P_("The character encoding of the file from which this object was constructed"),
440                          "unset",
441                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);
442
443   object_class->set_property = psppire_text_file_set_property;
444   object_class->get_property = psppire_text_file_get_property;
445
446   g_object_class_install_property (object_class,
447                                    PROP_FILE_NAME,
448                                    file_name_spec);
449
450   g_object_class_install_property (object_class,
451                                    PROP_ENCODING,
452                                    encoding_spec);
453
454   object_class->finalize = psppire_text_file_finalize;
455   object_class->dispose = psppire_text_file_dispose;
456 }
457
458
459 static void
460 psppire_text_file_init (PsppireTextFile *text_file)
461 {
462   text_file->encoding = g_strdup ("unset");
463   text_file->file_name = NULL;
464
465   text_file->dispose_has_run = FALSE;
466   text_file->stamp = g_random_int ();
467 }
468
469
470 PsppireTextFile *
471 psppire_text_file_new (const gchar *file_name, const gchar *encoding)
472 {
473   PsppireTextFile *retval =
474     g_object_new (PSPPIRE_TYPE_TEXT_FILE,
475                   "file-name", file_name,
476                   "encoding", encoding,
477                   NULL);
478
479   return retval;
480 }
481
482 static void
483 psppire_text_file_finalize (GObject *object)
484 {
485   PsppireTextFile *tf = PSPPIRE_TEXT_FILE (object);
486
487   g_free (tf->encoding);
488   g_free (tf->file_name);
489
490   /* must chain up */
491   (* parent_class->finalize) (object);
492 }
493
494
495 static void
496 psppire_text_file_dispose (GObject *object)
497 {
498   PsppireTextFile *ds = PSPPIRE_TEXT_FILE (object);
499
500   if (ds->dispose_has_run)
501     return;
502
503   /* must chain up */
504   (* parent_class->dispose) (object);
505
506   ds->dispose_has_run = TRUE;
507 }
508
509 gboolean
510 psppire_text_file_get_total_exact (PsppireTextFile *tf)
511 {
512   return tf->total_is_exact;
513 }
514
515 gulong
516 psppire_text_file_get_n_lines (PsppireTextFile *tf)
517 {
518   return tf->total_lines;
519 }