/ src / sat-pref-qth-editor.c
sat-pref-qth-editor.c
  1  /*
  2    Gpredict: Real-time satellite tracking and orbit prediction program
  3  
  4    Copyright (C)  2001-2017  Alexandru Csete, OZ9AEC
  5                        2011  Charles Suprin, AA1VS
  6   
  7    This program is free software; you can redistribute it and/or modify
  8    it under the terms of the GNU General Public License as published by
  9    the Free Software Foundation; either version 2 of the License, or
 10    (at your option) any later version.
 11    
 12    This program is distributed in the hope that it will be useful,
 13    but WITHOUT ANY WARRANTY; without even the implied warranty of
 14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 15    GNU General Public License for more details.
 16    
 17    You should have received a copy of the GNU General Public License
 18    along with this program; if not, visit http://www.fsf.org/
 19  */
 20  
 21  /*
 22   * Edit ground station details.
 23   *
 24   * The functions in this unit are used to edit the details of a given
 25   * ground station. The editor consists of a simple dialog window containing
 26   * widget for the individual setting.
 27   *
 28   * The functions can be used for editing the details of an existing ground
 29   * station or for adding a new location. In the first case, the widgets
 30   * in dialog will be preloaded with the existing data, while in the second
 31   * case the widgets will be empty and, upon successful completion a new
 32   * entry will be added to the GtkTreeView which contains the locations.
 33   */
 34  
 35  #ifdef HAVE_CONFIG_H
 36  #include <build-config.h>
 37  #endif
 38  #include <glib/gi18n.h>
 39  #include <glib/gstdio.h>
 40  #include <gtk/gtk.h>
 41  #include <math.h>
 42  
 43  #include "gpredict-utils.h"
 44  #include "loc-tree.h"
 45  #include "locator.h"
 46  #include "sat-cfg.h"
 47  #include "sat-log.h"
 48  #include "sat-pref-qth-data.h"
 49  #include "sat-pref-qth-editor.h"
 50  
 51  #define MAX_ALT 5000           /* maximum altitude for QTH in m */
 52  
 53  /*
 54   * Symbolic refs to be used when calling select_location in order
 55   * to determine which mode the selection should run in, ie.
 56   * select location or select weather station.
 57   */
 58  enum {
 59      SELECTION_MODE_LOC = 1,
 60      SELECTION_MODE_WX = 2
 61  };
 62  
 63  extern GtkWidget *window;       /* dialog window defined in sat-pref.c */
 64  
 65  static GtkWidget *dialog;       /* dialog window */
 66  static GtkWidget *name;         /* QTH name */
 67  static GtkWidget *location;     /* QTH location */
 68  static GtkWidget *desc;         /* QTH description */
 69  static GtkWidget *lat, *lon, *alt;      /* LAT, LON and ALT */
 70  static GtkWidget *ns, *ew;
 71  static GtkWidget *qra;          /* QRA locator */
 72  static gulong   latsigid, lonsigid, nssigid, ewsigid, qrasigid;
 73  static GtkWidget *wx;           /* weather station */
 74  
 75  #ifdef HAS_LIBGPS
 76  static GtkWidget *type;         /* GPSD type */
 77  static GtkWidget *server;       /* GPSD Server */
 78  static GtkWidget *port;         /* GPSD Port */
 79  #endif
 80  
 81  
 82  /* Update widgets from the currently selected row in the treeview */
 83  static void update_widgets(GtkTreeView * treeview)
 84  {
 85      GtkTreeSelection *selection;        /* the selection in the tree view */
 86      GtkTreeModel   *model;      /* the tree model corresponding to the selection */
 87      GtkTreeIter     iter;       /* the iter of the selection */
 88      gchar          *qthname;    /* location name */
 89      gchar          *qthdesc;    /* location description */
 90      gchar          *qthloc;     /* location */
 91      gdouble         qthlat;     /* latitude */
 92      gdouble         qthlon;     /* longitude */
 93      guint           qthalt;     /* altitude */
 94      gchar          *qthwx;      /* weather station */
 95      gchar          *qthgpsdserver;      /* gpsdserver */
 96      guint           qthtype;    /* type */
 97      guint           qthgpsdport;        /* gpsdport */
 98  
 99      selection = gtk_tree_view_get_selection(treeview);
100      if (gtk_tree_selection_get_selected(selection, &model, &iter))
101      {
102          /* get values */
103          gtk_tree_model_get(model, &iter,
104                             QTH_LIST_COL_NAME, &qthname,
105                             QTH_LIST_COL_LOC, &qthloc,
106                             QTH_LIST_COL_DESC, &qthdesc,
107                             QTH_LIST_COL_LAT, &qthlat,
108                             QTH_LIST_COL_LON, &qthlon,
109                             QTH_LIST_COL_ALT, &qthalt,
110                             QTH_LIST_COL_WX, &qthwx,
111                             QTH_LIST_COL_GPSD_SERVER, &qthgpsdserver,
112                             QTH_LIST_COL_GPSD_PORT, &qthgpsdport,
113                             QTH_LIST_COL_TYPE, &qthtype, -1);
114  
115          /* update widgets and free memory afterwards */
116          if (qthname)
117          {
118              gtk_entry_set_text(GTK_ENTRY(name), qthname);
119          }
120  
121          if (qthloc)
122          {
123              gtk_entry_set_text(GTK_ENTRY(location), qthloc);
124              g_free(qthloc);
125          }
126  
127          if (qthdesc)
128          {
129              gtk_entry_set_text(GTK_ENTRY(desc), qthdesc);
130              g_free(qthdesc);
131          }
132  
133          if (qthwx)
134          {
135              gtk_entry_set_text(GTK_ENTRY(wx), qthwx);
136              g_free(qthwx);
137          }
138  #ifdef HAS_LIBGPS
139          if (qthgpsdserver)
140          {
141              gtk_entry_set_text(GTK_ENTRY(server), qthgpsdserver);
142              g_free(qthgpsdserver);
143          }
144  #endif
145          if (qthlat < 0.00)
146              gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 1);
147          else
148              gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 0);
149  
150          gtk_spin_button_set_value(GTK_SPIN_BUTTON(lat), fabs(qthlat));
151  
152          if (qthlon < 0.00)
153              gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 1);
154          else
155              gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 0);
156  
157          gtk_spin_button_set_value(GTK_SPIN_BUTTON(lon), fabs(qthlon));
158  
159          gtk_spin_button_set_value(GTK_SPIN_BUTTON(alt), qthalt);
160  #ifdef HAS_LIBGPS
161          gtk_spin_button_set_value(GTK_SPIN_BUTTON(port), qthgpsdport);
162          gtk_combo_box_set_active(GTK_COMBO_BOX(type), qthtype);
163  #endif
164  
165          sat_log_log(SAT_LOG_LEVEL_DEBUG,
166                      _("%s:%d: Loaded %s for editing:\n"
167                        "LAT:%.4f LON:%.4f ALT:%d"),
168                      __FILE__, __LINE__,
169                      (qthname != NULL) ? qthname : "???",
170                      qthlat, qthlon, qthalt);
171  
172          g_free(qthname);
173  
174      }
175      else
176      {
177          sat_log_log(SAT_LOG_LEVEL_ERROR,
178                      _("%s:%d: No ground station selected!"),
179                      __FILE__, __LINE__);
180      }
181  }
182  
183  /**
184   * Clear the contents of all widgets.
185   *
186   * This function is usually called when the user clicks on the CLEAR button
187   *
188   */
189  static void clear_widgets()
190  {
191      gtk_entry_set_text(GTK_ENTRY(name), "");
192      gtk_entry_set_text(GTK_ENTRY(location), "");
193      gtk_entry_set_text(GTK_ENTRY(desc), "");
194      gtk_entry_set_text(GTK_ENTRY(wx), "");
195      gtk_spin_button_set_value(GTK_SPIN_BUTTON(lat), 0.0);
196      gtk_spin_button_set_value(GTK_SPIN_BUTTON(lon), 0.0);
197      gtk_spin_button_set_value(GTK_SPIN_BUTTON(alt), 0);
198      gtk_entry_set_text(GTK_ENTRY(qra), "");
199  }
200  
201  /**
202   * Apply changes.
203   * @return TRUE if things are ok, FALSE otherwise.
204   *
205   * This function is usually called when the user clicks the OK button.
206   */
207  static gboolean apply_changes(GtkTreeView * treeview, gboolean new)
208  {
209      GtkTreeSelection *selection;        /* selection in the treeview */
210      GtkTreeModel   *model;      /* the tree model corresponding to the selection */
211      GtkListStore   *liststore;  /* the list store corresponding to the model */
212      GtkTreeIter     iter;       /* iter used to add and modify row data */
213      const gchar    *qthname;
214      const gchar    *qthloc;
215      const gchar    *qthdesc;
216      const gchar    *qthwx;
217      const gchar    *qthgpsdserver;
218      gdouble         qthlat;
219      gdouble         qthlon;
220      guint           qthalt;
221      guint           qthtype;
222      guint           qthgpsdport;
223      const gchar    *qthqra;
224  
225      /* get values from dialog box */
226      qthname = gtk_entry_get_text(GTK_ENTRY(name));
227      qthloc = gtk_entry_get_text(GTK_ENTRY(location));
228      qthdesc = gtk_entry_get_text(GTK_ENTRY(desc));
229      qthwx = gtk_entry_get_text(GTK_ENTRY(wx));
230  
231  #ifdef HAS_LIBGPS
232      qthgpsdserver = gtk_entry_get_text(GTK_ENTRY(server));
233  #else
234      qthgpsdserver = "";
235  #endif
236  
237      qthlat = gtk_spin_button_get_value(GTK_SPIN_BUTTON(lat));
238      if (gtk_combo_box_get_active(GTK_COMBO_BOX(ns)))
239          qthlat = -qthlat;
240  
241      qthlon = gtk_spin_button_get_value(GTK_SPIN_BUTTON(lon));
242      if (gtk_combo_box_get_active(GTK_COMBO_BOX(ew)))
243          qthlon = -qthlon;
244  
245      qthalt = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(alt));
246  
247  #ifdef HAS_LIBGPS
248      qthgpsdport = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(port));
249      qthtype = gtk_combo_box_get_active(GTK_COMBO_BOX(type));
250  #else
251      qthgpsdport = 0;
252      qthtype = 0;                // FIXME: should probably use a #define
253  #endif
254  
255      /* get liststore */
256      liststore = GTK_LIST_STORE(gtk_tree_view_get_model(treeview));
257  
258      /* if this is a new entry, insert row into model */
259      if (new == TRUE)
260      {
261          gtk_list_store_append(liststore, &iter);
262      }
263      /* otherwise get current selection */
264      else
265      {
266          selection = gtk_tree_view_get_selection(treeview);
267  
268          if (gtk_tree_selection_get_selected(selection, &model, &iter))
269          {
270              liststore = GTK_LIST_STORE(model);
271          }
272          else
273          {
274              /* no selection; internal error */
275              sat_log_log(SAT_LOG_LEVEL_ERROR,
276                          _("%s:%d: Oooops, gpredict encountered an internal "
277                            "error (no selection in qth list)"), __FILE__,
278                          __LINE__);
279  
280              return FALSE;
281          }
282      }
283  
284      /* update values */
285      gtk_list_store_set(liststore, &iter,
286                         QTH_LIST_COL_NAME, qthname,
287                         QTH_LIST_COL_LOC, qthloc,
288                         QTH_LIST_COL_DESC, qthdesc,
289                         QTH_LIST_COL_LAT, qthlat,
290                         QTH_LIST_COL_LON, qthlon,
291                         QTH_LIST_COL_ALT, qthalt,
292                         QTH_LIST_COL_WX, qthwx,
293                         QTH_LIST_COL_GPSD_SERVER, qthgpsdserver,
294                         QTH_LIST_COL_GPSD_PORT, qthgpsdport,
295                         QTH_LIST_COL_TYPE, qthtype, -1);
296  
297      qthqra = gtk_entry_get_text(GTK_ENTRY(qra));
298      gtk_list_store_set(liststore, &iter, QTH_LIST_COL_QRA, qthqra, -1);
299  
300      return TRUE;
301  }
302  
303  /**
304   * Manage name changes.
305   *
306   * This function is called when the contents of the name entry changes.
307   * The primary purpose of this function is to check whether the char length
308   * of the name is greater than zero, if yes enable the OK button of the dialog.
309   */
310  static void name_changed(GtkWidget * widget, gpointer data)
311  {
312      const gchar    *text;
313      gchar          *entry, *end, *j;
314      gint            len, pos;
315  
316      (void)data;                 /* avoid unused parameter compiler warning */
317  
318      /* step 1: ensure that only valid characters are entered
319         (stolen from xlog, tnx pg4i)
320       */
321      entry = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
322      if ((len = g_utf8_strlen(entry, -1)) > 0)
323      {
324          end = entry + g_utf8_strlen(entry, -1);
325          for (j = entry; j < end; ++j)
326          {
327              if (!gpredict_legal_char(*j))
328              {
329                  gdk_display_beep(gdk_display_get_default());
330                  pos = gtk_editable_get_position(GTK_EDITABLE(widget));
331                  gtk_editable_delete_text(GTK_EDITABLE(widget), pos, pos + 1);
332              }
333          }
334      }
335  
336      /* step 2: if name seems all right, enable OK button */
337      text = gtk_entry_get_text(GTK_ENTRY(widget));
338  
339      if (g_utf8_strlen(text, -1) > 0)
340      {
341          gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
342                                            GTK_RESPONSE_OK, TRUE);
343      }
344      else
345      {
346          gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
347                                            GTK_RESPONSE_OK, FALSE);
348      }
349  }
350  
351  /**
352   * Manage SELECT button clicks.
353   *
354   * This function is called when the user clicks on one of the SELECT buttons.
355   * the data parameter contains information about which button has been clicked.
356   */
357  static void select_location(GtkWidget * widget, gpointer data)
358  {
359      guint           mode = GPOINTER_TO_UINT(data);
360      guint           flags;
361      gchar          *qthloc;
362      gchar          *qthwx;
363      gfloat          qthlat;
364      gfloat          qthlon;
365      guint           qthalt;
366      gboolean        selected = FALSE;
367  
368      (void)widget;               /* avoid unused parameter compiler warning */
369  
370      switch (mode)
371      {
372          /* We distinguish only between WX mode and "everything else".
373             Although a value != 1 or 2 is definitely a bug, we need to
374             have some sensible fall-back.
375           */
376      case SELECTION_MODE_WX:
377          flags = TREE_COL_FLAG_NAME | TREE_COL_FLAG_WX;
378          break;
379  
380      default:
381          flags = TREE_COL_FLAG_NAME |
382              TREE_COL_FLAG_LAT |
383              TREE_COL_FLAG_LON | TREE_COL_FLAG_ALT | TREE_COL_FLAG_WX;
384  
385          mode = SELECTION_MODE_LOC;
386          break;
387      }
388  
389      selected =
390          loc_tree_create(NULL, flags, &qthloc, &qthlat, &qthlon, &qthalt,
391                          &qthwx);
392  
393      if (selected)
394      {
395          /* update widgets */
396          switch (mode)
397          {
398  
399          case SELECTION_MODE_WX:
400              gtk_entry_set_text(GTK_ENTRY(wx), qthwx);
401              break;
402  
403          case SELECTION_MODE_LOC:
404              gtk_entry_set_text(GTK_ENTRY(location), qthloc);
405              gtk_entry_set_text(GTK_ENTRY(wx), qthwx);
406  
407              gtk_spin_button_set_value(GTK_SPIN_BUTTON(lat),
408                                        (gdouble) fabs(qthlat));
409              if (qthlat < 0.00)
410                  gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 1);
411              else
412                  gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 0);
413  
414              gtk_spin_button_set_value(GTK_SPIN_BUTTON(lon),
415                                        (gdouble) fabs(qthlon));
416              if (qthlon < 0.00)
417                  gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 1);
418              else
419                  gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 0);
420  
421              gtk_spin_button_set_value(GTK_SPIN_BUTTON(alt), qthalt);
422  
423              break;
424  
425          default:
426                 /*** FIXME: add some error reporting */
427              break;
428          }
429  
430          /* free some memory */
431          g_free(qthloc);
432          g_free(qthwx);
433      }
434  
435      /* else do nothing; we are finished */
436  }
437  
438  /**
439   * Manage coordinate changes.
440   *
441   * This function is called when the qth coordinates change. The change can
442   * be either one of the spin buttons or the combo boxes. It reads the
443   * coordinates and the calculates the new Maidenhead locator square.
444   */
445  static void latlon_changed(GtkWidget * widget, gpointer data)
446  {
447      gchar          *locator;
448      gint            retcode;
449      gdouble         latf, lonf;
450  
451      (void)widget;               /* avoid unused parameter compiler warning */
452      (void)data;                 /* avoid unused parameter compiler warning */
453  
454      locator = g_try_malloc(7);
455  
456      /* no need to check locator != NULL, since hamlib func will do it for us
457         and return RIGEINVAL
458       */
459      lonf = gtk_spin_button_get_value(GTK_SPIN_BUTTON(lon));
460      latf = gtk_spin_button_get_value(GTK_SPIN_BUTTON(lat));
461  
462      /* set the correct sign */
463      if (gtk_combo_box_get_active(GTK_COMBO_BOX(ns)))
464      {
465          /* index 1 => South */
466          latf = -latf;
467      }
468  
469      if (gtk_combo_box_get_active(GTK_COMBO_BOX(ew)))
470      {
471          /* index 1 => Wesr */
472          lonf = -lonf;
473      }
474  
475      retcode = longlat2locator(lonf, latf, locator, 3);
476  
477      if (retcode == RIG_OK)
478      {
479          /* debug message */
480          sat_log_log(SAT_LOG_LEVEL_DEBUG,
481                      _("%s:%s: %.2f %.2f => %s"),
482                      __FILE__, __func__,
483                      gtk_spin_button_get_value(GTK_SPIN_BUTTON(lon)),
484                      gtk_spin_button_get_value(GTK_SPIN_BUTTON(lat)), locator);
485  
486          g_signal_handler_block(qra, qrasigid);
487  
488          gtk_entry_set_text(GTK_ENTRY(qra), locator);
489  
490          g_signal_handler_unblock(qra, qrasigid);
491      }
492      else
493      {
494          /* send an error message and don't update */
495          sat_log_log(SAT_LOG_LEVEL_ERROR,
496                      _("%s:%d: Error converting lon/lat to locator"),
497                      __FILE__, __LINE__);
498      }
499  
500  
501      if (locator)
502          g_free(locator);
503  }
504  
505  /**
506   * Manage locator changes.
507   *
508   * This function is called when the Maidenhead locator is changed.
509   * It will calculate the new coordinates and update the spin butrtons and
510   * the combo boxes.
511   */
512  static void qra_changed(GtkEntry * entry, gpointer data)
513  {
514      gint            retcode;
515      gdouble         latf, lonf;
516      gchar          *msg;
517  
518      (void)entry;                /* avoid unused parameter compiler warning */
519      (void)data;                 /* avoid unused parameter compiler warning */
520  
521      retcode =
522          locator2longlat(&lonf, &latf, gtk_entry_get_text(GTK_ENTRY(qra)));
523  
524      if (retcode == RIG_OK)
525      {
526  
527          /* debug message */
528          sat_log_log(SAT_LOG_LEVEL_DEBUG,
529                      _("%s:%s: %s => %.2f %.2f"),
530                      __FILE__, __func__,
531                      gtk_entry_get_text(GTK_ENTRY(qra)), lonf, latf);
532  
533          /* block signal emissions for lat/lon widgets */
534          g_signal_handler_block(lat, latsigid);
535          g_signal_handler_block(lon, lonsigid);
536          g_signal_handler_block(ns, nssigid);
537          g_signal_handler_block(ew, ewsigid);
538          g_signal_handler_block(qra, qrasigid);
539  
540          /* update widgets */
541          if (latf < 0.00)
542              gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 1);
543          else
544              gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 0);
545  
546          if (lonf < 0.00)
547              gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 1);
548          else
549              gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 0);
550  
551          gtk_spin_button_set_value(GTK_SPIN_BUTTON(lat), fabs(latf));
552          gtk_spin_button_set_value(GTK_SPIN_BUTTON(lon), fabs(lonf));
553  
554          /* make sure text is upper case */
555          msg = g_ascii_strup(gtk_entry_get_text(GTK_ENTRY(qra)), -1);
556          gtk_entry_set_text(GTK_ENTRY(qra), msg);
557          g_free(msg);
558  
559          /* unblock signal emissions */
560          g_signal_handler_unblock(lat, latsigid);
561          g_signal_handler_unblock(lon, lonsigid);
562          g_signal_handler_unblock(ns, nssigid);
563          g_signal_handler_unblock(ew, ewsigid);
564          g_signal_handler_unblock(qra, qrasigid);
565  
566      }
567      else
568      {
569          /* send an error message and don't update */
570          sat_log_log(SAT_LOG_LEVEL_ERROR,
571                      _("%s:%d: Invalid locator: %s"),
572                      __FILE__, __LINE__, gtk_entry_get_text(GTK_ENTRY(qra)));
573      }
574  
575  }
576  
577  static GtkWidget *create_editor_widgets(GtkTreeView * treeview, gboolean new)
578  {
579      GtkWidget      *table;
580      GtkWidget      *label;
581      GtkWidget      *locbut;
582      GtkWidget      *wxbut;
583  
584      table = gtk_grid_new();
585      gtk_grid_set_column_homogeneous(GTK_GRID(table), FALSE);
586      gtk_grid_set_row_homogeneous(GTK_GRID(table), FALSE);
587      gtk_grid_set_column_spacing(GTK_GRID(table), 5);
588      gtk_grid_set_row_spacing(GTK_GRID(table), 5);
589      gtk_container_set_border_width(GTK_CONTAINER(table), 5);
590  
591      /* QTH name */
592      label = gtk_label_new(_("Name"));
593      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
594      gtk_grid_attach(GTK_GRID(table), label, 0, 0, 1, 1);
595  
596      name = gtk_entry_new();
597      gtk_entry_set_max_length(GTK_ENTRY(name), 25);
598      gtk_widget_set_tooltip_text(name,
599                                  _
600                                  ("Enter a short name for this ground station, "
601                                   "e.g. callsign.\n"
602                                   "Allowed characters: 0..9, a..z, A..Z, - and _"));
603      gtk_grid_attach(GTK_GRID(table), name, 1, 0, 3, 1);
604  
605      /* attach changed signal so that we can enable OK button when
606         a proper name has been entered
607       */
608      g_signal_connect(name, "changed", G_CALLBACK(name_changed), NULL);
609  
610      /* QTH description */
611      label = gtk_label_new(_("Description"));
612      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
613      gtk_grid_attach(GTK_GRID(table), label, 0, 1, 1, 1);
614  
615      desc = gtk_entry_new();
616      gtk_entry_set_max_length(GTK_ENTRY(desc), 256);
617      gtk_widget_set_tooltip_text(desc,
618                                  _("Enter an optional description for this"
619                                    " ground station."));
620      gtk_grid_attach(GTK_GRID(table), desc, 1, 1, 3, 1);
621  
622      /* location */
623      label = gtk_label_new(_("Location"));
624      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
625      gtk_grid_attach(GTK_GRID(table), label, 0, 2, 1, 1);
626  
627      location = gtk_entry_new();
628      gtk_entry_set_max_length(GTK_ENTRY(location), 50);
629      gtk_widget_set_tooltip_text(location,
630                                  _("Optional location of the ground station, "
631                                    "e.g. Copenhagen, Denmark."));
632      gtk_grid_attach(GTK_GRID(table), location, 1, 2, 2, 1);
633  
634      locbut = gtk_button_new_with_label(_("Select"));
635      gtk_widget_set_tooltip_text(locbut, _("Select a location from a list"));
636      g_signal_connect(locbut, "clicked", G_CALLBACK(select_location),
637                       GUINT_TO_POINTER(SELECTION_MODE_LOC));
638      gtk_grid_attach(GTK_GRID(table), locbut, 3, 2, 1, 1);
639  
640  
641      /* latitude */
642      label = gtk_label_new(_("Latitude (\302\260)"));
643      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
644      gtk_grid_attach(GTK_GRID(table), label, 0, 3, 1, 1);
645  
646      lat = gtk_spin_button_new_with_range(0.00, 90.00, 0.0001);
647      gtk_spin_button_set_increments(GTK_SPIN_BUTTON(lat), 0.0001, 1.0);
648      gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(lat), TRUE);
649      gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lat), 4);
650      gtk_widget_set_tooltip_text(lat,
651                                  _("Select the latitude of the ground station "
652                                    "in decimal degrees."));
653      gtk_grid_attach(GTK_GRID(table), lat, 1, 3, 1, 1);
654  
655      ns = gtk_combo_box_text_new();
656      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ns), _("North"));
657      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ns), _("South"));
658      gtk_combo_box_set_active(GTK_COMBO_BOX(ns), 0);
659      gtk_grid_attach(GTK_GRID(table), ns, 2, 3, 1, 1);
660  
661      /* longitude */
662      label = gtk_label_new(_("Longitude (\302\260)"));
663      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
664      gtk_grid_attach(GTK_GRID(table), label, 0, 4, 1, 1);
665  
666      lon = gtk_spin_button_new_with_range(0.00, 180.00, 0.0001);
667      gtk_spin_button_set_increments(GTK_SPIN_BUTTON(lon), 0.0001, 1.0);
668      gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(lon), TRUE);
669      gtk_spin_button_set_digits(GTK_SPIN_BUTTON(lon), 4);
670      gtk_widget_set_tooltip_text(lon,
671                                  _("Select the longitude of the ground station "
672                                    "in decimal degrees."));
673      gtk_grid_attach(GTK_GRID(table), lon, 1, 4, 1, 1);
674  
675      ew = gtk_combo_box_text_new();
676      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ew), _("East"));
677      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(ew), _("West"));
678      gtk_combo_box_set_active(GTK_COMBO_BOX(ew), 0);
679      gtk_grid_attach(GTK_GRID(table), ew, 2, 4, 1, 1);
680  
681      /* connect lat/lon spinners and combos to callback
682         remember signal id so that we can block signals
683         while doing automatic cross-updates
684       */
685      latsigid = g_signal_connect(lat, "value-changed",
686                                  G_CALLBACK(latlon_changed), NULL);
687      lonsigid = g_signal_connect(lon, "value-changed",
688                                  G_CALLBACK(latlon_changed), NULL);
689      nssigid = g_signal_connect(ns, "changed",
690                                 G_CALLBACK(latlon_changed), NULL);
691      ewsigid = g_signal_connect(ew, "changed",
692                                 G_CALLBACK(latlon_changed), NULL);
693  
694      /* QRA locator */
695      label = gtk_label_new(_("Locator"));
696      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
697      gtk_grid_attach(GTK_GRID(table), label, 0, 5, 1, 1);
698  
699      qra = gtk_entry_new();
700      gtk_entry_set_max_length(GTK_ENTRY(qra), 6);
701      gtk_widget_set_tooltip_text(qra, _("Maidenhead locator grid."));
702      qrasigid = g_signal_connect(qra, "changed", G_CALLBACK(qra_changed), NULL);
703      gtk_grid_attach(GTK_GRID(table), qra, 1, 5, 1, 1);
704  
705      /* altitude */
706      label = gtk_label_new(_("Altitude"));
707      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
708      gtk_grid_attach(GTK_GRID(table), label, 0, 6, 1, 1);
709  
710      if (sat_cfg_get_bool(SAT_CFG_BOOL_USE_IMPERIAL))
711          alt = gtk_spin_button_new_with_range(0, M_TO_FT(MAX_ALT), 1);
712      else
713          alt = gtk_spin_button_new_with_range(0, MAX_ALT, 1);
714  
715      gtk_spin_button_set_increments(GTK_SPIN_BUTTON(alt), 1, 100);
716      gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(alt), TRUE);
717      gtk_widget_set_tooltip_text(alt,
718                                  _("Select the altitude of the ground station "
719                                    "in meters or feet "
720                                    "depending on your settings"));
721      gtk_grid_attach(GTK_GRID(table), alt, 1, 6, 1, 1);
722  
723      if (sat_cfg_get_bool(SAT_CFG_BOOL_USE_IMPERIAL))
724          label = gtk_label_new(_("ft ASL"));
725      else
726          label = gtk_label_new(_("m ASL"));
727  
728      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
729      gtk_grid_attach(GTK_GRID(table), label, 2, 6, 1, 1);
730  
731      /* weather station */
732      label = gtk_label_new(_("Weather St"));
733      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
734      gtk_grid_attach(GTK_GRID(table), label, 0, 7, 1, 1);
735  
736      wx = gtk_entry_new();
737      gtk_entry_set_max_length(GTK_ENTRY(wx), 4);
738      gtk_widget_set_tooltip_text(wx, _("Four letter code for weather station"));
739      gtk_grid_attach(GTK_GRID(table), wx, 1, 7, 2, 1);
740  
741      wxbut = gtk_button_new_with_label(_("Select"));
742      gtk_widget_set_tooltip_text(wxbut, _("Select a weather station"));
743      g_signal_connect(wxbut, "clicked", G_CALLBACK(select_location),
744                       GUINT_TO_POINTER(SELECTION_MODE_WX));
745      gtk_grid_attach(GTK_GRID(table), wxbut, 3, 7, 1, 1);
746  
747  #ifdef HAS_LIBGPS
748      /* GPSD enabled */
749      label = gtk_label_new(_("QTH Type"));
750      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
751      gtk_grid_attach(GTK_GRID(table), label, 0, 8, 1, 1);
752  
753      type = gtk_combo_box_text_new();
754      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type), _("Static"));
755      gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(type), _("GPSD"));
756      gtk_combo_box_set_active(GTK_COMBO_BOX(type), 0);
757      gtk_widget_set_tooltip_text(type,
758                                  _("A qth can be static, ie. it does not "
759                                    "change, or gpsd based for computers with "
760                                    "gps attached."));
761      gtk_grid_attach(GTK_GRID(table), type, 1, 8, 1, 1);
762  
763      /* GPSD Server */
764      label = gtk_label_new(_("GPSD Server"));
765      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
766      gtk_grid_attach(GTK_GRID(table), label, 0, 9, 1, 1);
767  
768      server = gtk_entry_new();
769      gtk_entry_set_max_length(GTK_ENTRY(server), 6000);
770      gtk_widget_set_tooltip_text(server, _("GPSD Server."));
771      gtk_grid_attach(GTK_GRID(table), server, 1, 9, 1, 1);
772  
773      /* GPSD Port */
774      label = gtk_label_new(_("GPSD Port"));
775      g_object_set(label, "xalign", 0.0f, "yalign", 0.5f, NULL);
776      gtk_grid_attach(GTK_GRID(table), label, 0, 10, 1, 1);
777  
778      port = gtk_spin_button_new_with_range(0, 32768, 1);
779      gtk_spin_button_set_increments(GTK_SPIN_BUTTON(port), 1, 100);
780      gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(port), TRUE);
781      gtk_widget_set_tooltip_text(port,
782                                  _("Set the port for GPSD to use. Default for "
783                                    "gpsd is 2947."));
784      gtk_grid_attach(GTK_GRID(table), port, 1, 10, 1, 1);
785  #endif
786  
787      if (!new)
788          update_widgets(treeview);
789  
790      gtk_widget_show_all(table);
791  
792      return table;
793  }
794  
795  /*
796   * Add or edit a QTH entry.
797   *
798   * The parameter new is used to indicate whether a new entry should be
799   * created or just edit the one selected in the treeview.
800   */
801  void sat_pref_qth_editor_run(GtkTreeView * treeview, gboolean new)
802  {
803      gint            response;
804      gboolean        finished = FALSE;
805  
806      /* create dialog and add contents */
807      dialog = gtk_dialog_new_with_buttons(_("Edit ground station data"),
808                                           GTK_WINDOW(window),
809                                           GTK_DIALOG_MODAL |
810                                           GTK_DIALOG_DESTROY_WITH_PARENT,
811                                           "_Clear", GTK_RESPONSE_REJECT,
812                                           "_Cancel", GTK_RESPONSE_CANCEL,
813                                           "_OK", GTK_RESPONSE_OK, NULL);
814  
815      /* disable OK button to begin with */
816      gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog),
817                                        GTK_RESPONSE_OK, FALSE);
818  
819      gtk_container_add(GTK_CONTAINER
820                        (gtk_dialog_get_content_area(GTK_DIALOG(dialog))),
821                        create_editor_widgets(treeview, new));
822  
823      /* this hacky-thing is to keep the dialog running in case the
824         CLEAR button is plressed. OK and CANCEL will exit the loop
825       */
826      while (!finished)
827      {
828          response = gtk_dialog_run(GTK_DIALOG(dialog));
829  
830          switch (response)
831          {
832          case GTK_RESPONSE_OK:
833              /* OK */
834              if (apply_changes(treeview, new))
835                  finished = TRUE;
836              else
837                  finished = FALSE;
838  
839              break;
840  
841          case GTK_RESPONSE_REJECT:
842              /* CLEAR */
843              clear_widgets();
844              break;
845  
846          default:
847              /* Everything else is considered CANCEL */
848              finished = TRUE;
849              break;
850          }
851      }
852  
853      gtk_widget_destroy(dialog);
854  }