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 }