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