1 /*
2   Gpredict: Real-time satellite tracking and orbit prediction program
3 
4   Copyright (C)  2001-2013  Alexandru Csete, OZ9AEC.
5 
6   Authors: Alexandru Csete
7            Charles Suprin
8 
9   Comments, questions and bugreports should be submitted via
10   http://sourceforge.net/projects/gpredict/
11   More details can be found at the project home page:
12 
13   http://gpredict.oz9aec.net/
14 
15   This program is free software; you can redistribute it and/or modify
16   it under the terms of the GNU General Public License as published by
17   the Free Software Foundation; either version 2 of the License, or
18   (at your option) any later version.
19 
20   This program is distributed in the hope that it will be useful,
21   but WITHOUT ANY WARRANTY; without even the implied warranty of
22   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23   GNU General Public License for more details.
24 
25   You should have received a copy of the GNU General Public License
26   along with this program; if not, visit http://www.fsf.org/
27 */
28 /*
29  * Module manager.
30  *
31  * The module manager is responsible for the management of opened modules.
32  * It consist of a GtkNoteBook container where the modules are placed initially.
33  *
34  * The module manager is initialised with the mod_mgr_create function, which will
35  * create the notebook widget and re-open the modules that have been open when
36  * gpredict has been quit last time.
37  *
38  * To add additional modules the mod_mgr_add_module function should be used. This
39  * function takes a fully initialised GtkSatModule (FIXME: cast to GtkWidget) and
40  * a boolean flag indicating whether the module should be docked into the notebook
41  * or not. Please note, that if a module is added with dock=FALSE, the caller will
42  * have the responsibility of creating a proper container window for the module.
43  *
44  * Finally, when gpredict is about to exit, the state of the module manager can be
45  * saved by calling the mod_mgr_save_state. This will save a list of open modules
46  * so that they can be restored next time gpredict is re-opened.
47  *
48  * The mod-mgr maintains an internal GSList with references to the opened modules.
49  * This allows the mod-mgr to know about both docked and undocked modules.
50  *
51  */
52 #ifdef HAVE_CONFIG_H
53 #include <build-config.h>
54 #endif
55 #include <glib/gi18n.h>
56 #include <gtk/gtk.h>
57 
58 #include "config-keys.h"
59 #include "compat.h"
60 #include "gtk-sat-module.h"
61 #include "gtk-sat-module-popup.h"
62 #include "mod-cfg.h"
63 #include "mod-mgr.h"
64 #include "sat-cfg.h"
65 #include "sat-log.h"
66 
67 extern GtkWidget *app;
68 
69 /* List of modules, docked and undocked */
70 static GSList  *modules = NULL;
71 
72 
73 /* The notebook widget for docked modules */
74 static GtkWidget *nbook = NULL;
75 
76 
77 static void     update_window_title(void);
78 static void     switch_page_cb(GtkNotebook * notebook,
79                                gpointer * page,
80                                guint page_num, gpointer user_data);
81 
82 static void     create_module_window(GtkWidget * module);
83 
84 
mod_mgr_create(void)85 GtkWidget      *mod_mgr_create(void)
86 {
87     gchar          *openmods = NULL;
88     gchar         **mods;
89     gint            count, i;
90     GtkWidget      *module;
91     gchar          *modfile;
92     gchar          *confdir;
93     gint            page;
94 
95     nbook = gtk_notebook_new();
96     gtk_notebook_set_scrollable(GTK_NOTEBOOK(nbook), TRUE);
97     gtk_notebook_popup_enable(GTK_NOTEBOOK(nbook));
98     g_signal_connect(G_OBJECT(nbook), "switch-page",
99                      G_CALLBACK(switch_page_cb), NULL);
100 
101     openmods = sat_cfg_get_str(SAT_CFG_STR_OPEN_MODULES);
102     page = sat_cfg_get_int(SAT_CFG_INT_MODULE_CURRENT_PAGE);
103 
104     if (openmods)
105     {
106         mods = g_strsplit(openmods, ";", 0);
107         count = g_strv_length(mods);
108 
109         for (i = 0; i < count; i++)
110         {
111 
112             confdir = get_modules_dir();
113             modfile = g_strconcat(confdir, G_DIR_SEPARATOR_S,
114                                   mods[i], ".mod", NULL);
115             g_free(confdir);
116             module = gtk_sat_module_new(modfile);
117 
118             if (IS_GTK_SAT_MODULE(module))
119             {
120 
121                 /* if module state was window or user does not want to restore the
122                    state of the modules, pack the module into the notebook */
123                 if ((GTK_SAT_MODULE(module)->state == GTK_SAT_MOD_STATE_DOCKED)
124                     || !sat_cfg_get_bool(SAT_CFG_BOOL_MOD_STATE))
125                 {
126                     mod_mgr_add_module(module, TRUE);
127                 }
128                 else
129                 {
130                     mod_mgr_add_module(module, FALSE);
131                     create_module_window(module);
132                 }
133             }
134             else
135             {
136                 sat_log_log(SAT_LOG_LEVEL_ERROR,
137                             _("%s: Failed to restore %s"), __func__, mods[i]);
138 
139                 /* try to smartly handle disappearing modules */
140                 page--;
141             }
142 
143             g_free(modfile);
144 
145         }
146 
147         /* set to the page open when gpredict was closed */
148         if (page >= 0)
149             gtk_notebook_set_current_page(GTK_NOTEBOOK(nbook), page);
150 
151         g_strfreev(mods);
152         g_free(openmods);
153 
154         /* disable tabs if only one page in notebook */
155         if ((gtk_notebook_get_n_pages(GTK_NOTEBOOK(nbook))) == 1)
156         {
157             gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), FALSE);
158         }
159         else
160         {
161             gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), TRUE);
162         }
163     }
164     else
165     {
166         sat_log_log(SAT_LOG_LEVEL_INFO,
167                     _("%s: No modules have to be restored."), __func__);
168     }
169 
170     return nbook;
171 }
172 
173 /* Register a new module in the mod-mgr. If the dock flag is true the module is
174  * added to the mod-mgr notebook, otherwise it will be up to the caller to
175  * create a proper container.
176  */
mod_mgr_add_module(GtkWidget * module,gboolean dock)177 gint mod_mgr_add_module(GtkWidget * module, gboolean dock)
178 {
179     gint            retcode = 0;
180     gint            page;
181 
182 
183     if (module)
184     {
185 
186         /* add module to internal list */
187         modules = g_slist_append(modules, module);
188 
189         if (dock)
190         {
191             /* add module to notebook if state = DOCKED */
192             page = gtk_notebook_append_page(GTK_NOTEBOOK(nbook),
193                                             module,
194                                             gtk_label_new(GTK_SAT_MODULE
195                                                           (module)->name));
196 
197             /* allow nmodule to be dragged to different position */
198             gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(nbook), module,
199                                              TRUE);
200 
201             gtk_notebook_set_current_page(GTK_NOTEBOOK(nbook), page);
202 
203             /* send message to logger */
204             sat_log_log(SAT_LOG_LEVEL_INFO,
205                         _("%s: Added %s to module manager (page %d)."),
206                         __func__, GTK_SAT_MODULE(module)->name, page);
207         }
208         else
209         {
210             /* send message to logger */
211             sat_log_log(SAT_LOG_LEVEL_INFO,
212                         _("%s: Added %s to module manager (NOT DOCKED)."),
213                         __func__, GTK_SAT_MODULE(module)->name);
214         }
215         retcode = 0;
216     }
217     else
218     {
219         sat_log_log(SAT_LOG_LEVEL_ERROR,
220                     _("%s: Module %s seems to be NULL"),
221                     __func__, GTK_SAT_MODULE(module)->name);
222         retcode = 1;
223     }
224 
225     /* disable tabs if only one page in notebook */
226     if ((gtk_notebook_get_n_pages(GTK_NOTEBOOK(nbook))) == 1)
227     {
228         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), FALSE);
229     }
230     else
231     {
232         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), TRUE);
233     }
234 
235     update_window_title();
236 
237     return retcode;
238 }
239 
mod_mgr_remove_module(GtkWidget * module)240 gint mod_mgr_remove_module(GtkWidget * module)
241 {
242     gint            page;
243     gint            retcode = 0;
244 
245     /* remove from notebook */
246     if (GTK_SAT_MODULE(module)->state == GTK_SAT_MOD_STATE_DOCKED)
247     {
248         /* get page number for this module */
249         page = gtk_notebook_page_num(GTK_NOTEBOOK(nbook), module);
250 
251         if (page == -1)
252         {
253             /* this is some kind of bug (inconsistency between internal states) */
254             sat_log_log(SAT_LOG_LEVEL_ERROR,
255                         _
256                         ("%s: Could not find child in notebook. This may hurt..."),
257                         __func__);
258 
259             retcode = 1;
260         }
261         else
262         {
263             gtk_notebook_remove_page(GTK_NOTEBOOK(nbook), page);
264 
265             sat_log_log(SAT_LOG_LEVEL_INFO,
266                         _("%s: Removed child from notebook page %d."),
267                         __func__, page);
268 
269             retcode = 0;
270         }
271     }
272 
273     modules = g_slist_remove(modules, module);
274 
275     /* undocked modules will have to destroy themselves
276        because of their parent window
277      */
278 
279     /* disable tabs if only one page in notebook */
280     if ((gtk_notebook_get_n_pages(GTK_NOTEBOOK(nbook))) == 1)
281     {
282         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), FALSE);
283     }
284     else
285     {
286         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), TRUE);
287     }
288 
289     /* update window title */
290     update_window_title();
291 
292     return retcode;
293 }
294 
295 /*
296  * Save state of module manager.
297  *
298  * This function saves the state of the module manager. Currently, this consists
299  * of saving the list of open modules. If no modules are open, the function saves
300  * a NULL-list, indication that the corresponding configuration key should be
301  * removed.
302  */
mod_mgr_save_state()303 void mod_mgr_save_state()
304 {
305     guint           num;
306     guint           i;
307     GtkWidget      *module;
308     gchar          *mods = NULL;
309     gchar          *buff;
310     gint            page;
311 
312 
313     if (!nbook)
314     {
315         sat_log_log(SAT_LOG_LEVEL_ERROR,
316                     _("%s: Attempt to save state but mod-mgr is NULL?"),
317                     __func__);
318         return;
319     }
320 
321     num = g_slist_length(modules);
322     if (num == 0)
323     {
324         sat_log_log(SAT_LOG_LEVEL_INFO,
325                     _("%s: No modules need to save state."), __func__);
326 
327         sat_cfg_set_str(SAT_CFG_STR_OPEN_MODULES, NULL);
328 
329         return;
330     }
331 
332     for (i = 0; i < num; i++)
333     {
334         module = GTK_WIDGET(g_slist_nth_data(modules, i));
335 
336         /* save state of the module */
337         mod_cfg_save(GTK_SAT_MODULE(module)->name,
338                      GTK_SAT_MODULE(module)->cfgdata);
339 
340         if (i == 0)
341         {
342             buff = g_strdup(GTK_SAT_MODULE(module)->name);
343         }
344         else
345         {
346             buff = g_strconcat(mods, ";", GTK_SAT_MODULE(module)->name, NULL);
347             g_free(mods);
348         }
349 
350         mods = g_strdup(buff);
351         g_free(buff);
352         sat_log_log(SAT_LOG_LEVEL_DEBUG, _("%s: Stored %s"),
353                     __func__, GTK_SAT_MODULE(module)->name);
354     }
355 
356     /* store the currently open page number */
357     page = gtk_notebook_get_current_page(GTK_NOTEBOOK(nbook));
358 
359     sat_log_log(SAT_LOG_LEVEL_INFO, _("%s: Saved states for %d modules."),
360                 __func__, num);
361 
362     sat_cfg_set_str(SAT_CFG_STR_OPEN_MODULES, mods);
363     sat_cfg_set_int(SAT_CFG_INT_MODULE_CURRENT_PAGE, page);
364 
365     g_free(mods);
366 }
367 
mod_mgr_mod_is_visible(GtkWidget * module)368 gboolean mod_mgr_mod_is_visible(GtkWidget * module)
369 {
370     gint            page;
371     gboolean        retcode = TRUE;
372 
373     /* get page number for this module */
374     page = gtk_notebook_page_num(GTK_NOTEBOOK(nbook), module);
375 
376     if (page != -1)
377     {
378         if (gtk_notebook_get_current_page(GTK_NOTEBOOK(nbook)) == page)
379         {
380             retcode = TRUE;
381         }
382         else
383         {
384             retcode = FALSE;
385         }
386     }
387     else
388     {
389         retcode = FALSE;
390     }
391 
392     return retcode;
393 }
394 
395 /*
396  * Dock a module into the notebook.
397  *
398  * This function inserts the module into the notebook but does not add it
399  * to the list of modules, since it should already be there.
400  *
401  * The function does some sanity checks to ensure the the module actually
402  * is in the internal list of modules and also that the module is not
403  * already present in the notebook. If any of these checks fail, the function
404  * will send an error message and try to recover.
405  *
406  * The function does not modify the internal state of the module, module->state,
407  * that is up to the module itself.
408  */
mod_mgr_dock_module(GtkWidget * module)409 gint mod_mgr_dock_module(GtkWidget * module)
410 {
411     gint            retcode = 0;
412     gint            page;
413 
414     if (!g_slist_find(modules, module))
415     {
416         sat_log_log(SAT_LOG_LEVEL_ERROR,
417                     _("%s: Module %s not found in list. Trying to recover."),
418                     __func__, GTK_SAT_MODULE(module)->name);
419         modules = g_slist_append(modules, module);
420     }
421 
422     page = gtk_notebook_page_num(GTK_NOTEBOOK(nbook), module);
423     if (page != -1)
424     {
425         sat_log_log(SAT_LOG_LEVEL_ERROR,
426                     _("%s: Module %s already in notebook!"),
427                     __func__, GTK_SAT_MODULE(module)->name);
428         retcode = 1;
429     }
430     else
431     {
432         /* add module to notebook */
433         page = gtk_notebook_append_page(GTK_NOTEBOOK(nbook),
434                                         module,
435                                         gtk_label_new(GTK_SAT_MODULE(module)->
436                                                       name));
437 
438         sat_log_log(SAT_LOG_LEVEL_INFO,
439                     _("%s: Docked %s into notebook (page %d)"),
440                     __func__, GTK_SAT_MODULE(module)->name, page);
441 
442         retcode = 0;
443     }
444 
445     /* disable tabs if only one page in notebook */
446     if ((gtk_notebook_get_n_pages(GTK_NOTEBOOK(nbook))) == 1)
447     {
448         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), FALSE);
449     }
450     else
451     {
452         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), TRUE);
453     }
454 
455     /* update window title */
456     update_window_title();
457 
458     return retcode;
459 }
460 
461 /*
462  * Undock module from notebook
463  *
464  * This function removes module from the notebook without removing it from
465  * the internal list of modules.
466  *
467  * The function does some sanity checks to ensure that the module actually
468  * exists in the mod-mgr, if not it will add module to the internal list
469  * and raise a warning.
470  *
471  * The function does not modify the internal state of the module, module->state,
472  * that is up to the module itself.
473  *
474  * \note The module itself is responsible for temporarily incrementing the
475  *       reference count of the widget in order to avoid destruction when
476  *       removing from the notebook.
477  */
mod_mgr_undock_module(GtkWidget * module)478 gint mod_mgr_undock_module(GtkWidget * module)
479 {
480     gint            retcode = 0;
481     gint            page;
482 
483     if (!g_slist_find(modules, module))
484     {
485         sat_log_log(SAT_LOG_LEVEL_ERROR,
486                     _("%s: Module %s not found in list. Trying to recover."),
487                     __func__, GTK_SAT_MODULE(module)->name);
488         modules = g_slist_append(modules, module);
489     }
490 
491     page = gtk_notebook_page_num(GTK_NOTEBOOK(nbook), module);
492     if (page == -1)
493     {
494         sat_log_log(SAT_LOG_LEVEL_ERROR,
495                     _("%s: Module %s does not seem to be docked!"),
496                     __func__, GTK_SAT_MODULE(module)->name);
497         retcode = 1;
498     }
499     else
500     {
501 
502         gtk_notebook_remove_page(GTK_NOTEBOOK(nbook), page);
503 
504         sat_log_log(SAT_LOG_LEVEL_INFO,
505                     _("%s: Removed %s from notebook page %d."),
506                     __func__, GTK_SAT_MODULE(module)->name, page);
507 
508         retcode = 0;
509     }
510 
511     /* disable tabs if only one page in notebook */
512     if ((gtk_notebook_get_n_pages(GTK_NOTEBOOK(nbook))) == 1)
513     {
514         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), FALSE);
515     }
516     else
517     {
518         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(nbook), TRUE);
519     }
520 
521     /* update window title */
522     update_window_title();
523 
524     return retcode;
525 }
526 
update_window_title()527 static void update_window_title()
528 {
529     gint            pgn, num;
530     GtkWidget      *pg;
531     gchar          *title;
532 
533     /* get number of pages */
534     num = gtk_notebook_get_n_pages(GTK_NOTEBOOK(nbook));
535 
536     if (num == 0)
537     {
538         gtk_window_set_title(GTK_WINDOW(app), _("Gpredict: (none)"));
539     }
540     else
541     {
542         pgn = gtk_notebook_get_current_page(GTK_NOTEBOOK(nbook));
543         pg = gtk_notebook_get_nth_page(GTK_NOTEBOOK(nbook), pgn);
544         title = g_strdup_printf(_("Gpredict: %s"),
545                                 gtk_notebook_get_tab_label_text(GTK_NOTEBOOK
546                                                                 (nbook), pg));
547         gtk_window_set_title(GTK_WINDOW(app), title);
548         g_free(title);
549     }
550 }
551 
switch_page_cb(GtkNotebook * notebook,gpointer * page,guint page_num,gpointer user_data)552 static void switch_page_cb(GtkNotebook * notebook,
553                            gpointer * page, guint page_num, gpointer user_data)
554 {
555     GtkWidget      *pg;
556     gchar          *title;
557 
558     (void)notebook;
559     (void)page;
560     (void)user_data;
561 
562     pg = gtk_notebook_get_nth_page(GTK_NOTEBOOK(nbook), page_num);
563     title = g_strdup_printf(_("Gpredict: %s"),
564                             gtk_notebook_get_tab_label_text(GTK_NOTEBOOK
565                                                             (nbook), pg));
566     gtk_window_set_title(GTK_WINDOW(app), title);
567     g_free(title);
568 }
569 
mod_mgr_reload_sats()570 void mod_mgr_reload_sats()
571 {
572     guint           num;
573     guint           i;
574     GtkSatModule   *mod;
575 
576     if (!nbook)
577     {
578         sat_log_log(SAT_LOG_LEVEL_ERROR,
579                     _("%s: Attempt to reload sats but mod-mgr is NULL?"),
580                     __func__);
581         return;
582     }
583 
584     num = g_slist_length(modules);
585     if (num == 0)
586     {
587         sat_log_log(SAT_LOG_LEVEL_INFO,
588                     _("%s: No modules need to reload sats."), __func__);
589         return;
590     }
591 
592     /* for each module in the GSList execute sat_module_reload_sats() */
593     for (i = 0; i < num; i++)
594     {
595         mod = GTK_SAT_MODULE(g_slist_nth_data(modules, i));
596         gtk_sat_module_reload_sats(mod);
597     }
598 }
599 
create_module_window(GtkWidget * module)600 static void create_module_window(GtkWidget * module)
601 {
602     gint            w, h;
603     gchar          *icon;       /* icon file name */
604     gchar          *title;      /* window title */
605     GtkAllocation   aloc;
606 
607     gtk_widget_get_allocation(module, &aloc);
608     /* get stored size; use size from main window if size not explicitly stoed */
609     if (g_key_file_has_key(GTK_SAT_MODULE(module)->cfgdata,
610                            MOD_CFG_GLOBAL_SECTION, MOD_CFG_WIN_WIDTH, NULL))
611     {
612         w = g_key_file_get_integer(GTK_SAT_MODULE(module)->cfgdata,
613                                    MOD_CFG_GLOBAL_SECTION,
614                                    MOD_CFG_WIN_WIDTH, NULL);
615     }
616     else
617     {
618         w = aloc.width;
619     }
620     if (g_key_file_has_key(GTK_SAT_MODULE(module)->cfgdata,
621                            MOD_CFG_GLOBAL_SECTION, MOD_CFG_WIN_HEIGHT, NULL))
622     {
623         h = g_key_file_get_integer(GTK_SAT_MODULE(module)->cfgdata,
624                                    MOD_CFG_GLOBAL_SECTION,
625                                    MOD_CFG_WIN_HEIGHT, NULL);
626     }
627     else
628     {
629         h = aloc.height;
630     }
631 
632     /* increase reference count of module */
633     //g_object_ref (module);
634 
635     /* we don't need the positions */
636     //GTK_SAT_MODULE (module)->vpanedpos = -1;
637     //GTK_SAT_MODULE (module)->hpanedpos = -1;
638 
639     /* undock from mod-mgr */
640     //mod_mgr_undock_module (module);
641 
642     /* create window */
643     GTK_SAT_MODULE(module)->win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
644     title = g_strconcat(_("Gpredict: "),
645                         GTK_SAT_MODULE(module)->name,
646                         " (", GTK_SAT_MODULE(module)->qth->name, ")", NULL);
647     gtk_window_set_title(GTK_WINDOW(GTK_SAT_MODULE(module)->win), title);
648     g_free(title);
649     gtk_window_set_default_size(GTK_WINDOW(GTK_SAT_MODULE(module)->win), w, h);
650     g_signal_connect(G_OBJECT(GTK_SAT_MODULE(module)->win), "configure_event",
651                      G_CALLBACK(module_window_config_cb), module);
652 
653     icon = logo_file_name("gpredict_icon_color.svg");
654     if (g_file_test(icon, G_FILE_TEST_EXISTS))
655     {
656         gtk_window_set_icon_from_file(GTK_WINDOW(GTK_SAT_MODULE(module)->win),
657                                       icon, NULL);
658     }
659     g_free(icon);
660 
661     /* move window to stored position if requested by configuration */
662     if (sat_cfg_get_bool(SAT_CFG_BOOL_MOD_WIN_POS) &&
663         g_key_file_has_key(GTK_SAT_MODULE(module)->cfgdata,
664                            MOD_CFG_GLOBAL_SECTION,
665                            MOD_CFG_WIN_POS_X,
666                            NULL) &&
667         g_key_file_has_key(GTK_SAT_MODULE(module)->cfgdata,
668                            MOD_CFG_GLOBAL_SECTION, MOD_CFG_WIN_POS_Y, NULL))
669     {
670 
671         gtk_window_move(GTK_WINDOW(GTK_SAT_MODULE(module)->win),
672                         g_key_file_get_integer(GTK_SAT_MODULE(module)->cfgdata,
673                                                MOD_CFG_GLOBAL_SECTION,
674                                                MOD_CFG_WIN_POS_X, NULL),
675                         g_key_file_get_integer(GTK_SAT_MODULE(module)->cfgdata,
676                                                MOD_CFG_GLOBAL_SECTION,
677                                                MOD_CFG_WIN_POS_Y, NULL));
678     }
679 
680     gtk_container_add(GTK_CONTAINER(GTK_SAT_MODULE(module)->win), module);
681     gtk_widget_show_all(GTK_SAT_MODULE(module)->win);
682 
683     /* reparent time manager window if visible */
684     if (GTK_SAT_MODULE(module)->tmgActive)
685     {
686         gtk_window_set_transient_for(GTK_WINDOW
687                                      (GTK_SAT_MODULE(module)->tmgWin),
688                                      GTK_WINDOW(GTK_SAT_MODULE(module)->win));
689     }
690 }
691