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