/ src / sat-pref-layout.c
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  }