/ src / sat-pref-qth.c
sat-pref-qth.c
  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             Charles Suprin  <hamaa1vs@gmail.com>
  8  
  9    This program is free software; you can redistribute it and/or modify
 10    it under the terms of the GNU General Public License as published by
 11    the Free Software Foundation; either version 2 of the License, or
 12    (at your option) any later version.
 13    
 14    This program is distributed in the hope that it will be useful,
 15    but WITHOUT ANY WARRANTY; without even the implied warranty of
 16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17    GNU General Public License for more details.
 18    
 19    You should have received a copy of the GNU General Public License
 20    along with this program; if not, visit http://www.fsf.org/
 21  */
 22  #ifdef HAVE_CONFIG_H
 23  #include <build-config.h>
 24  #endif
 25  #include <glib/gi18n.h>
 26  #include <glib/gstdio.h>
 27  #include <gtk/gtk.h>
 28  
 29  #include "compat.h"
 30  #include "gpredict-utils.h"
 31  #include "locator.h"
 32  #include "qth-data.h"
 33  #include "sat-cfg.h"
 34  #include "sat-log.h"
 35  #include "sat-pref-qth.h"
 36  #include "sat-pref-qth-data.h"
 37  #include "sat-pref-qth-editor.h"
 38  
 39  
 40  extern GtkWidget *window;       /* dialog window defined in sat-pref.c */
 41  static GtkWidget *addbutton;
 42  static GtkWidget *editbutton;
 43  static GtkWidget *delbutton;
 44  static gulong   handler_id;
 45  
 46  static GtkWidget *qthlist;      /* we need access to this from the ok and cancel functions */
 47  
 48  /* private function prototypes */
 49  static GtkWidget *create_qth_list(void);
 50  static GtkTreeModel *create_and_fill_model(void);
 51  static guint    read_qth_file(GtkListStore * liststore, gchar * filename);
 52  static GtkWidget *create_buttons(GtkTreeView * qthlist);
 53  static void     default_toggled(GtkCellRendererToggle * cell,
 54                                  gchar * path_str, gpointer data);
 55  
 56  static gboolean clear_default_flags(GtkTreeModel * model,
 57                                      GtkTreePath * path,
 58                                      GtkTreeIter * iter, gpointer defqth);
 59  
 60  static void     float_cell_data_function(GtkTreeViewColumn * col,
 61                                           GtkCellRenderer * renderer,
 62                                           GtkTreeModel * model,
 63                                           GtkTreeIter * iter, gpointer column);
 64  
 65  static void     add_cb(GtkWidget * button, gpointer data);
 66  static void     edit_cb(GtkWidget * button, gpointer data);
 67  static void     delete_cb(GtkWidget * button, gpointer data);
 68  static void     row_activated_cb(GtkTreeView * tree_view,
 69                                   GtkTreePath * path,
 70                                   GtkTreeViewColumn * column,
 71                                   gpointer user_data);
 72  
 73  /* static gboolean check_and_set_default_qth (GtkTreeModel *model, */
 74  /*                                                      GtkTreePath  *path, */
 75  /*                                                      GtkTreeIter  *iter, */
 76  /*                                                      gpointer      data); */
 77  
 78  static gboolean save_qth(GtkTreeModel * model,
 79                           GtkTreePath * path,
 80                           GtkTreeIter * iter, gpointer data);
 81  
 82  static void     delete_location_files(void);
 83  
 84  
 85  static gboolean convert_qth_altitude(GtkTreeModel * model,
 86                                       GtkTreePath * path,
 87                                       GtkTreeIter * iter, gpointer data);
 88  
 89  
 90  /**
 91   * @brief Create and initialise widgets for the locations prefs tab.
 92   *
 93   * The QTH tab consists of two main parts: A list showing the configured
 94   * locations (.qth files in USER_CONF_DIR/ and three buttons beside the list:
 95   *    Add      Add a new location to the list
 96   *    Edit     Edit selected location
 97   *    Delete   Delete selected location
 98   *
 99   * @note This module uses the gtk-sat-data infrastructure to read and write
100   *       .qth files.
101   *
102   * Add button always active.
103   * The other buttons are active only if a row is selected.
104   *
105   */
106  GtkWidget      *sat_pref_qth_create()
107  {
108      GtkWidget      *vbox;       /* vbox containing the list part and the details part */
109      GtkWidget      *swin;
110  
111      vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
112      gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
113      gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
114  
115      /* create qth list and pack into scrolled window */
116      qthlist = create_qth_list();
117      swin = gtk_scrolled_window_new(NULL, NULL);
118      gtk_container_add(GTK_CONTAINER(swin), qthlist);
119  
120      gtk_box_pack_start(GTK_BOX(vbox), swin, TRUE, TRUE, 0);
121      gtk_box_pack_start(GTK_BOX(vbox),
122                         create_buttons(GTK_TREE_VIEW(qthlist)),
123                         FALSE, FALSE, 0);
124  
125      return vbox;
126  }
127  
128  /** User pressed cancel. Any changes to config must be cancelled. */
129  void sat_pref_qth_cancel()
130  {
131  }
132  
133  /** User pressed OK. Any changes should be stored in config. */
134  void sat_pref_qth_ok()
135  {
136      delete_location_files();
137  
138      gtk_tree_model_foreach(gtk_tree_view_get_model(GTK_TREE_VIEW(qthlist)),
139                             save_qth, NULL);
140  }
141  
142  /** Create QTH list widgets. */
143  static GtkWidget *create_qth_list()
144  {
145      GtkTreeModel   *model;
146      GtkCellRenderer *renderer;
147      GtkTreeViewColumn *column;
148  
149      qthlist = gtk_tree_view_new();
150  
151      model = create_and_fill_model();
152      gtk_tree_view_set_model(GTK_TREE_VIEW(qthlist), model);
153      g_object_unref(model);
154  
155      /* name column */
156      renderer = gtk_cell_renderer_text_new();
157      column = gtk_tree_view_column_new_with_attributes(_("Name"), renderer,
158                                                        "text",
159                                                        QTH_LIST_COL_NAME, NULL);
160      gtk_tree_view_column_set_expand(column, TRUE);
161      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
162  
163      /* location column */
164      renderer = gtk_cell_renderer_text_new();
165      column = gtk_tree_view_column_new_with_attributes(_("Location"), renderer,
166                                                        "text", QTH_LIST_COL_LOC,
167                                                        NULL);
168      gtk_tree_view_column_set_expand(column, TRUE);
169      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
170  
171      /* lat column */
172      renderer = gtk_cell_renderer_text_new();
173      column = gtk_tree_view_column_new_with_attributes(_("Lat"), renderer,
174                                                        "text", QTH_LIST_COL_LAT,
175                                                        NULL);
176      gtk_tree_view_column_set_alignment(column, 0.5);
177      gtk_tree_view_column_set_cell_data_func(column,
178                                              renderer,
179                                              float_cell_data_function,
180                                              GUINT_TO_POINTER(QTH_LIST_COL_LAT),
181                                              NULL);
182      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
183  
184      /* lon column */
185      renderer = gtk_cell_renderer_text_new();
186      column = gtk_tree_view_column_new_with_attributes(_("Lon"), renderer,
187                                                        "text", QTH_LIST_COL_LON,
188                                                        NULL);
189      gtk_tree_view_column_set_alignment(column, 0.5);
190      gtk_tree_view_column_set_cell_data_func(column,
191                                              renderer,
192                                              float_cell_data_function,
193                                              GUINT_TO_POINTER(QTH_LIST_COL_LON),
194                                              NULL);
195      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
196  
197      /* alt column */
198      renderer = gtk_cell_renderer_text_new();
199      if (sat_cfg_get_bool(SAT_CFG_BOOL_USE_IMPERIAL))
200      {
201          column = gtk_tree_view_column_new_with_attributes(_("Alt\n(ft)"),
202                                                            renderer,
203                                                            "text",
204                                                            QTH_LIST_COL_ALT,
205                                                            NULL);
206      }
207      else
208      {
209          column =
210              gtk_tree_view_column_new_with_attributes(_("Alt\n(m)"), renderer,
211                                                       "text", QTH_LIST_COL_ALT,
212                                                       NULL);
213      }
214      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
215      gtk_tree_view_column_set_alignment(column, 0.5);
216  
217      /* locator */
218      renderer = gtk_cell_renderer_text_new();
219      column = gtk_tree_view_column_new_with_attributes(_("QRA"), renderer,
220                                                        "text", QTH_LIST_COL_QRA,
221                                                        NULL);
222      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
223      gtk_tree_view_column_set_alignment(column, 0.5);
224  
225      /* weather station */
226      /*      renderer = gtk_cell_renderer_text_new (); */
227      /*      column = gtk_tree_view_column_new_with_attributes (_("WX"), renderer, */
228      /*                                       "text", QTH_LIST_COL_WX, */
229      /*                                       NULL); */
230      /*      gtk_tree_view_insert_column (GTK_TREE_VIEW (qthlist), column, -1); */
231      /*      gtk_tree_view_column_set_alignment (column, 0.5); */
232  
233      /* default */
234      renderer = gtk_cell_renderer_toggle_new();
235      handler_id = g_signal_connect(renderer, "toggled",
236                                    G_CALLBACK(default_toggled), model);
237  
238      column = gtk_tree_view_column_new_with_attributes(_("Default"), renderer,
239                                                        "active",
240                                                        QTH_LIST_COL_DEF, NULL);
241      gtk_tree_view_append_column(GTK_TREE_VIEW(qthlist), column);
242      gtk_tree_view_column_set_alignment(column, 0.5);
243  
244      g_signal_connect(qthlist, "row-activated", G_CALLBACK(row_activated_cb),
245                       NULL);
246  
247  #ifdef HAS_LIBGPS
248      /* GPSD enabled */
249      /*server */
250      renderer = gtk_cell_renderer_text_new();
251      column =
252          gtk_tree_view_column_new_with_attributes(_("GPSD\nServer"), renderer,
253                                                   "text",
254                                                   QTH_LIST_COL_GPSD_SERVER,
255                                                   NULL);
256      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
257      gtk_tree_view_column_set_alignment(column, 0.5);
258      /*port */
259      renderer = gtk_cell_renderer_text_new();
260      column =
261          gtk_tree_view_column_new_with_attributes(_("GPSD\nPort"), renderer,
262                                                   "text",
263                                                   QTH_LIST_COL_GPSD_PORT, NULL);
264      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
265      gtk_tree_view_column_set_alignment(column, 0.5);
266  
267      /*type */
268      renderer = gtk_cell_renderer_text_new();
269      column = gtk_tree_view_column_new_with_attributes(_("QTH\nType"), renderer,
270                                                        "text",
271                                                        QTH_LIST_COL_TYPE, NULL);
272      gtk_tree_view_insert_column(GTK_TREE_VIEW(qthlist), column, -1);
273      gtk_tree_view_column_set_alignment(column, 0.5);
274  #endif
275  
276      return qthlist;
277  }
278  
279  /**
280   * Create data storage for QTH list.
281   *
282   * This function creates the data storage necessary for the
283   * list view. The newly created tree model is populated with
284   * data from the .qth files in the users config directory.
285   * The individual .qth files are read by the read_qth_file
286   * function.
287   */
288  static GtkTreeModel *create_and_fill_model()
289  {
290      GtkListStore   *liststore;  /* the list store data structure */
291      GDir           *dir = NULL; /* directory handle */
292      GError         *error = NULL;       /* error flag and info */
293      gchar          *dirname;    /* directory name */
294      const gchar    *filename;   /* file name */
295      gchar          *buff;
296  
297      /* create a new list store */
298      liststore = gtk_list_store_new(QTH_LIST_COL_NUM, G_TYPE_STRING,     // QTH name
299                                     G_TYPE_STRING,       // Location
300                                     G_TYPE_STRING,       // Description
301                                     G_TYPE_DOUBLE,       // Latitude
302                                     G_TYPE_DOUBLE,       // Longitude
303                                     G_TYPE_INT,  // Altitude
304                                     G_TYPE_STRING,       // QRA locator
305                                     G_TYPE_STRING,       // Weather station
306                                     G_TYPE_BOOLEAN,      // Default
307                                     G_TYPE_INT,  //type
308                                     G_TYPE_STRING,       //server
309                                     G_TYPE_INT   //port
310          );
311  
312      gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(liststore),
313                                           QTH_LIST_COL_NAME,
314                                           GTK_SORT_ASCENDING);
315      /* scan for .qth files in the user config directory and
316         add the contents of each .qth file to the list store
317       */
318      dirname = get_user_conf_dir();
319      dir = g_dir_open(dirname, 0, &error);
320  
321      if (dir)
322      {
323          while ((filename = g_dir_read_name(dir)))
324          {
325              if (g_str_has_suffix(filename, ".qth"))
326              {
327                  buff = g_strconcat(dirname, G_DIR_SEPARATOR_S, filename, NULL);
328  
329                  /* read qth file */
330                  if (read_qth_file(liststore, buff))
331                  {
332                      /* send debug message */
333                      sat_log_log(SAT_LOG_LEVEL_DEBUG,
334                                  _("%s:%d: Read QTH data from %s."),
335                                  __FILE__, __LINE__, filename);
336                  }
337                  else
338                  {
339                      /* error reading the file */
340                      sat_log_log(SAT_LOG_LEVEL_ERROR,
341                                  _("%s:%d: Error reading %s (see prev msg)"),
342                                  __FILE__, __LINE__, filename);
343                  }
344                  g_free(buff);
345              }
346          }
347      }
348      else
349      {
350          sat_log_log(SAT_LOG_LEVEL_ERROR,
351                      _("%s:%d: Failed to open user cfg dir (%s)"),
352                      __FILE__, __LINE__, error->message);
353          g_clear_error(&error);
354      }
355  
356      g_free(dirname);
357      g_dir_close(dir);
358  
359      return GTK_TREE_MODEL(liststore);
360  }
361  
362  /**
363   * Read QTH file and add data to list store.
364   * @param liststore The GtkListStore where the data should be stored.
365   * @param filename The full name of the qth file.
366   * @return 1 if read is successful, 0 if an error occurs.
367   *
368   * The function uses the gtk-sat-data infrastructure to read the qth
369   * data from the specified file.
370   *
371   * There is a little challenge here. First, we want to read the data from
372   * the .qth files and store them in the list store. To do this we use a
373   * qth_t structure, which can be populated using gtk_sat_data_read_qth.
374   * Then, when the configuration is finished and the user presses "OK", we
375   * want to write all the data back to the .qth files. To do that, we create
376   * an up-to-date qth_t data structure and pass it to the gtk_sat_data_write_qth
377   * function, which will take care of updating the GKeyFile data structure and
378   * writing the contents to the .qth file.
379   */
380  static guint read_qth_file(GtkListStore * liststore, gchar * filename)
381  {
382      GtkTreeIter     item;       /* new item added to the list store */
383      qth_t          *qth;        /* qth data structure */
384      gchar          *defqth;
385      gboolean        is_default = FALSE;
386      gchar          *fname;
387      gint            dispalt;    /* displayed altitude */
388  
389      if ((qth = g_try_new0(qth_t, 1)) == NULL)
390      {
391          sat_log_log(SAT_LOG_LEVEL_ERROR,
392                      _("%s:%d: Failed to allocate memory!\n"),
393                      __FILE__, __LINE__);
394  
395          return FALSE;
396      }
397  
398      /* read data from file */
399      if (!qth_data_read(filename, qth))
400      {
401          g_free(qth);
402          return FALSE;
403      }
404  
405      /* calculate QRA locator */
406      gint            retcode;
407  
408      qth->qra = g_malloc(7);
409      retcode = longlat2locator(qth->lon, qth->lat, qth->qra, 3);
410  
411      if (retcode != RIG_OK)
412      {
413          sat_log_log(SAT_LOG_LEVEL_ERROR,
414                      _("%s:%d: Could not convert (%.2f,%.2f) to QRA."),
415                      __FILE__, __LINE__, qth->lat, qth->lon);
416          qth->qra[0] = '\0';
417      }
418      else
419      {
420          qth->qra[6] = '\0';
421          sat_log_log(SAT_LOG_LEVEL_DEBUG,
422                      _("%s:%d: QRA locator is %s"),
423                      __FILE__, __LINE__, qth->qra);
424      }
425  
426      /* is this the default qth? */
427      defqth = sat_cfg_get_str(SAT_CFG_STR_DEF_QTH);
428  
429      if (g_str_has_suffix(filename, defqth))
430      {
431          is_default = TRUE;
432  
433          sat_log_log(SAT_LOG_LEVEL_INFO,
434                      _("%s:%d: This appears to be the default QTH."),
435                      __FILE__, __LINE__);
436      }
437  
438      g_free(defqth);
439  
440      /* check wehter we are using imperial or metric system;
441         in case of imperial we have to convert altitude from
442         meters to feet.
443         note: the internat data are always kept in metric and
444         only the displayed numbers are converted. Therefore,
445         we use a dedicated var 'dispalt'
446       */
447      /* NO, ONLY DISPLAY WIDGETS WILL BE AFFECTED BY THIS SETTING */
448      if (sat_cfg_get_bool(SAT_CFG_BOOL_USE_IMPERIAL))
449      {
450          dispalt = (gint) M_TO_FT(qth->alt);
451      }
452      else
453      {
454          dispalt = (gint) qth->alt;
455      }
456  
457      /* strip file name; we don't need the whole path */
458      fname = g_path_get_basename(filename);
459  
460      /* we now have all necessary data in the qth_t structure;
461         add the data to the list store */
462      gtk_list_store_append(liststore, &item);
463      gtk_list_store_set(liststore, &item,
464                         QTH_LIST_COL_NAME, qth->name,
465                         QTH_LIST_COL_LOC, qth->loc,
466                         QTH_LIST_COL_DESC, qth->desc,
467                         QTH_LIST_COL_LAT, qth->lat,
468                         QTH_LIST_COL_LON, qth->lon,
469                         QTH_LIST_COL_ALT, dispalt,
470                         QTH_LIST_COL_QRA, qth->qra,
471                         QTH_LIST_COL_WX, qth->wx,
472                         QTH_LIST_COL_DEF, is_default,
473                         QTH_LIST_COL_TYPE, qth->type,
474                         QTH_LIST_COL_GPSD_SERVER, qth->gpsd_server,
475                         QTH_LIST_COL_GPSD_PORT, qth->gpsd_port, -1);
476  
477      g_free(fname);
478  
479      /* we are finished with this qth, free it */
480      qth_data_free(qth);
481  
482      return TRUE;
483  }
484  
485  /**
486   * Create buttons.
487   * @param qthlist The GtkTreeView widget containing the qth data.
488   * @return A button box containing the buttons.
489   *
490   * This function creates and initialises the three buttons below the qth list.
491   * The treeview widget is needed by the buttons when they are activated.
492   */
493  static GtkWidget *create_buttons(GtkTreeView * qthlist)
494  {
495      GtkWidget      *box;
496  
497      /* add button */
498      addbutton = gtk_button_new_with_label(_("Add new"));
499      gtk_widget_set_tooltip_text(addbutton,
500                                  _("Add a new ground station to the list"));
501      g_signal_connect(addbutton, "clicked", G_CALLBACK(add_cb), qthlist);
502  
503      /* edit button */
504      editbutton = gtk_button_new_with_label(_("Edit"));
505      gtk_widget_set_tooltip_text(editbutton,
506                                  _("Edit the selected ground station"));
507      g_signal_connect(editbutton, "clicked", G_CALLBACK(edit_cb), qthlist);
508  
509      /* delete button; don't forget to delete file.... */
510      delbutton = gtk_button_new_with_label(_("Delete"));
511      gtk_widget_set_tooltip_text(delbutton,
512                                  _("Delete selected ground station"));
513      g_signal_connect(delbutton, "clicked", G_CALLBACK(delete_cb), qthlist);
514  
515      /* vertical button box */
516      box = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
517      gtk_button_box_set_layout(GTK_BUTTON_BOX(box), GTK_BUTTONBOX_START);
518  
519      gtk_container_add(GTK_CONTAINER(box), addbutton);
520      gtk_container_add(GTK_CONTAINER(box), editbutton);
521      gtk_container_add(GTK_CONTAINER(box), delbutton);
522  
523      return box;
524  }
525  
526  static void add_cb(GtkWidget * button, gpointer data)
527  {
528      GtkTreeView    *qthlist = GTK_TREE_VIEW(data);
529  
530      (void)button;
531  
532      sat_pref_qth_editor_run(qthlist, TRUE);
533  }
534  
535  static void row_activated_cb(GtkTreeView * tree_view,
536                               GtkTreePath * path,
537                               GtkTreeViewColumn * column, gpointer user_data)
538  {
539      (void)path;
540      (void)column;
541      (void)user_data;
542  
543      sat_pref_qth_editor_run(tree_view, FALSE);
544  }
545  
546  static void edit_cb(GtkWidget * button, gpointer data)
547  {
548      GtkTreeView    *qthlist = GTK_TREE_VIEW(data);
549  
550      (void)button;
551  
552      sat_pref_qth_editor_run(qthlist, FALSE);
553  }
554  
555  /**
556   * Delete selected location
557   *
558   * This function is called when the user clicks the DELETE button.
559   * If there are more than one locations defined in the list, the function
560   * will delete the selected one. If the deleted location used to be the default
561   * location, the first entry in the list will be selected as new default location.
562   *
563   * @note This function only deletes from the QTH list, not from disk. If the user
564   *       eventually presses OK, all QTH files will be removed before the new data
565   *       is rewritten to disk, thus the deleted entry will not be saved again. On
566   *       the other hand, if user clicks cancel, the delete process will be undone.
567   */
568  static void delete_cb(GtkWidget * button, gpointer data)
569  {
570      GtkTreeView    *qthlist = GTK_TREE_VIEW(data);
571      GtkTreeModel   *model = gtk_tree_view_get_model(qthlist);
572      GtkTreeModel   *selmod;
573      GtkTreeSelection *selection;
574      GtkTreeIter     iter;
575  
576      (void)button;
577  
578      /* if this is the only entry, tell user that it is not
579         possible to delete
580       */
581      if (gtk_tree_model_iter_n_children(model, NULL) < 2)
582      {
583  
584          GtkWidget      *dialog;
585  
586          dialog = gtk_message_dialog_new(GTK_WINDOW(window),
587                                          GTK_DIALOG_MODAL |
588                                          GTK_DIALOG_DESTROY_WITH_PARENT,
589                                          GTK_MESSAGE_ERROR,
590                                          GTK_BUTTONS_OK,
591                                          _("Can not delete ground station!\n\n"
592                                            "You need to have at least one ground\n"
593                                            "station set up, otherwise gpredict may\n"
594                                            "not work properly."));
595  
596          gtk_dialog_run(GTK_DIALOG(dialog));
597          gtk_widget_destroy(dialog);
598      }
599      else
600      {
601          /* get selected row
602             FIXME: do we really need to work with two models?
603           */
604          selection = gtk_tree_view_get_selection(qthlist);
605          if (gtk_tree_selection_get_selected(selection, &selmod, &iter))
606          {
607  
608              gboolean        neednewdef = FALSE;
609              gchar          *buff;
610  
611              gtk_tree_model_get(selmod, &iter,
612                                 QTH_LIST_COL_DEF, &neednewdef,
613                                 QTH_LIST_COL_NAME, &buff, -1);
614  
615              g_free(buff);
616  
617              /* delete qth entry from list */
618              gtk_list_store_remove(GTK_LIST_STORE(selmod), &iter);
619  
620  
621              /* if the selected qth entry is the default one
622                 select the first entry in the list as the
623                 new default qth.
624               */
625              if (neednewdef)
626              {
627                  if (gtk_tree_model_get_iter_first(model, &iter))
628                  {
629                      gtk_list_store_set(GTK_LIST_STORE(model),
630                                         &iter, QTH_LIST_COL_DEF, TRUE, -1);
631                  }
632                  else
633                  {
634                      /* huh? no more entries, this is a bug! */
635                      sat_log_log(SAT_LOG_LEVEL_ERROR,
636                                  _("%s:%d: Empty ground station list!"),
637                                  __FILE__, __LINE__);
638                  }
639              }
640          }
641      }
642  }
643  
644  /**
645   * Handle toggle events on "Default" check box
646   * @param cell The item that received the signal.
647   * @param path_str Path string.
648   * @param data Pointer to user data (list store).
649   *
650   * This function is called when the user clicks on "Default" check box
651   * indicating that a new default location has been selected. If the
652   * clicked check box has been un-checked the action is ignored, because
653   * we need a default location. If the clicked check box has been checked,
654   * the default flag of the checked QTH is set to TRUE, while the flag is
655   * cleared for all the other QTH's.
656   */
657  static void default_toggled(GtkCellRendererToggle * cell, gchar * path_str,
658                              gpointer data)
659  {
660      GtkTreeModel   *model = (GtkTreeModel *) data;
661      GtkTreeIter     iter;
662      GtkTreePath    *path = gtk_tree_path_new_from_string(path_str);
663      gboolean        fixed;
664      gchar          *defqth;
665  
666      /* block toggle signals while we mess with the check boxes */
667      g_signal_handler_block(cell, handler_id);
668  
669      /* get toggled iter */
670      gtk_tree_model_get_iter(model, &iter, path);
671      gtk_tree_model_get(model, &iter, QTH_LIST_COL_DEF, &fixed, -1);
672  
673      if (fixed)
674      {
675          /* do nothing except sending a message */
676          sat_log_log(SAT_LOG_LEVEL_INFO,
677                      _("%s:%d: Default QTH can not be cleared! "
678                        "Select another QTH to change default."),
679                      __FILE__, __LINE__);
680      }
681      else
682      {
683          /* make this qth new default */
684          gtk_list_store_set(GTK_LIST_STORE(model), &iter,
685                             QTH_LIST_COL_DEF, TRUE, -1);
686  
687          /* copy file name of new default QTH to a string buffer */
688          gtk_tree_model_get(model, &iter, QTH_LIST_COL_NAME, &defqth, -1);
689  
690          sat_log_log(SAT_LOG_LEVEL_INFO,
691                      _("%s:%d: New default QTH is %s.qth."),
692                      __FILE__, __LINE__, defqth);
693  
694          /* clear the default flag for the other qth */
695          gtk_tree_model_foreach(model, clear_default_flags, defqth);
696  
697          g_free(defqth);
698  
699      }
700  
701      /* clean up */
702      gtk_tree_path_free(path);
703  
704      /* unblock toggle signals */
705      g_signal_handler_unblock(cell, handler_id);
706  }
707  
708  /**
709   * Clear default flag for all qth, except defqth.
710   * @param model The GtkTreeModel.
711   * @param path  The GtkTreePath.
712   * @param iter  The GtkTreeIter of the current item.
713   * @param defqth The file name of the new default which should not be cleared.
714   *
715   * This function is called for each QTH entry in the QTH list with the purpose of
716   * clearing the default flag. This happens when the user selects a new QTH to be
717   * the default QTH and the other ones need to be cleared.
718   */
719  static gboolean clear_default_flags(GtkTreeModel * model, GtkTreePath * path,
720                                      GtkTreeIter * iter, gpointer defqth)
721  {
722      gchar          *thisqth;
723  
724      (void)path;
725  
726      gtk_tree_model_get(model, iter, QTH_LIST_COL_NAME, &thisqth, -1);
727  
728      /* clear flag if this is not the new default QTH */
729      if (g_ascii_strcasecmp(thisqth, (gchar *) defqth))
730      {
731          gtk_list_store_set(GTK_LIST_STORE(model), iter,
732                             QTH_LIST_COL_DEF, FALSE, -1);
733  
734          sat_log_log(SAT_LOG_LEVEL_DEBUG,
735                      _("%s:%d: Clearing default flag for %s."),
736                      __FILE__, __LINE__, thisqth);
737      }
738  
739      g_free(thisqth);
740  
741      return FALSE;
742  }
743  
744  /* render column containing float
745     by using this instead of the default data function, we can
746     control the number of decimals and display the coordinates in a
747     fancy way, including degree sign and NWSE suffixes.
748  
749     Please note that this function only affects how the numbers are
750     displayed (rendered), the tree_store will still contain the
751     original floating point numbers. Very cool!
752  */
753  static void float_cell_data_function(GtkTreeViewColumn * col,
754                                       GtkCellRenderer * renderer,
755                                       GtkTreeModel * model,
756                                       GtkTreeIter * iter, gpointer column)
757  {
758      gdouble         number;
759      gchar          *buff;
760      guint           coli = GPOINTER_TO_UINT(column);
761      gchar           hmf = ' ';
762  
763      (void)col;
764  
765      gtk_tree_model_get(model, iter, coli, &number, -1);
766  
767      /* check whether configuration requests the use
768         of N, S, E and W instead of signs
769       */
770      if (sat_cfg_get_bool(SAT_CFG_BOOL_USE_NSEW))
771      {
772          if (coli == QTH_LIST_COL_LAT)
773          {
774              if (number < 0.0)
775              {
776                  number *= -1.0;
777                  hmf = 'S';
778              }
779              else
780              {
781                  hmf = 'N';
782              }
783          }
784          else if (coli == QTH_LIST_COL_LON)
785          {
786              if (number < 0.0)
787              {
788                  number *= -1.0;
789                  hmf = 'W';
790              }
791              else
792              {
793                  hmf = 'E';
794              }
795          }
796          else
797          {
798              sat_log_log(SAT_LOG_LEVEL_ERROR,
799                          _("%s:%d: Invalid column: %d"),
800                          __FILE__, __LINE__, coli);
801              hmf = '?';
802          }
803      }
804  
805      /* format the number */
806      buff = g_strdup_printf("%.4f\302\260%c", number, hmf);
807      g_object_set(renderer, "text", buff, NULL);
808      g_free(buff);
809  }
810  
811  /** Save a row from the QTH list (called by the save function) */
812  static gboolean save_qth(GtkTreeModel * model, GtkTreePath * path,
813                           GtkTreeIter * iter, gpointer data)
814  {
815      qth_t           qth;
816      gboolean        def = FALSE;
817      gchar          *filename, *confdir;
818      gchar          *buff;
819  
820      (void)path;
821      (void)data;
822  
823      gtk_tree_model_get(model, iter,
824                         QTH_LIST_COL_DEF, &def,
825                         QTH_LIST_COL_NAME, &qth.name,
826                         QTH_LIST_COL_LOC, &qth.loc,
827                         QTH_LIST_COL_DESC, &qth.desc,
828                         QTH_LIST_COL_LAT, &qth.lat,
829                         QTH_LIST_COL_LON, &qth.lon,
830                         QTH_LIST_COL_ALT, &qth.alt,
831                         QTH_LIST_COL_WX, &qth.wx,
832                         QTH_LIST_COL_TYPE, &qth.type,
833                         QTH_LIST_COL_GPSD_SERVER, &qth.gpsd_server,
834                         QTH_LIST_COL_GPSD_PORT, &qth.gpsd_port, -1);
835  
836      confdir = get_user_conf_dir();
837      filename = g_strconcat(confdir, G_DIR_SEPARATOR_S, qth.name, ".qth", NULL);
838      g_free(confdir);
839  
840      /* check wehter we are using imperial or metric system;
841         in case of imperial we have to convert altitude from
842         feet to meters before saving.
843       */
844      if (sat_cfg_get_bool(SAT_CFG_BOOL_USE_IMPERIAL))
845      {
846          qth.alt = (guint) FT_TO_M(qth.alt);
847      }
848  
849      if (qth_data_save(filename, &qth))
850      {
851          /* saved ok, go on check whether qth is default */
852          if (def)
853          {
854              sat_log_log(SAT_LOG_LEVEL_INFO,
855                          _("%s:%d: %s appears to be default QTH"),
856                          __FILE__, __LINE__, qth.name);
857  
858              buff = g_path_get_basename(filename);
859              sat_cfg_set_str(SAT_CFG_STR_DEF_QTH, buff);
860              g_free(buff);
861          }
862      }
863  
864      g_free(filename);
865      g_free(qth.name);
866      g_free(qth.loc);
867      g_free(qth.desc);
868      g_free(qth.wx);
869  
870      return FALSE;
871  }
872  
873  /**
874   * Remove .qth files.
875   *
876   * This function is used to remove any existing .qth file
877   * before storing the data from the QTH list.
878   */
879  static void delete_location_files()
880  {
881      GDir           *dir = NULL; /* directory handle */
882      GError         *error = NULL;       /* error flag and info */
883      gchar          *dirname;    /* directory name */
884      const gchar    *filename;   /* file name */
885      gchar          *buff;
886  
887      /* scan for .qth files in the user config directory and
888         add the contents of each .qth file to the list store
889       */
890      dirname = get_user_conf_dir();
891      dir = g_dir_open(dirname, 0, &error);
892  
893      if (dir)
894      {
895          while ((filename = g_dir_read_name(dir)))
896          {
897              if (g_str_has_suffix(filename, ".qth"))
898              {
899                  buff = g_strconcat(dirname, G_DIR_SEPARATOR_S, filename, NULL);
900  
901                  /* remove file */
902                  if (g_remove(buff))
903                  {
904                      sat_log_log(SAT_LOG_LEVEL_ERROR,
905                                  _("%s:%d: Failed to remove %s"),
906                                  __FILE__, __LINE__, filename);
907                  }
908                  else
909                  {
910                      sat_log_log(SAT_LOG_LEVEL_DEBUG,
911                                  _("%s:%d: Removed %s"),
912                                  __FILE__, __LINE__, filename);
913                  }
914  
915                  g_free(buff);
916              }
917          }
918      }
919      else
920      {
921          sat_log_log(SAT_LOG_LEVEL_ERROR,
922                      _("%s:%d: Failed to open user cfg dir (%s)"),
923                      __FILE__, __LINE__, error->message);
924          g_clear_error(&error);
925  
926      }
927  
928      g_free(dirname);
929      g_dir_close(dir);
930  }
931  
932  /**
933   * Manage measurement system changes.
934   *
935   * This function should be called when the user changes between the
936   * metric and imperial systems. When that happens, we need to convert
937   * location altitudeas right away, otherwise the altitudes in the qth
938   * list will keep their old values in the new system.
939   *
940   * If imperial = TRUE the new system is imperial and so we have to use
941   * M_TO_FT.
942   */
943  void sat_pref_qth_sys_changed(gboolean imperial)
944  {
945      gtk_tree_model_foreach(gtk_tree_view_get_model(GTK_TREE_VIEW(qthlist)),
946                             convert_qth_altitude, GUINT_TO_POINTER(imperial));
947  }
948  
949  /**
950   * Convert altitude of the specified QTH entry.
951   *
952   * The data contain pointer to a boolean indicating whether the new
953   * system is imperial (TRUE) or metric (FALSE)
954   */
955  static          gboolean
956  convert_qth_altitude(GtkTreeModel * model,
957                       GtkTreePath * path, GtkTreeIter * iter, gpointer data)
958  {
959      gint            alti;
960      GtkTreeViewColumn *column;
961      gchar          *title;
962  
963      (void)path;
964  
965      /* first, get the current altitude and other data */
966      gtk_tree_model_get(model, iter, QTH_LIST_COL_ALT, &alti, -1);
967      column = gtk_tree_view_get_column(GTK_TREE_VIEW(qthlist), 3);
968  
969      if (GPOINTER_TO_UINT(data))
970      {
971          /* new sys is imperial */
972          alti = M_TO_FT(alti);
973          title = g_strdup(_("Alt (ft)"));
974      }
975      else
976      {
977          /* new sys is metric */
978          alti = FT_TO_M(alti);
979          title = g_strdup(_("Alt (m)"));
980      }
981  
982      /* store new value */
983      gtk_list_store_set(GTK_LIST_STORE(model), iter,
984                         QTH_LIST_COL_ALT, alti, -1);
985      /* update column title */
986      gtk_tree_view_column_set_title(column, title);
987      g_free(title);
988  
989      return FALSE;
990  }