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