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