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 <gtk/gtk.h>
24 
25 #include "compat.h"
26 #include "config-keys.h"
27 #include "gtk-sat-module.h"
28 #include "mod-cfg-get-param.h"
29 #include "sat-cfg.h"
30 #include "sat-log.h"
31 #include "sat-pref-layout.h"
32 
33 static gboolean dirty = FALSE;
34 static gboolean reset = FALSE;
35 
36 /* check boxes for window positioning */
37 static GtkWidget *mwin, *mod, *state;
38 
39 /* Text entry for layout string */
40 static GtkWidget *gridstr;
41 static gulong   gridstr_sigid;
42 
43 /* layout selector combo */
44 static GtkWidget *selector;
45 
46 /* layout thumbnail */
47 static GtkWidget *thumb;
48 
49 
50 /* the number of predefined layouts (+1 for custom). */
51 #define PREDEF_NUM 10
52 
53 /* Predefined layouts. */
54 gchar          *predef_layout[PREDEF_NUM][3] = {
55     {"1;0;2;0;1;2;0;1;1;2;3;1;2;1;2", N_("World map, polar and single sat"),
56      "gpredict-layout-00.png"},
57     {"1;0;2;0;1", N_("World map"), "gpredict-layout-01.png"},
58     {"0;0;2;0;1", N_("Table"), "gpredict-layout-02.png"},
59     {"1;0;2;0;2;0;0;2;2;3", N_("World map and table"),
60      "gpredict-layout-03.png"},
61     {"2;0;1;0;1;3;1;2;0;1", N_("Polar and single sat"),
62      "gpredict-layout-04.png"},
63     {"2;0;1;0;1;4;1;2;0;1", N_("Polar and upcoming passes"),
64      "gpredict-layout-05.png"},
65     {"1;0;3;0;4;0;0;3;4;6;2;0;1;6;8;3;1;2;6;8;4;2;3;6;8",
66      N_("All views (narrow)"), "gpredict-layout-06.png"},
67     {"1;0;3;0;3;0;0;3;3;4;2;3;4;0;2;4;3;4;2;3;3;3;4;3;4",
68      N_("All views (wide)"), "gpredict-layout-07.png"},
69     {"1;0;3;0;3;0;0;3;3;4;2;3;4;0;2;3;3;4;2;4",
70      N_("Map, table, polar and single sat (wide)"), "gpredict-layout-08.png"},
71     {"", N_("Custom"), "gpredict-layout-99.png"}
72 };
73 
74 
75 /* User pressed cancel. Any changes to config must be cancelled. */
sat_pref_layout_cancel(GKeyFile * cfg)76 void sat_pref_layout_cancel(GKeyFile * cfg)
77 {
78     gchar          *str;
79 
80     (void)cfg;
81 
82     str = sat_cfg_get_str(SAT_CFG_STR_MODULE_GRID);
83     gtk_entry_set_text(GTK_ENTRY(gridstr), str);
84     g_free(str);
85 
86     dirty = FALSE;
87 }
88 
89 /* User pressed OK. Any changes should be stored in config. */
sat_pref_layout_ok(GKeyFile * cfg)90 void sat_pref_layout_ok(GKeyFile * cfg)
91 {
92     if (dirty)
93     {
94         /* we have new settings */
95         if (cfg != NULL)
96         {
97             g_key_file_set_string(cfg,
98                                   MOD_CFG_GLOBAL_SECTION,
99                                   MOD_CFG_GRID,
100                                   gtk_entry_get_text(GTK_ENTRY(gridstr)));
101         }
102         else
103         {
104             sat_cfg_set_str(SAT_CFG_STR_MODULE_GRID,
105                             gtk_entry_get_text(GTK_ENTRY(gridstr)));
106             sat_cfg_set_bool(SAT_CFG_BOOL_MAIN_WIN_POS,
107                              gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
108                                                           (mwin)));
109             sat_cfg_set_bool(SAT_CFG_BOOL_MOD_WIN_POS,
110                              gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
111                                                           (mod)));
112             sat_cfg_set_bool(SAT_CFG_BOOL_MOD_STATE,
113                              gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
114                                                           (state)));
115         }
116     }
117     else if (reset)
118     {
119         /* we have to reset the values to global or default settings */
120         if (cfg == NULL)
121         {
122             /* layout */
123             sat_cfg_reset_str(SAT_CFG_STR_MODULE_GRID);
124 
125             /* window placement */
126             sat_cfg_reset_bool(SAT_CFG_BOOL_MAIN_WIN_POS);
127             sat_cfg_reset_bool(SAT_CFG_BOOL_MOD_WIN_POS);
128             sat_cfg_reset_bool(SAT_CFG_BOOL_MOD_STATE);
129         }
130         else
131         {
132             g_key_file_remove_key((GKeyFile *) (cfg),
133                                   MOD_CFG_GLOBAL_SECTION, MOD_CFG_GRID, NULL);
134         }
135     }
136     dirty = FALSE;
137     reset = FALSE;
138 }
139 
140 /**
141  * Get thumbnail icon filename from selection ID.
142  *
143  * @param sel The ID of the predefined layout or PREDEF_NUM-1 for custom.
144  * @return A newly allocated string containing the full path of the icon.
145  *
146  * This function generates an icon file name from the ID of a predefined
147  * layout. PREDEF_NUM-1 corresponds to the last entry in predef_layout[][],
148  * which is the custom layout. The returned string should be freed when no
149  * longer needed.
150  *
151  * The function checks that sel is within valid range (0...PREDEF_NUM-1). If
152  * sel is outside the range, the custom layout icon is returned.
153  */
thumb_file_from_sel(guint sel)154 static gchar   *thumb_file_from_sel(guint sel)
155 {
156     gchar          *fname;
157 
158     if (sel < PREDEF_NUM)
159         fname = icon_file_name(predef_layout[sel][2]);
160     else
161         fname = icon_file_name(predef_layout[PREDEF_NUM - 1][2]);
162 
163     return fname;
164 }
165 
layout_code_changed(GtkWidget * widget,gpointer data)166 static void layout_code_changed(GtkWidget * widget, gpointer data)
167 {
168     gchar          *entry, *end, *j;
169     gint            len, pos;
170 
171     (void)data;
172 
173     /* step 1: ensure that only valid characters are entered
174        (stolen from xlog, tnx pg4i)
175      */
176     entry = gtk_editable_get_chars(GTK_EDITABLE(widget), 0, -1);
177     if ((len = g_utf8_strlen(entry, -1)) > 0)
178     {
179         end = entry + g_utf8_strlen(entry, -1);
180         for (j = entry; j < end; ++j)
181         {
182             int             c = *j;
183 
184             if (g_ascii_isdigit(c) || c == ';')
185             {
186                 dirty = TRUE;
187                 /* ensure combo box is set to custom */
188                 if (gtk_combo_box_get_active(GTK_COMBO_BOX(selector)) !=
189                     PREDEF_NUM - 1)
190                 {
191                     gtk_combo_box_set_active(GTK_COMBO_BOX(selector),
192                                              PREDEF_NUM - 1);
193                 }
194             }
195             else
196             {
197                 gdk_beep();
198                 pos = gtk_editable_get_position(GTK_EDITABLE(widget));
199                 gtk_editable_delete_text(GTK_EDITABLE(widget), pos, pos + 1);
200             }
201         }
202     }
203 }
204 
205 /* Callback to manage layout selection via combo box */
layout_selected_cb(GtkComboBox * combo,gpointer data)206 static void layout_selected_cb(GtkComboBox * combo, gpointer data)
207 {
208     gint            idx;
209     gchar          *icon;
210 
211     (void)data;
212 
213     idx = gtk_combo_box_get_active(combo);
214     if (idx < PREDEF_NUM)
215     {
216         dirty = TRUE;
217 
218         /* update icon */
219         icon = thumb_file_from_sel(idx);
220         gtk_image_set_from_file(GTK_IMAGE(thumb), icon);
221         g_free(icon);
222 
223         /* update layout code, unless Custom is selected */
224         if (idx < PREDEF_NUM - 1)
225         {
226             g_signal_handler_block(gridstr, gridstr_sigid);
227             gtk_entry_set_text(GTK_ENTRY(gridstr), predef_layout[idx][0]);
228             g_signal_handler_unblock(gridstr, gridstr_sigid);
229             gtk_widget_set_sensitive(gridstr, FALSE);
230         }
231         else
232         {
233             gtk_widget_set_sensitive(gridstr, TRUE);
234         }
235     }
236 }
237 
238 /* Create layout selector. */
create_layout_selector(GKeyFile * cfg,GtkGrid * table)239 static void create_layout_selector(GKeyFile * cfg, GtkGrid * table)
240 {
241     GtkWidget      *label;
242     gchar          *buffer;
243     gchar          *thumbfile;
244     guint           i, sel = PREDEF_NUM - 1;
245 
246     /* get the current settings */
247     if (cfg != NULL)
248     {
249         buffer = mod_cfg_get_str(cfg,
250                                  MOD_CFG_GLOBAL_SECTION,
251                                  MOD_CFG_GRID, SAT_CFG_STR_MODULE_GRID);
252     }
253     else
254     {
255         buffer = sat_cfg_get_str(SAT_CFG_STR_MODULE_GRID);
256     }
257 
258     /* create header */
259     label = gtk_label_new(_("Select layout:"));
260     g_object_set(label, "xalign", 1.0, "yalign", 0.5, NULL);
261     gtk_grid_attach(table, label, 0, 0, 1, 1);
262 
263     /* layout selector */
264     selector = gtk_combo_box_text_new();
265     gtk_grid_attach(table, selector, 1, 0, 2, 1);
266 
267     for (i = 0; i < PREDEF_NUM; i++)
268     {
269         /* append default layout string to combo box */
270         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(selector),
271                                        _(predef_layout[i][1]));
272 
273         /* check if this layout corresponds to the settings */
274         if (!g_ascii_strcasecmp(buffer, predef_layout[i][0]))
275         {
276             sel = i;
277         }
278     }
279 
280     gtk_combo_box_set_active(GTK_COMBO_BOX(selector), sel);
281     g_signal_connect(selector, "changed", G_CALLBACK(layout_selected_cb),
282                      NULL);
283 
284     /* layout preview thumbnail */
285     thumbfile = thumb_file_from_sel(sel);
286     thumb = gtk_image_new_from_file(thumbfile);
287     g_free(thumbfile);
288     gtk_grid_attach(table, thumb, 1, 1, 2, 1);
289 
290     /* layout string */
291     label = gtk_label_new(_("Layout code:"));
292     g_object_set(label, "xalign", 1.0, "yalign", 0.5, NULL);
293     gtk_grid_attach(table, label, 0, 2, 1, 1);
294 
295     gridstr = gtk_entry_new();
296     gtk_entry_set_text(GTK_ENTRY(gridstr), buffer);
297     g_free(buffer);
298     gtk_widget_set_tooltip_text(gridstr,
299                                 _("This entry holds the layout code for the "
300                                   "module.\n"
301                                   "Consult the user manual for how to create "
302                                   "custom layouts using layout codes."));
303 
304     /* disable if it is a predefined layout */
305     if (sel < PREDEF_NUM - 1)
306     {
307         gtk_widget_set_sensitive(gridstr, FALSE);
308     }
309 
310     /* connect changed signal handler */
311     gridstr_sigid =
312         g_signal_connect(gridstr, "changed", G_CALLBACK(layout_code_changed),
313                          NULL);
314 
315     gtk_grid_attach(table, gridstr, 1, 2, 3, 1);
316 }
317 
318 /* Toggle window positioning settings. */
window_pos_toggle_cb(GtkWidget * toggle,gpointer data)319 static void window_pos_toggle_cb(GtkWidget * toggle, gpointer data)
320 {
321     (void)toggle;
322     (void)data;
323     dirty = TRUE;
324 }
325 
326 /* window placement widgets */
create_window_placement(GtkBox * vbox)327 static void create_window_placement(GtkBox * vbox)
328 {
329     GtkWidget      *label;
330 
331     /* create header */
332     label = gtk_label_new(NULL);
333     g_object_set(label, "xalign", 0.0, "yalign", 0.5, NULL);
334     gtk_label_set_markup(GTK_LABEL(label), _("<b>Window Placements:</b>"));
335     gtk_box_pack_start(vbox, label, FALSE, FALSE, 0);
336 
337     /* main window setting */
338     mwin =
339         gtk_check_button_new_with_label(_("Restore position of main window"));
340     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mwin),
341                                  sat_cfg_get_bool(SAT_CFG_BOOL_MAIN_WIN_POS));
342     gtk_widget_set_tooltip_text(mwin,
343                                 _
344                                 ("If you check this button, gpredict will try "
345                                  "to place the main window at the position it was "
346                                  "during the last session.\n"
347                                  "Note that window managers can ignore this request."));
348     g_signal_connect(G_OBJECT(mwin), "toggled",
349                      G_CALLBACK(window_pos_toggle_cb), NULL);
350     gtk_box_pack_start(vbox, mwin, FALSE, FALSE, 0);
351 
352     /* module window setting */
353     mod = gtk_check_button_new_with_label(_
354                                           ("Restore position of module windows"));
355     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mod),
356                                  sat_cfg_get_bool(SAT_CFG_BOOL_MOD_WIN_POS));
357     gtk_widget_set_tooltip_text(mod,
358                                 _
359                                 ("If you check this button, gpredict will try "
360                                  "to place the module windows at the position "
361                                  "they were the last time.\n"
362                                  "Note that window managers can ignore this request."));
363     g_signal_connect(G_OBJECT(mod), "toggled",
364                      G_CALLBACK(window_pos_toggle_cb), NULL);
365     gtk_box_pack_start(vbox, mod, FALSE, FALSE, 0);
366 
367     /* module state */
368     state =
369         gtk_check_button_new_with_label(_
370                                         ("Restore the state of modules when reopened (docked or window)"));
371     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state),
372                                  sat_cfg_get_bool(SAT_CFG_BOOL_MOD_STATE));
373     gtk_widget_set_tooltip_text(state,
374                                 _("If you check this button, gpredict will "
375                                   "restore the states of the modules from the last time they were used."));
376     g_signal_connect(G_OBJECT(state), "toggled",
377                      G_CALLBACK(window_pos_toggle_cb), NULL);
378     gtk_box_pack_start(vbox, state, FALSE, FALSE, 0);
379 }
380 
381 /*
382  * Reset settings.
383  *
384  * @param button The RESET button.
385  * @param cfg Pointer to the module config or NULL in global mode.
386  *
387  * This function is called when the user clicks on the RESET button. In global mode
388  * (when cfg = NULL) the function will reset the settings to the efault values, while
389  * in "local" mode (when cfg != NULL) the function will reset the module settings to
390  * the global settings. This is done by removing the corresponding key from the GKeyFile.
391  */
reset_cb(GtkWidget * button,gpointer cfg)392 static void reset_cb(GtkWidget * button, gpointer cfg)
393 {
394     guint           i, sel = PREDEF_NUM - 1;
395     gchar          *buffer;
396 
397     (void)button;
398 
399     /* views */
400     if (cfg == NULL)
401     {
402         /* global mode, get defaults */
403         buffer = sat_cfg_get_str_def(SAT_CFG_STR_MODULE_GRID);
404         gtk_entry_set_text(GTK_ENTRY(gridstr), buffer);
405     }
406     else
407     {
408         /* local mode, get global value */
409         buffer = sat_cfg_get_str(SAT_CFG_STR_MODULE_GRID);
410         gtk_entry_set_text(GTK_ENTRY(gridstr), buffer);
411     }
412 
413     /* findcombo box setting */
414     for (i = 0; i < PREDEF_NUM; i++)
415     {
416         /* check if this layout corresponds to the settings */
417         if (!g_ascii_strcasecmp(buffer, predef_layout[i][0]))
418         {
419             sel = i;
420         }
421     }
422     gtk_combo_box_set_active(GTK_COMBO_BOX(selector), sel);
423     g_free(buffer);
424 
425     /* window placement settings */
426     if (cfg == NULL)
427     {
428         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mwin),
429                                      sat_cfg_get_bool_def
430                                      (SAT_CFG_BOOL_MAIN_WIN_POS));
431         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(mod),
432                                      sat_cfg_get_bool_def
433                                      (SAT_CFG_BOOL_MOD_WIN_POS));
434         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(state),
435                                      sat_cfg_get_bool_def
436                                      (SAT_CFG_BOOL_MOD_STATE));
437     }
438 
439     /* reset flags */
440     reset = TRUE;
441     dirty = FALSE;
442 }
443 
create_reset_button(GKeyFile * cfg,GtkBox * vbox)444 static void create_reset_button(GKeyFile * cfg, GtkBox * vbox)
445 {
446     GtkWidget      *button;
447     GtkWidget      *butbox;
448 
449     button = gtk_button_new_with_label(_("Reset"));
450     g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(reset_cb), cfg);
451 
452     if (cfg == NULL)
453     {
454         gtk_widget_set_tooltip_text(button,
455                                     _
456                                     ("Reset settings to the default values."));
457     }
458     else
459     {
460         gtk_widget_set_tooltip_text(button,
461                                     _
462                                     ("Reset module settings to the global values."));
463     }
464 
465     butbox = gtk_button_box_new(GTK_ORIENTATION_HORIZONTAL);
466     gtk_button_box_set_layout(GTK_BUTTON_BOX(butbox), GTK_BUTTONBOX_END);
467     gtk_box_pack_end(GTK_BOX(butbox), button, FALSE, TRUE, 10);
468     gtk_box_pack_end(vbox, butbox, FALSE, TRUE, 0);
469 
470 }
471 
sat_pref_layout_create(GKeyFile * cfg)472 GtkWidget      *sat_pref_layout_create(GKeyFile * cfg)
473 {
474     GtkWidget      *table;
475     GtkWidget      *vbox;
476 
477     /* create the table */
478     table = gtk_grid_new();
479     gtk_grid_set_row_spacing(GTK_GRID(table), 10);
480     gtk_grid_set_column_spacing(GTK_GRID(table), 5);
481 
482     /* layout selector */
483     create_layout_selector(cfg, GTK_GRID(table));
484 
485     /* separator */
486     gtk_grid_attach(GTK_GRID(table),
487                     gtk_separator_new(GTK_ORIENTATION_HORIZONTAL), 0, 3, 5, 1);
488 
489     /* create vertical box */
490     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
491     gtk_box_set_homogeneous(GTK_BOX(vbox), FALSE);
492     gtk_container_set_border_width(GTK_CONTAINER(vbox), 20);
493     gtk_box_pack_start(GTK_BOX(vbox), table, FALSE, TRUE, 0);
494 
495     /* window placement */
496     if (cfg == NULL)
497         create_window_placement(GTK_BOX(vbox));
498 
499     /* create RESET button */
500     create_reset_button(cfg, GTK_BOX(vbox));
501 
502     /* reset flags */
503     dirty = FALSE;
504     reset = FALSE;
505 
506     return vbox;;
507 }
508