sat-pref-layout.c
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. */ 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. */ 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 */ 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 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_display_beep(gdk_display_get_default()); 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 */ 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. */ 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. */ 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 */ 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 */ 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 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 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 }