gpredict-utils.c
1 /* 2 Gpredict: Real-time satellite tracking and orbit prediction program 3 4 Copyright (C) 2001-2019 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 #define _GNU_SOURCE 20 #ifdef HAVE_CONFIG_H 21 #include <build-config.h> 22 #endif 23 #include <ctype.h> 24 #include <glib/gi18n.h> 25 #include <glib/gstdio.h> 26 #include <gtk/gtk.h> 27 28 #include "compat.h" 29 #include "gpredict-utils.h" 30 #include "sat-log.h" 31 #include "strnatcmp.h" 32 33 34 35 /** 36 * Create a horizontal pixmap button. 37 * 38 * The text will be placed to the right of the image. 39 * file is only the icon name, not the full path. 40 */ 41 GtkWidget *gpredict_hpixmap_button(const gchar * file, const gchar * text, 42 const gchar * tooltip) 43 { 44 GtkWidget *button; 45 GtkWidget *image; 46 GtkWidget *box; 47 gchar *path; 48 49 path = icon_file_name(file); 50 image = gtk_image_new_from_file(path); 51 g_free(path); 52 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 53 gtk_box_set_homogeneous(GTK_BOX(box), FALSE); 54 gtk_box_pack_start(GTK_BOX(box), image, TRUE, TRUE, 0); 55 if (text != NULL) 56 gtk_box_pack_start(GTK_BOX(box), gtk_label_new(text), TRUE, TRUE, 0); 57 58 button = gtk_button_new(); 59 gtk_widget_set_tooltip_text(button, tooltip); 60 gtk_container_add(GTK_CONTAINER(button), box); 61 62 return button; 63 } 64 65 /** 66 * Create a vertical pixmap button. 67 * 68 * The text will be placed under the image. 69 * file is only the icon name, not the full path. 70 */ 71 GtkWidget *gpredict_vpixmap_button(const gchar * file, const gchar * text, 72 const gchar * tooltip) 73 { 74 GtkWidget *button; 75 GtkWidget *image; 76 GtkWidget *box; 77 gchar *path; 78 79 path = icon_file_name(file); 80 image = gtk_image_new_from_file(path); 81 g_free(path); 82 box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 83 gtk_box_set_homogeneous(GTK_BOX(box), FALSE); 84 gtk_box_pack_start(GTK_BOX(box), image, TRUE, TRUE, 0); 85 if (text != NULL) 86 gtk_box_pack_start(GTK_BOX(box), gtk_label_new(text), TRUE, TRUE, 0); 87 88 button = gtk_button_new(); 89 gtk_widget_set_tooltip_text(button, tooltip); 90 gtk_container_add(GTK_CONTAINER(button), box); 91 92 return button; 93 } 94 95 /** 96 * Create a horizontal pixmap button using stock pixmap. 97 * 98 * The text will be placed to the right of the image. 99 * The icon size will be GTK_ICON_SIZE_BUTTON. 100 */ 101 GtkWidget *gpredict_hstock_button(const gchar * stock_id, 102 const gchar * text, 103 const gchar * tooltip) 104 { 105 GtkWidget *button; 106 GtkWidget *image; 107 GtkWidget *box; 108 109 image = gtk_image_new_from_icon_name(stock_id, GTK_ICON_SIZE_BUTTON); 110 box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 111 gtk_box_set_homogeneous(GTK_BOX(box), FALSE); 112 gtk_box_pack_start(GTK_BOX(box), image, TRUE, TRUE, 0); 113 if (text != NULL) 114 gtk_box_pack_start(GTK_BOX(box), gtk_label_new(text), TRUE, TRUE, 0); 115 116 button = gtk_button_new(); 117 gtk_widget_set_tooltip_text(button, tooltip); 118 gtk_container_add(GTK_CONTAINER(button), box); 119 120 return button; 121 } 122 123 /** 124 * Create and set tooltips for GtkComboBox. 125 * 126 * @param text Pointer to the desired tooltip text. 127 * 128 * This function creates and sets the tooltips for the specified widget. 129 * This function is called by the \a grig_set_combo_tooltips function which 130 * is must be used as callback for the "realize" signal of the GtkComboBox. 131 */ 132 static void set_combo_tooltip(GtkWidget * combo, gpointer text) 133 { 134 /* if current child is a button we have BINGO! */ 135 if (GTK_IS_BUTTON(combo)) 136 gtk_widget_set_tooltip_text(combo, text); 137 } 138 139 /** 140 * Create and set tooltips for GtkComboBox. 141 * 142 * @param combo The GtkComboBox widget. 143 * @param text Pointer to the desired tooltip text. 144 * 145 * This function creates and sets the tooltips for the specified widget. 146 * The interface is implemented such that this function can be connected 147 * directly to the @a realized signal of the GtkComboBox. 148 * 149 * Actually, this function only loops over all the children of the GtkComboBox 150 * and calls the set_combo_tooltip internal function. 151 * 152 * @note This works only if the function is actually used as callback for the 153 * @a realize signal og the GtkComboBox. 154 * 155 * @note This great trick has been pointed out by Matthias Clasen, he has done the 156 * the same for the filter combo in the new GtkFileChooser 157 * ref: gtkfilechooserdefault.c:3151 in Gtk+ 2.5.5 158 */ 159 void gpredict_set_combo_tooltips(GtkWidget * combo, gpointer text) 160 { 161 /* for each child in the container call the internal 162 function which actually creates the tooltips. 163 */ 164 gtk_container_forall(GTK_CONTAINER(combo), set_combo_tooltip, text); 165 166 } 167 168 gint gpredict_file_copy(const gchar * in, const gchar * out) 169 { 170 gchar *contents; 171 gboolean status = 0; 172 GError *err = NULL; 173 gsize ulen; 174 gssize slen; 175 176 g_return_val_if_fail(in != NULL, 1); 177 g_return_val_if_fail(out != NULL, 1); 178 179 /* read source file */ 180 if (!g_file_get_contents(in, &contents, &ulen, &err)) 181 { 182 sat_log_log(SAT_LOG_LEVEL_ERROR, "%s: %s", __func__, err->message); 183 g_clear_error(&err); 184 185 status = 1; 186 } 187 else 188 { 189 /* write contents to new file */ 190 slen = (gssize) ulen; 191 192 if (!g_file_set_contents(out, contents, slen, &err)) 193 { 194 sat_log_log(SAT_LOG_LEVEL_ERROR, "%s: %s", __func__, err->message); 195 g_clear_error(&err); 196 197 status = 1; 198 } 199 200 g_free(contents); 201 } 202 203 return status; 204 } 205 206 /** 207 * Create a miniature pixmap button with no relief. 208 * 209 * Pixmapfile is only the icon name, not the full path. 210 */ 211 GtkWidget *gpredict_mini_mod_button(const gchar * pixmapfile, 212 const gchar * tooltip) 213 { 214 GtkWidget *button; 215 GtkWidget *image; 216 gchar *path; 217 218 path = icon_file_name(pixmapfile); 219 image = gtk_image_new_from_file(path); 220 g_free(path); 221 222 button = gtk_button_new(); 223 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); 224 gtk_widget_set_tooltip_text(button, tooltip); 225 gtk_container_add(GTK_CONTAINER(button), image); 226 227 return button; 228 } 229 230 /** 231 * Convert a BCD colour to a GdkColor structure. 232 * 233 * @param rgb The source colour in 0xRRGGBB form. 234 * @param color Pointer to an existing GdkColor structure. 235 */ 236 void rgb2gdk(guint rgb, GdkColor * color) 237 { 238 guint16 r, g, b; 239 guint tmp; 240 241 /* sanity checks */ 242 if (color == NULL) 243 { 244 sat_log_log(SAT_LOG_LEVEL_ERROR, 245 _("%s:%s: %s called with color = NULL"), 246 __FILE__, __LINE__, __func__); 247 return; 248 } 249 250 /* red */ 251 tmp = rgb & 0xFF0000; 252 r = (guint16) (tmp >> 16); 253 254 /* green */ 255 tmp = rgb & 0x00FF00; 256 g = (guint16) (tmp >> 8); 257 258 /* blue */ 259 tmp = rgb & 0x0000FF; 260 b = (guint16) tmp; 261 262 /* store colours */ 263 color->red = 257 * r; 264 color->green = 257 * g; 265 color->blue = 257 * b; 266 } 267 268 /** 269 * Convert a BCD colour to a GdkColor structure. 270 * 271 * @param rgba The source colour in 0xRRGGBBAA form. 272 * @param color Pointer to an existing GdkColor structure. 273 * @param alpha Pointer to where the alpha channel value should be stored 274 */ 275 void rgba2gdk(guint rgba, GdkColor * color, guint16 * alpha) 276 { 277 guint16 r, g, b; 278 guint tmp; 279 280 /* sanity checks */ 281 if (color == NULL) 282 { 283 sat_log_log(SAT_LOG_LEVEL_ERROR, 284 _("%s:%s: %s called with color = NULL"), 285 __FILE__, __LINE__, __func__); 286 return; 287 } 288 if (alpha == NULL) 289 { 290 sat_log_log(SAT_LOG_LEVEL_ERROR, 291 _("%s:%s: %s called with alpha = NULL"), 292 __FILE__, __LINE__, __func__); 293 return; 294 } 295 296 /* red */ 297 tmp = rgba & 0xFF000000; 298 r = (guint16) (tmp >> 24); 299 300 /* green */ 301 tmp = rgba & 0x00FF0000; 302 g = (guint16) (tmp >> 16); 303 304 /* blue */ 305 tmp = rgba & 0x0000FF00; 306 b = (guint16) (tmp >> 8); 307 308 /* alpha channel */ 309 *alpha = (guint16) (257 * (rgba & 0x000000FF)); 310 311 /* store colours */ 312 color->red = 257 * r; 313 color->green = 257 * g; 314 color->blue = 257 * b; 315 } 316 317 /** 318 * Convert GdkColor to BCD colour. 319 * 320 * @param color The GdkColor structure. 321 * @param rgb Pointer to where the 0xRRGGBB encoded colour should be stored. 322 */ 323 void gdk2rgb(const GdkColor * color, guint * rgb) 324 { 325 guint r, g, b; 326 guint16 tmp; 327 328 /* sanity checks */ 329 if (color == NULL) 330 { 331 sat_log_log(SAT_LOG_LEVEL_ERROR, 332 _("%s:%s: %s called with color = NULL"), 333 __FILE__, __LINE__, __func__); 334 return; 335 } 336 if (rgb == NULL) 337 { 338 sat_log_log(SAT_LOG_LEVEL_ERROR, 339 _("%s:%s: %s called with rgb = NULL"), 340 __FILE__, __LINE__, __func__); 341 return; 342 } 343 344 /* red */ 345 tmp = color->red / 257; 346 r = (guint) (tmp << 16); 347 348 /* green */ 349 tmp = color->green / 257; 350 g = (guint) (tmp << 8); 351 352 /* blue */ 353 tmp = color->blue / 257; 354 b = (guint) tmp; 355 356 *rgb = (r | g | b); 357 } 358 359 /** 360 * Convert GdkColor and alpha channel to BCD colour. 361 * 362 * @param color The GdkColor structure. 363 * @param alpha The value of the alpha channel. 364 * @param rgb Pointer to where the 0xRRGGBBAA encoded colour should be stored. 365 */ 366 void gdk2rgba(const GdkColor * color, guint16 alpha, guint * rgba) 367 { 368 guint r, g, b, a; 369 guint16 tmp; 370 371 /* sanity checks */ 372 if (color == NULL) 373 { 374 sat_log_log(SAT_LOG_LEVEL_ERROR, 375 _("%s:%s: %s called with color = NULL"), 376 __FILE__, __LINE__, __func__); 377 return; 378 } 379 if (rgba == NULL) 380 { 381 sat_log_log(SAT_LOG_LEVEL_ERROR, 382 _("%s:%s: %s called with rgba = NULL"), 383 __FILE__, __LINE__, __func__); 384 return; 385 } 386 387 /* red */ 388 tmp = color->red / 257; 389 r = (guint) (tmp << 24); 390 391 /* green */ 392 tmp = color->green / 257; 393 g = (guint) (tmp << 16); 394 395 /* blue */ 396 tmp = color->blue / 257; 397 b = (guint) (tmp << 8); 398 399 /* alpha */ 400 tmp = alpha / 257; 401 a = (guint) tmp; 402 403 *rgba = (r | g | b | a); 404 } 405 406 /** 407 * Convert GdkColor to RRGGBB hex format (for Pango Markup). 408 * 409 * @param color The GdkColor structure. 410 * @return A newly allocated character string. 411 */ 412 gchar *rgba2html(guint rgba) 413 { 414 gchar *col; 415 guint8 r, g, b; 416 guint tmp; 417 418 tmp = rgba & 0xFF000000; 419 r = (guint8) (tmp >> 24); 420 421 /* green */ 422 tmp = rgba & 0x00FF0000; 423 g = (guint8) (tmp >> 16); 424 425 /* blue */ 426 tmp = rgba & 0x0000FF00; 427 b = (guint8) (tmp >> 8); 428 429 col = g_strdup_printf("%X%X%X", r, g, b); 430 431 return col; 432 } 433 434 /** 435 * String comparison function 436 * @param s1 first string 437 * @param s2 second string 438 */ 439 int gpredict_strcmp(const char *s1, const char *s2) 440 { 441 #if 0 442 gchar *a, *b; 443 int retval; 444 445 a = g_ascii_strup(s1, -1); 446 b = g_ascii_strup(s2, -1); 447 448 retval = strverscmp(a, b); 449 g_free(a); 450 g_free(b); 451 return retval; 452 #else 453 return strnatcasecmp(s1, s2); 454 #endif 455 } 456 457 /** 458 * Substring finding function 459 * 460 * @param s1 the larger string 461 * @param s2 the substring that we are searching for. 462 * @return pointer to the substring location 463 * 464 * this is a substitute for strcasestr which is a gnu c extension and not available everywhere. 465 */ 466 char *gpredict_strcasestr(const char *s1, const char *s2) 467 { 468 size_t s1_len = strlen(s1); 469 size_t s2_len = strlen(s2); 470 471 while (s1_len >= s2_len) 472 { 473 if (strncasecmp(s1, s2, s2_len) == 0) 474 return (char *)s1; 475 476 s1++; 477 s1_len--; 478 } 479 480 return NULL; 481 } 482 483 /** 484 * Save a GKeyFile structure to a file 485 * 486 * @param cfgdata is a pointer to the GKeyFile. 487 * @param filename is a pointer the filename string. 488 * @return 1 on error and zero on success. 489 * 490 */ 491 gboolean gpredict_save_key_file(GKeyFile * cfgdata, const char *filename) 492 { 493 GError *error = NULL; 494 495 if (!g_key_file_save_to_file(cfgdata, filename, &error)) 496 { 497 sat_log_log(SAT_LOG_LEVEL_ERROR, 498 _("%s: Error writing config data (%s)."), 499 __func__, 500 error != NULL ? error->message : "unknown error"); 501 g_clear_error(&error); 502 return 1; 503 } 504 505 return 0; 506 } 507 508 509 /** 510 * Check if \c ch is an alpha-num; in range \c "[0-9a-zA-F]". 511 * Or \c "ch == '-'" or \c "ch == '_'". 512 * 513 * @param ch the character code to check. 514 * @return TRUE if okay. 515 */ 516 gboolean gpredict_legal_char(int ch) 517 { 518 if (g_ascii_isalnum(ch) || ch == '-' || ch == '_') 519 return (TRUE); 520 return (FALSE); 521 } 522 523 524 /* Convert a 0xRRGGBBAA encoded config integer to a GdkRGBA structure */ 525 void rgba_from_cfg(guint cfg_rgba, GdkRGBA * gdk_rgba) 526 { 527 if (gdk_rgba == NULL) 528 { 529 sat_log_log(SAT_LOG_LEVEL_ERROR, 530 _("%s called with gdk_rgba = NULL"), __func__); 531 return; 532 } 533 534 gdk_rgba->red = ((cfg_rgba >> 24) & 0xFF) / 255.0; 535 gdk_rgba->green = ((cfg_rgba >> 16) & 0xFF) / 255.0; 536 gdk_rgba->blue = ((cfg_rgba >> 8) & 0xFF) / 255.0; 537 gdk_rgba->alpha = (cfg_rgba & 0xFF) / 255.0; 538 } 539 540 /* convert GdkRGBA struct to 0xRRGGBBAA formatted config integer */ 541 guint rgba_to_cfg(const GdkRGBA * gdk_rgba) 542 { 543 guint cfg_int = 0; 544 545 if (gdk_rgba == NULL) 546 { 547 sat_log_log(SAT_LOG_LEVEL_ERROR, 548 _("%s called with gdk_rgba = NULL"), __func__); 549 return 0; 550 } 551 552 cfg_int = ((guint) (gdk_rgba->red * 255.0)) << 24; 553 cfg_int += ((guint) (gdk_rgba->green * 255.0)) << 16; 554 cfg_int += ((guint) (gdk_rgba->blue * 255.0)) << 8; 555 cfg_int += (guint) (gdk_rgba->alpha * 255.0); 556 557 return cfg_int; 558 } 559 560 /* Convert a 0xRRGGBB encoded config integer to a GdkRGBA structure (no alpha channel) */ 561 void rgb_from_cfg(guint cfg_rgb, GdkRGBA * gdk_rgba) 562 { 563 if (gdk_rgba == NULL) 564 { 565 sat_log_log(SAT_LOG_LEVEL_ERROR, 566 _("%s called with gdk_rgba = NULL"), __func__); 567 return; 568 } 569 570 gdk_rgba->red = ((cfg_rgb >> 16) & 0xFF) / 255.0; 571 gdk_rgba->green = ((cfg_rgb >> 8) & 0xFF) / 255.0; 572 gdk_rgba->blue = (cfg_rgb & 0xFF) / 255.0; 573 gdk_rgba->alpha = 1.0; 574 } 575 576 /* convert GdkRGBA struct to 0xRRGGBB formatted config integer, ignoring the alpha channel */ 577 guint rgb_to_cfg(const GdkRGBA * gdk_rgba) 578 { 579 guint cfg_int = 0; 580 581 if (gdk_rgba == NULL) 582 { 583 sat_log_log(SAT_LOG_LEVEL_ERROR, 584 _("%s called with gdk_rgba = NULL"), __func__); 585 return 0; 586 } 587 588 cfg_int = ((guint) (gdk_rgba->red * 255.0)) << 16; 589 cfg_int += ((guint) (gdk_rgba->green * 255.0)) << 8; 590 cfg_int += (guint) (gdk_rgba->blue * 255.0); 591 592 return cfg_int; 593 }