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