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 #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 */
gpredict_hpixmap_button(const gchar * file,const gchar * text,const gchar * tooltip)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 */
gpredict_vpixmap_button(const gchar * file,const gchar * text,const gchar * tooltip)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 */
gpredict_hstock_button(const gchar * stock_id,const gchar * text,const gchar * tooltip)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 */
set_combo_tooltip(GtkWidget * combo,gpointer text)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 funcion 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 */
gpredict_set_combo_tooltips(GtkWidget * combo,gpointer text)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
gpredict_file_copy(const gchar * in,const gchar * out)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 */
gpredict_mini_mod_button(const gchar * pixmapfile,const gchar * tooltip)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 */
rgb2gdk(guint rgb,GdkColor * color)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 */
rgba2gdk(guint rgba,GdkColor * color,guint16 * alpha)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 */
gdk2rgb(const GdkColor * color,guint * rgb)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 */
gdk2rgba(const GdkColor * color,guint16 alpha,guint * rgba)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 */
rgba2html(guint rgba)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 */
gpredict_strcmp(const char * s1,const char * s2)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 */
gpredict_strcasestr(const char * s1,const char * s2)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 * This might one day be in glib but for now it is not a standard function.
491 * Variants of this were throughout the code and it is now consilidated here.
492 */
gpredict_save_key_file(GKeyFile * cfgdata,const char * filename)493 gboolean gpredict_save_key_file(GKeyFile * cfgdata, const char *filename)
494 {
495 gchar *datastream; /* cfgdata string */
496 gsize length; /* length of the data stream */
497 gsize written; /* number of bytes actually written */
498 gboolean err = 0; /* the error value */
499 GIOChannel *cfgfile; /* file */
500 GError *error = NULL; /* Error handler */
501
502 /* ok, go on and convert the data */
503 datastream = g_key_file_to_data(cfgdata, &length, &error);
504
505 if (error != NULL)
506 {
507 sat_log_log(SAT_LOG_LEVEL_ERROR,
508 _("%s: Could not create config data (%s)."),
509 __func__, error->message);
510
511 g_clear_error(&error);
512
513 err = 1;
514 }
515 else
516 {
517 cfgfile = g_io_channel_new_file(filename, "w", &error);
518
519 if (error != NULL)
520 {
521 sat_log_log(SAT_LOG_LEVEL_ERROR,
522 _("%s: Could not create config file (%s)."),
523 __func__, error->message);
524
525 g_clear_error(&error);
526
527 err = 1;
528 }
529 else
530 {
531 g_io_channel_write_chars(cfgfile,
532 datastream, length, &written, &error);
533
534 g_io_channel_shutdown(cfgfile, TRUE, NULL);
535 g_io_channel_unref(cfgfile);
536
537 if (error != NULL)
538 {
539 sat_log_log(SAT_LOG_LEVEL_ERROR,
540 _("%s: Error writing config data (%s)."),
541 __func__, error->message);
542
543 g_clear_error(&error);
544
545 err = 1;
546 }
547 else if (length != written)
548 {
549 sat_log_log(SAT_LOG_LEVEL_WARN,
550 _("%s: Wrote only %d out of %d chars."),
551 __func__, written, length);
552
553 err = 1;
554 }
555 else
556 {
557 sat_log_log(SAT_LOG_LEVEL_INFO,
558 _("%s: Configuration saved for %s."),
559 __func__, filename);
560
561 err = 0;
562 }
563 }
564 }
565 g_free(datastream);
566
567 return err;
568 }
569
570
571 /**
572 * Check if \c ch is an alpha-num; in range \c "[0-9a-zA-F]".
573 * Or \c "ch == '-'" or \c "ch == '_'".
574 *
575 * @param ch the character code to check.
576 * @return TRUE if okay.
577 */
gpredict_legal_char(int ch)578 gboolean gpredict_legal_char(int ch)
579 {
580 if (g_ascii_isalnum(ch) || ch == '-' || ch == '_')
581 return (TRUE);
582 return (FALSE);
583 }
584
585
586 /* Convert a 0xRRGGBBAA encoded config integer to a GdkRGBA structure */
rgba_from_cfg(guint cfg_rgba,GdkRGBA * gdk_rgba)587 void rgba_from_cfg(guint cfg_rgba, GdkRGBA * gdk_rgba)
588 {
589 if (gdk_rgba == NULL)
590 {
591 sat_log_log(SAT_LOG_LEVEL_ERROR,
592 _("%s called with gdk_rgba = NULL"), __func__);
593 return;
594 }
595
596 gdk_rgba->red = ((cfg_rgba >> 24) & 0xFF) / 255.0;
597 gdk_rgba->green = ((cfg_rgba >> 16) & 0xFF) / 255.0;
598 gdk_rgba->blue = ((cfg_rgba >> 8) & 0xFF) / 255.0;
599 gdk_rgba->alpha = (cfg_rgba & 0xFF) / 255.0;
600 }
601
602 /* convert GdkRGBA struct to 0xRRGGBBAA formatted config integer */
rgba_to_cfg(const GdkRGBA * gdk_rgba)603 guint rgba_to_cfg(const GdkRGBA * gdk_rgba)
604 {
605 guint cfg_int = 0;
606
607 if (gdk_rgba == NULL)
608 {
609 sat_log_log(SAT_LOG_LEVEL_ERROR,
610 _("%s called with gdk_rgba = NULL"), __func__);
611 return 0;
612 }
613
614 cfg_int = ((guint) (gdk_rgba->red * 255.0)) << 24;
615 cfg_int += ((guint) (gdk_rgba->green * 255.0)) << 16;
616 cfg_int += ((guint) (gdk_rgba->blue * 255.0)) << 8;
617 cfg_int += (guint) (gdk_rgba->alpha * 255.0);
618
619 return cfg_int;
620 }
621
622 /* Convert a 0xRRGGBB encoded config integer to a GdkRGBA structure (no alpha channel) */
rgb_from_cfg(guint cfg_rgb,GdkRGBA * gdk_rgba)623 void rgb_from_cfg(guint cfg_rgb, GdkRGBA * gdk_rgba)
624 {
625 if (gdk_rgba == NULL)
626 {
627 sat_log_log(SAT_LOG_LEVEL_ERROR,
628 _("%s called with gdk_rgba = NULL"), __func__);
629 return;
630 }
631
632 gdk_rgba->red = ((cfg_rgb >> 16) & 0xFF) / 255.0;
633 gdk_rgba->green = ((cfg_rgb >> 8) & 0xFF) / 255.0;
634 gdk_rgba->blue = (cfg_rgb & 0xFF) / 255.0;
635 gdk_rgba->alpha = 1.0;
636 }
637
638 /* convert GdkRGBA struct to 0xRRGGBB formatted config integer, ignoring the alpha channel */
rgb_to_cfg(const GdkRGBA * gdk_rgba)639 guint rgb_to_cfg(const GdkRGBA * gdk_rgba)
640 {
641 guint cfg_int = 0;
642
643 if (gdk_rgba == NULL)
644 {
645 sat_log_log(SAT_LOG_LEVEL_ERROR,
646 _("%s called with gdk_rgba = NULL"), __func__);
647 return 0;
648 }
649
650 cfg_int = ((guint) (gdk_rgba->red * 255.0)) << 16;
651 cfg_int += ((guint) (gdk_rgba->green * 255.0)) << 8;
652 cfg_int += (guint) (gdk_rgba->blue * 255.0);
653
654 return cfg_int;
655 }
656