1 /*
2     Gpredict: Real-time satellite tracking and orbit prediction program
3 
4     Copyright (C)  2001-2017  Alexandru Csete, OZ9AEC.
5 
6     Authors: Alexandru Csete <oz9aec@gmail.com>
7 
8     Comments, questions and bugreports should be submitted via
9     http://sourceforge.net/projects/gpredict/
10     More details can be found at the project home page:
11 
12             http://gpredict.oz9aec.net/
13 
14     This program is free software; you can redistribute it and/or modify
15     it under the terms of the GNU General Public License as published by
16     the Free Software Foundation; either version 2 of the License, or
17     (at your option) any later version.
18 
19     This program is distributed in the hope that it will be useful,
20     but WITHOUT ANY WARRANTY; without even the implied warranty of
21     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22     GNU General Public License for more details.
23 
24     You should have received a copy of the GNU General Public License
25     along with this program; if not, visit http://www.fsf.org/
26 */
27 #ifdef HAVE_CONFIG_H
28 #include <build-config.h>
29 #endif
30 #include <gtk/gtk.h>
31 
32 #include "gtk-sat-data.h"
33 #include "pass-to-txt.h"
34 #include "predict-tools.h"
35 #include "sat-cfg.h"
36 #include "sat-log.h"
37 #include "sat-pass-dialogs.h"
38 #include "gpredict-utils.h"
39 #include "save-pass.h"
40 #include "sgpsdp/sgp4sdp4.h"
41 
42 static void     file_changed(GtkWidget * widget, gpointer data);
43 static void     save_pass_exec(GtkWidget * parent,
44                                pass_t * pass, qth_t * qth,
45                                const gchar * savedir, const gchar * savefile,
46                                gint format, gint contents);
47 static void     save_passes_exec(GtkWidget * parent,
48                                  GSList * passes, qth_t * qth,
49                                  const gchar * savedir, const gchar * savefile,
50                                  gint format, gint contents);
51 static void     save_to_file(GtkWidget * parent, const gchar * fname,
52                              const gchar * data);
53 
54 enum pass_content_e {
55     PASS_CONTENT_ALL = 0,
56     PASS_CONTENT_TABLE,
57     PASS_CONTENT_DATA,
58 };
59 
60 enum passes_content_e {
61     PASSES_CONTENT_FULL = 0,
62     PASSES_CONTENT_SUM,
63 };
64 
65 #define SAVE_FORMAT_TXT     0
66 
67 /**
68  * Save a satellite pass.
69  *
70  * @param toplevel Pointer to the parent dialogue window
71  *
72  * This function is called from the button click handler of the satellite pass
73  * dialogue when the user presses the Save button.
74  *
75  * The function opens the Save Pass dialogue asking the user for where to save
76  * the pass and in which format. When the user has made the required choices,
77  * the function uses the lower level functions to save the pass information
78  * to a file.
79  *
80  * \note All the relevant data are attached to the parent dialogue window.
81  */
save_pass(GtkWidget * parent)82 void save_pass(GtkWidget * parent)
83 {
84     GtkWidget      *dialog;
85     GtkWidget      *grid;
86     GtkWidget      *dirchooser;
87     GtkWidget      *filchooser;
88     GtkWidget      *contents;
89     GtkWidget      *label;
90     gint            response;
91     pass_t         *pass;
92     qth_t          *qth;
93     gchar          *savedir = NULL;
94     gchar          *savefile;
95     gint            cont;
96 
97 
98     /* get data attached to parent */
99     qth = (qth_t *) g_object_get_data(G_OBJECT(parent), "qth");
100     pass = (pass_t *) g_object_get_data(G_OBJECT(parent), "pass");
101 
102     /* create the dialog */
103     dialog =
104         gtk_dialog_new_with_buttons(_("Save Pass Details"), GTK_WINDOW(parent),
105                                     GTK_DIALOG_MODAL |
106                                     GTK_DIALOG_DESTROY_WITH_PARENT,
107                                     "_Cancel", GTK_RESPONSE_REJECT,
108                                     "_Save", GTK_RESPONSE_ACCEPT, NULL);
109     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
110 
111     /* create the table */
112     grid = gtk_grid_new();
113     gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
114     gtk_grid_set_row_spacing(GTK_GRID(grid), 10);
115     gtk_container_set_border_width(GTK_CONTAINER(grid), 10);
116 
117     /* directory chooser */
118     label = gtk_label_new(_("Save in folder:"));
119     g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_START,
120                  "valign", GTK_ALIGN_CENTER, NULL);
121     gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
122 
123     dirchooser = gtk_file_chooser_button_new(_("Select a folder"),
124                                              GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
125     savedir = sat_cfg_get_str(SAT_CFG_STR_PRED_SAVE_DIR);
126     if (savedir)
127     {
128         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dirchooser),
129                                             savedir);
130         g_free(savedir);
131     }
132     else
133     {
134         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dirchooser),
135                                             g_get_home_dir());
136     }
137     gtk_grid_attach(GTK_GRID(grid), dirchooser, 1, 0, 1, 1);
138 
139     /* file name */
140     label = gtk_label_new(_("Save using file name:"));
141     g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_START,
142                  "valign", GTK_ALIGN_CENTER, NULL);
143     gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
144 
145     filchooser = gtk_entry_new();
146     gtk_entry_set_max_length(GTK_ENTRY(filchooser), 100);
147     g_signal_connect(filchooser, "changed", G_CALLBACK(file_changed), dialog);
148     gtk_grid_attach(GTK_GRID(grid), filchooser, 1, 1, 1, 1);
149 
150     /* use satellite name + orbit num as default; replace invalid characters
151        with dash */
152     savefile = g_strdup_printf("%s-%d", pass->satname, pass->orbit);
153     savefile = g_strdelimit(savefile, " ", '-');
154     savefile = g_strdelimit(savefile, "!?/\\()*&%$#@[]{}=+<>,.|:;", '_');
155     gtk_entry_set_text(GTK_ENTRY(filchooser), savefile);
156     g_free(savefile);
157 
158     /* file contents */
159     label = gtk_label_new(_("File contents:"));
160     g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_START,
161                  "valign", GTK_ALIGN_CENTER, NULL);
162     gtk_grid_attach(GTK_GRID(grid), label, 0, 2, 1, 1);
163 
164     contents = gtk_combo_box_text_new();
165     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(contents),
166                                    _("Info+header+data"));
167     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(contents),
168                                    _("Header + data"));
169     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(contents),
170                                    _("Data only"));
171     gtk_combo_box_set_active(GTK_COMBO_BOX(contents),
172                              sat_cfg_get_int(SAT_CFG_INT_PRED_SAVE_CONTENTS));
173     gtk_grid_attach(GTK_GRID(grid), contents, 1, 2, 1, 1);
174 
175     gtk_widget_show_all(grid);
176     gtk_container_add(GTK_CONTAINER
177                       (gtk_dialog_get_content_area(GTK_DIALOG(dialog))), grid);
178 
179     /* run the dialog */
180     response = gtk_dialog_run(GTK_DIALOG(dialog));
181 
182     switch (response)
183     {
184         /* user clicked the save button */
185     case GTK_RESPONSE_ACCEPT:
186 
187         /* get file and directory */
188         savedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dirchooser));
189         savefile = g_strdup(gtk_entry_get_text(GTK_ENTRY(filchooser)));
190         cont = gtk_combo_box_get_active(GTK_COMBO_BOX(contents));
191 
192         /* call saver */
193         save_pass_exec(dialog, pass, qth, savedir, savefile, SAVE_FORMAT_TXT,
194                        cont);
195 
196         /* store new settings */
197         sat_cfg_set_str(SAT_CFG_STR_PRED_SAVE_DIR, savedir);
198         sat_cfg_set_int(SAT_CFG_INT_PRED_SAVE_CONTENTS, cont);
199 
200         /* clean up */
201         g_free(savedir);
202         g_free(savefile);
203         break;
204 
205         /* cancel */
206     default:
207         break;
208 
209     }
210 
211     gtk_widget_destroy(dialog);
212 }
213 
214 /**
215  * Save a satellite passes.
216  *
217  * @param toplevel Pointer to the parent dialogue window
218  *
219  * This function is called from the button click handler of the satellite passes
220  * dialogue when the user presses the Save button.
221  *
222  * The function opens the Save Pass dialogue asking the user for where to save
223  * the data and in which format. When the user has made the required choices,
224  * the function uses the lower level functions to save the pass information
225  * to a file.
226  *
227  * @note All the relevant data are attached to the parent dialogue window.
228  */
save_passes(GtkWidget * parent)229 void save_passes(GtkWidget * parent)
230 {
231     GtkWidget      *dialog;
232     GtkWidget      *grid;
233     GtkWidget      *dirchooser;
234     GtkWidget      *filchooser;
235     GtkWidget      *contents;
236     GtkWidget      *label;
237     gint            response;
238     GSList         *passes;
239     gchar          *sat;
240     qth_t          *qth;
241     gchar          *savedir = NULL;
242     gchar          *savefile;
243     gint            cont;
244 
245     /* get data attached to parent */
246     sat = (gchar *) g_object_get_data(G_OBJECT(parent), "sat");
247     qth = (qth_t *) g_object_get_data(G_OBJECT(parent), "qth");
248     passes = (GSList *) g_object_get_data(G_OBJECT(parent), "passes");
249 
250     /* create the dialog */
251     dialog = gtk_dialog_new_with_buttons(_("Save Passes"), GTK_WINDOW(parent),
252                                          GTK_DIALOG_MODAL |
253                                          GTK_DIALOG_DESTROY_WITH_PARENT,
254                                          "_Cancel", GTK_RESPONSE_REJECT,
255                                          "_Save", GTK_RESPONSE_ACCEPT, NULL);
256     gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
257 
258     /* create the table */
259     grid = gtk_grid_new();
260     gtk_grid_set_column_spacing(GTK_GRID(grid), 10);
261     gtk_grid_set_row_spacing(GTK_GRID(grid), 10);
262     gtk_container_set_border_width(GTK_CONTAINER(grid), 10);
263 
264     /* directory chooser */
265     label = gtk_label_new(_("Save in folder:"));
266     g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_START,
267                  "valign", GTK_ALIGN_CENTER, NULL);
268     gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
269 
270     dirchooser = gtk_file_chooser_button_new(_("Select a folder"),
271                                              GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER);
272     savedir = sat_cfg_get_str(SAT_CFG_STR_PRED_SAVE_DIR);
273     if (savedir)
274     {
275         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dirchooser),
276                                             savedir);
277         g_free(savedir);
278     }
279     else
280     {
281         gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dirchooser),
282                                             g_get_home_dir());
283     }
284     gtk_grid_attach(GTK_GRID(grid), dirchooser, 1, 0, 1, 1);
285 
286     /* file name */
287     label = gtk_label_new(_("Save using file name:"));
288     g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_START,
289                  "valign", GTK_ALIGN_CENTER, NULL);
290     gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
291 
292     filchooser = gtk_entry_new();
293     gtk_entry_set_max_length(GTK_ENTRY(filchooser), 100);
294     g_signal_connect(filchooser, "changed", G_CALLBACK(file_changed), dialog);
295     gtk_grid_attach(GTK_GRID(grid), filchooser, 1, 1, 1, 1);
296 
297     /* use satellite name + orbit num as default; replace invalid characters
298        with dash */
299     savefile = g_strdup_printf("%s-passes", sat);
300     savefile = g_strdelimit(savefile, " ", '-');
301     savefile = g_strdelimit(savefile, "!?/\\()*&%$#@[]{}=+<>,.|:;", '_');
302     gtk_entry_set_text(GTK_ENTRY(filchooser), savefile);
303     g_free(savefile);
304 
305     /* file contents */
306     label = gtk_label_new(_("File contents:"));
307     g_object_set(G_OBJECT(label), "halign", GTK_ALIGN_START,
308                  "valign", GTK_ALIGN_CENTER, NULL);
309     gtk_grid_attach(GTK_GRID(grid), label, 0, 2, 1, 1);
310 
311     contents = gtk_combo_box_text_new();
312     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(contents),
313                                    _("Complete report"));
314     gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(contents), _("Summary"));
315     gtk_combo_box_set_active(GTK_COMBO_BOX(contents), 0);
316     gtk_grid_attach(GTK_GRID(grid), contents, 1, 2, 1, 1);
317 
318     gtk_widget_show_all(grid);
319     gtk_container_add(GTK_CONTAINER
320                       (gtk_dialog_get_content_area(GTK_DIALOG(dialog))), grid);
321 
322     /* run the dialog */
323     response = gtk_dialog_run(GTK_DIALOG(dialog));
324 
325     switch (response)
326     {
327         /* user clicked the save button */
328     case GTK_RESPONSE_ACCEPT:
329 
330         /* get file and directory */
331         savedir = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dirchooser));
332         savefile = g_strdup(gtk_entry_get_text(GTK_ENTRY(filchooser)));
333         cont = gtk_combo_box_get_active(GTK_COMBO_BOX(contents));
334 
335         /* call saver */
336         save_passes_exec(dialog, passes, qth, savedir, savefile,
337                          SAVE_FORMAT_TXT, cont);
338 
339         /* store new settings */
340         sat_cfg_set_str(SAT_CFG_STR_PRED_SAVE_DIR, savedir);
341 
342         /* clean up */
343         g_free(savedir);
344         g_free(savefile);
345         break;
346 
347         /* cancel */
348     default:
349         break;
350     }
351     gtk_widget_destroy(dialog);
352 }
353 
354 /**
355  * Manage file name changes.
356  *
357  * @param widget The GtkEntry that received the signal
358  * @param data User data (not used).
359  *
360  * This function is called when the contents of the file name entry changes.
361  * It validates all characters and invalid chars are deleted.
362  * The function sets the state of the Save button according to the validity
363  * of the current file name.
364  */
file_changed(GtkWidget * widget,gpointer data)365 static void file_changed(GtkWidget * widget, gpointer data)
366 {
367     gchar          *entry, *end, *j;
368     gint            len, pos;
369     const gchar    *text;
370     GtkWidget      *dialog = GTK_WIDGET(data);
371 
372     /* ensure that only valid characters are entered
373        (stolen from xlog, tnx pg4i)
374      */
375     entry = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
376     if ((len = g_utf8_strlen(entry, -1)) > 0)
377     {
378         end = entry + g_utf8_strlen(entry, -1);
379         for (j = entry; j < end; ++j)
380         {
381             if (!gpredict_legal_char(*j))
382             {
383                 gdk_beep();
384                 pos = gtk_editable_get_position(GTK_EDITABLE(widget));
385                 gtk_editable_delete_text(GTK_EDITABLE(widget), pos, pos + 1);
386             }
387         }
388     }
389 
390     /* step 2: if name seems all right, enable OK button */
391     text = gtk_entry_get_text(GTK_ENTRY(widget));
392 
393     if (g_utf8_strlen(text, -1) > 0)
394     {
395         gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
396                                           GTK_RESPONSE_ACCEPT, TRUE);
397     }
398     else
399     {
400         gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
401                                           GTK_RESPONSE_ACCEPT, FALSE);
402     }
403 }
404 
405 /**
406  * Save data to file.
407  *
408  * @param parent Parent window (needed for error dialogs).
409  * @param pass The pass data to save.
410  * @param qth The observer data
411  * @param savedir The directory where data should be saved.
412  * @param savefile The file where data should be saved.
413  * @param format The file format
414  * @param contents The contents defining whether to save headers and such.
415  *
416  * This is the function that does the actual saving to a data file once all
417  * required information has been gathered (i.e. file name, format, contents).
418  * The function does some last minute checking while saving and provides
419  * error messages if anything fails during the process.
420  *
421  * @note The formatting is done by external functions according to the selected
422  *       file format.
423  */
save_passes_exec(GtkWidget * parent,GSList * passes,qth_t * qth,const gchar * savedir,const gchar * savefile,gint format,gint contents)424 static void save_passes_exec(GtkWidget * parent,
425                              GSList * passes, qth_t * qth,
426                              const gchar * savedir, const gchar * savefile,
427                              gint format, gint contents)
428 {
429     gchar          *fname;
430     gchar          *pgheader;
431     gchar          *tblheader;
432     gchar          *tblcontents;
433     gchar          *buff = NULL;
434     gchar          *data = NULL;
435     pass_t         *pass;
436     gint            fields;
437     guint           i, n;
438 
439     switch (format)
440     {
441     case SAVE_FORMAT_TXT:
442 
443         /* prepare full file name */
444         fname =
445             g_strconcat(savedir, G_DIR_SEPARATOR_S, savefile, ".txt", NULL);
446 
447         /* get visible columns for summary */
448         fields = sat_cfg_get_int(SAT_CFG_INT_PRED_MULTI_COL);
449 
450         /* create file contents */
451         pgheader = passes_to_txt_pgheader(passes, qth, fields);
452         tblheader = passes_to_txt_tblheader(passes, qth, fields);
453         tblcontents = passes_to_txt_tblcontents(passes, qth, fields);
454 
455         data = g_strconcat(pgheader, tblheader, tblcontents, NULL);
456 
457         g_free(pgheader);
458         g_free(tblheader);
459         g_free(tblcontents);
460 
461         if (contents == PASSES_CONTENT_FULL)
462         {
463             fields = sat_cfg_get_int(SAT_CFG_INT_PRED_SINGLE_COL);
464             n = g_slist_length(passes);
465 
466             for (i = 0; i < n; i++)
467             {
468 
469                 pass = PASS(g_slist_nth_data(passes, i));
470 
471                 tblheader = pass_to_txt_tblheader(pass, qth, fields);
472                 tblcontents = pass_to_txt_tblcontents(pass, qth, fields);
473                 buff = g_strdup_printf("%s\n Orbit %d\n%s%s",
474                                        data, pass->orbit,
475                                        tblheader, tblcontents);
476                 g_free(data);
477                 data = g_strdup(buff);
478                 g_free(buff);
479 
480             }
481         }
482 
483         /* save data */
484         save_to_file(parent, fname, data);
485         g_free(data);
486         g_free(fname);
487         break;
488 
489     default:
490         sat_log_log(SAT_LOG_LEVEL_ERROR,
491                     _("%s: Invalid file format: %d"), __func__, format);
492         break;
493     }
494 }
495 
496 /**
497  * Save data to file.
498  *
499  * @param parent Parent window (needed for error dialogs).
500  * @param passes The pass data to save.
501  * @param qth The observer data
502  * @param savedir The directory where data should be saved.
503  * @param savefile The file where data should be saved.
504  * @param format The file format
505  * @param contents The contents defining whether to save headers and such.
506  *
507  * This is the function that does the actual saving to a data file once all
508  * required information has been gathered (i.e. file name, format, contents).
509  * The function does some last minute checking while saving and provides
510  * error messages if anything fails during the process.
511  *
512  * @note The formatting is done by external functions according to the selected
513  *       file format.
514  */
save_pass_exec(GtkWidget * parent,pass_t * pass,qth_t * qth,const gchar * savedir,const gchar * savefile,gint format,gint contents)515 static void save_pass_exec(GtkWidget * parent,
516                            pass_t * pass, qth_t * qth,
517                            const gchar * savedir, const gchar * savefile,
518                            gint format, gint contents)
519 {
520     gchar          *fname;
521     gchar          *pgheader;
522     gchar          *tblheader;
523     gchar          *tblcontents;
524     gchar          *buff = NULL;
525     gchar          *data = NULL;
526     gint            fields;
527 
528     switch (format)
529     {
530     case SAVE_FORMAT_TXT:
531 
532         /* prepare full file name */
533         fname =
534             g_strconcat(savedir, G_DIR_SEPARATOR_S, savefile, ".txt", NULL);
535 
536         /* get visible columns */
537         fields = sat_cfg_get_int(SAT_CFG_INT_PRED_SINGLE_COL);
538 
539         /* create file contents */
540         pgheader = pass_to_txt_pgheader(pass, qth, fields);
541         tblheader = pass_to_txt_tblheader(pass, qth, fields);
542         tblcontents = pass_to_txt_tblcontents(pass, qth, fields);
543 
544         /* Add page header if selected */
545         if (contents == PASS_CONTENT_ALL)
546         {
547             data = g_strdup(pgheader);
548         }
549 
550         /* Add table header if selected */
551         if ((contents == PASS_CONTENT_ALL) || (contents == PASS_CONTENT_TABLE))
552         {
553             if (data != NULL)
554             {
555                 buff = g_strdup(data);
556                 g_free(data);
557                 data = g_strconcat(buff, tblheader, NULL);
558                 g_free(buff);
559             }
560             else
561             {
562                 data = g_strdup(tblheader);
563             }
564 
565         }
566 
567         /* Add data */
568         if (data != NULL)
569         {
570             buff = g_strdup(data);
571             g_free(data);
572             data = g_strconcat(buff, tblcontents, NULL);
573             g_free(buff);
574         }
575         else
576         {
577             data = g_strdup(tblcontents);
578         }
579 
580         /* save data */
581         save_to_file(parent, fname, data);
582 
583         /* clean up memory */
584         g_free(fname);
585         g_free(data);
586         g_free(pgheader);
587         g_free(tblheader);
588         g_free(tblcontents);
589 
590         break;
591 
592     default:
593         sat_log_log(SAT_LOG_LEVEL_ERROR,
594                     _("%s: Invalid file format: %d"), __func__, format);
595         break;
596     }
597 }
598 
save_to_file(GtkWidget * parent,const gchar * fname,const gchar * data)599 static void save_to_file(GtkWidget * parent, const gchar * fname,
600                          const gchar * data)
601 {
602     GIOChannel     *chan;
603     GError         *err = NULL;
604     GtkWidget      *dialog;
605     gsize           count;
606 
607     /* create file */
608     chan = g_io_channel_new_file(fname, "w", &err);
609     if (err != NULL)
610     {
611         sat_log_log(SAT_LOG_LEVEL_ERROR,
612                     _("%s: Could not create file %s (%s)"),
613                     __func__, fname, err->message);
614 
615         /* error dialog */
616         dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
617                                         GTK_DIALOG_MODAL |
618                                         GTK_DIALOG_DESTROY_WITH_PARENT,
619                                         GTK_MESSAGE_ERROR,
620                                         GTK_BUTTONS_CLOSE,
621                                         _("Could not create file %s\n\n%s"),
622                                         fname, err->message);
623         gtk_dialog_run(GTK_DIALOG(dialog));
624         gtk_widget_destroy(dialog);
625 
626         /* clean up and return */
627         g_clear_error(&err);
628 
629         return;
630     }
631 
632     /* save contents to file */
633     g_io_channel_write_chars(chan, data, -1, &count, &err);
634     if (err != NULL)
635     {
636         sat_log_log(SAT_LOG_LEVEL_ERROR,
637                     _("%s: An error occurred while saving data to %s (%s)"),
638                     __func__, fname, err->message);
639 
640         dialog = gtk_message_dialog_new(GTK_WINDOW(parent),
641                                         GTK_DIALOG_MODAL |
642                                         GTK_DIALOG_DESTROY_WITH_PARENT,
643                                         GTK_MESSAGE_ERROR,
644                                         GTK_BUTTONS_CLOSE,
645                                         _
646                                         ("An error occurred while saving data to %s\n\n%s"),
647                                         fname, err->message);
648         gtk_dialog_run(GTK_DIALOG(dialog));
649         gtk_widget_destroy(dialog);
650         g_clear_error(&err);
651     }
652     else
653     {
654         sat_log_log(SAT_LOG_LEVEL_DEBUG,
655                     _("%s: Written %d characters to %s"),
656                     __func__, count, fname);
657     }
658 
659     /* close file, we don't care about errors here */
660     g_io_channel_shutdown(chan, TRUE, NULL);
661     g_io_channel_unref(chan);
662 
663 }
664