]> Pileus Git - grits/blob - src/aweather-gui.c
Adding a prefs dialog (which needs a lot more work)
[grits] / src / aweather-gui.c
1 /*
2  * Copyright (C) 2009 Andy Spencer <spenceal@rose-hulman.edu>
3  * 
4  * This program is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
16  */
17
18 #include <config.h>
19 #include <gtk/gtk.h>
20 #include <gdk/gdkkeysyms.h>
21 #include <math.h>
22
23 #include "misc.h"
24 #include "aweather-gui.h"
25 #include "aweather-plugin.h"
26 #include "gis-opengl.h"
27 #include "location.h"
28
29 /* Needed prototpes */
30 gboolean on_gui_key_press(GtkWidget *widget, GdkEventKey *event, AWeatherGui *self);
31 static void site_setup(AWeatherGui *self);
32 static void time_setup(AWeatherGui *self);
33
34 /****************
35  * GObject code *
36  ****************/
37 G_DEFINE_TYPE(AWeatherGui, aweather_gui, GTK_TYPE_WINDOW);
38 static void aweather_gui_init(AWeatherGui *self)
39 {
40         g_debug("AWeatherGui: init");
41
42         /* Setup window */
43         self->builder = gtk_builder_new();
44         GError *error = NULL;
45         if (!gtk_builder_add_from_file(self->builder, DATADIR "/aweather/main.ui", &error))
46                 g_error("Failed to create gtk builder: %s", error->message);
47         gtk_widget_reparent(aweather_gui_get_widget(self, "body"), GTK_WIDGET(self));
48
49         /* GIS things */
50         GtkWidget *drawing = aweather_gui_get_widget(self, "drawing");
51         self->world   = gis_world_new();
52         self->view    = gis_view_new();
53         self->opengl  = gis_opengl_new(self->world, self->view, GTK_DRAWING_AREA(drawing));
54
55         /* Plugins */
56         self->plugins     = NULL;
57         // TODO: set/load these with prefereces
58         aweather_gui_load_plugin(self, "example");
59         //aweather_gui_load_plugin(self, "radar");
60         //aweather_gui_load_plugin(self, "ridge");
61         self->gtk_plugins = GTK_LIST_STORE(aweather_gui_get_object(self, "plugins"));
62         GDir *dir = g_dir_open(PLUGINDIR, 0, NULL);
63         const gchar *name;
64         GtkTreeIter iter;
65         while ((name = g_dir_read_name(dir))) {
66                 if (g_pattern_match_simple("*.so", name)) {
67                         gtk_list_store_append(self->gtk_plugins, &iter);
68                         gtk_list_store_set(self->gtk_plugins, &iter, 0, name, 1, FALSE, -1);
69                 }
70         }
71
72         /* Misc, helpers */
73         site_setup(self);
74         time_setup(self);
75
76         /* Connect signals */
77         gtk_builder_connect_signals(self->builder, self);
78         g_signal_connect(self, "key-press-event",
79                         G_CALLBACK(on_gui_key_press), self);
80         g_signal_connect_swapped(self->world, "offline",
81                         G_CALLBACK(gtk_toggle_action_set_active),
82                         aweather_gui_get_object(self, "offline"));
83
84         /* Preferences */
85         gchar *filename = g_build_filename(g_get_user_config_dir(),
86                         "aweather", "config.ini", NULL);
87         self->prefs = g_key_file_new();
88         g_key_file_load_from_file(self->prefs, filename, G_KEY_FILE_KEEP_COMMENTS, &error);
89 }
90 static GObject *aweather_gui_constructor(GType gtype, guint n_properties,
91                 GObjectConstructParam *properties)
92 {
93         g_debug("aweather_gui: constructor");
94         GObjectClass *parent_class = G_OBJECT_CLASS(aweather_gui_parent_class);
95         return  parent_class->constructor(gtype, n_properties, properties);
96 }
97 static void aweather_gui_dispose(GObject *_self)
98 {
99         g_debug("AWeatherGui: dispose");
100         AWeatherGui *self = AWEATHER_GUI(_self);
101         if (self->builder) {
102                 /* Reparent to avoid double unrefs */
103                 GtkWidget *body   = aweather_gui_get_widget(self, "body");
104                 GtkWidget *window = aweather_gui_get_widget(self, "main_window");
105                 gtk_widget_reparent(body, window);
106                 g_object_unref(self->builder);
107                 self->builder = NULL;
108         }
109         if (self->world) {
110                 g_object_unref(self->world);
111                 self->world = NULL;
112         }
113         if (self->view) {
114                 g_object_unref(self->view);
115                 self->view = NULL;
116         }
117         if (self->opengl) {
118                 g_object_unref(self->opengl);
119                 self->opengl = NULL;
120         }
121         if (self->plugins) {
122                 g_list_foreach(self->plugins, (GFunc)g_object_unref, NULL);
123                 g_list_free(self->plugins);
124                 self->plugins = NULL;
125         }
126         if (self->prefs) {
127                 gchar *filename = g_build_filename(g_get_user_config_dir(),
128                                 "aweather", "config.ini", NULL);
129                 gsize length;
130                 g_mkdir_with_parents(g_path_get_dirname(filename), 0755);
131                 gchar *data = g_key_file_to_data(self->prefs, &length, NULL);
132                 g_file_set_contents(filename, data, length, NULL);
133                 g_key_file_free(self->prefs);
134                 self->prefs = NULL;
135         }
136         G_OBJECT_CLASS(aweather_gui_parent_class)->dispose(_self);
137 }
138 static void aweather_gui_finalize(GObject *_self)
139 {
140         g_debug("AWeatherGui: finalize");
141         G_OBJECT_CLASS(aweather_gui_parent_class)->finalize(_self);
142         gtk_main_quit();
143
144 }
145 static void aweather_gui_class_init(AWeatherGuiClass *klass)
146 {
147         g_debug("AWeatherGui: class_init");
148         GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
149         gobject_class->constructor  = aweather_gui_constructor;
150         gobject_class->dispose      = aweather_gui_dispose;
151         gobject_class->finalize     = aweather_gui_finalize;
152 }
153
154 /*************
155  * Callbacks *
156  *************/
157 gboolean on_gui_key_press(GtkWidget *widget, GdkEventKey *event, AWeatherGui *self)
158 {
159         g_debug("AWeatherGui: on_gui_key_press - key=%x, state=%x",
160                         event->keyval, event->state);
161         if (event->keyval == GDK_q)
162                 gtk_widget_destroy(GTK_WIDGET(self));
163         else if (event->keyval == GDK_r && event->state & GDK_CONTROL_MASK)
164                 gis_world_refresh(self->world);
165         else if (event->keyval == GDK_Tab || event->keyval == GDK_ISO_Left_Tab) {
166                 GtkNotebook *tabs = GTK_NOTEBOOK(aweather_gui_get_widget(self, "tabs"));
167                 gint num_tabs = gtk_notebook_get_n_pages(tabs);
168                 gint cur_tab  = gtk_notebook_get_current_page(tabs);
169                 if (event->state & GDK_SHIFT_MASK)
170                         gtk_notebook_set_current_page(tabs, (cur_tab-1)%num_tabs);
171                 else 
172                         gtk_notebook_set_current_page(tabs, (cur_tab+1)%num_tabs);
173         };
174         return FALSE;
175 }
176
177 void on_quit(GtkMenuItem *menu, AWeatherGui *self)
178 {
179         gtk_widget_destroy(GTK_WIDGET(self));
180 }
181
182 void on_zoomin(GtkAction *action, AWeatherGui *self)
183 {
184         gis_view_zoom(self->view, 3./4);
185 }
186
187 void on_zoomout(GtkAction *action, AWeatherGui *self)
188 {
189         gis_view_zoom(self->view, 4./3);
190 }
191
192 void on_refresh(GtkAction *action, AWeatherGui *self)
193 {
194         gis_world_refresh(self->world);
195 }
196
197 void load_window(gchar *name, AWeatherGui *self)
198 {
199         // TODO: use gtk_widget_hide_on_delete()
200         char *objects[2] = {name, NULL};
201         gtk_builder_add_objects_from_file(self->builder,
202                         DATADIR "/aweather/main.ui", objects, NULL);
203         gtk_builder_connect_signals(self->builder, self);
204         GtkWidget *main_win = aweather_gui_get_widget(self, "main_window");
205         GtkWidget *this_win = aweather_gui_get_widget(self, name);
206         gtk_window_set_transient_for(GTK_WINDOW(this_win), GTK_WINDOW(main_win));
207         gtk_widget_show_all(this_win);
208 }
209
210 void on_about(GtkAction *action, AWeatherGui *self)
211 {
212         load_window("about_window", self);
213 }
214
215 void on_plugin_toggled(GtkCellRendererToggle *cell, gchar *path_str, AWeatherGui *self)
216 {
217         GtkWidget    *tview = aweather_gui_get_widget(self, "plugins_view");
218         GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(tview));
219         g_message("model=%p", model);
220         gboolean state;
221         gchar *so;
222         GtkTreeIter iter;
223         gtk_tree_model_get_iter_from_string(model, &iter, path_str);
224         gtk_tree_model_get(model, &iter, 0, &so, 1, &state, -1);
225         state = !state;
226         gtk_list_store_set(GTK_LIST_STORE(model), &iter, 1, state, -1);
227         g_strdelimit(so, ".", '\0');
228         if (state)
229                 aweather_gui_load_plugin(self, so);
230         else
231                 aweather_gui_unload_plugin(self, so);
232         g_free(so);
233         gtk_widget_show_all(aweather_gui_get_widget(self, "tabs"));
234 }
235 void on_prefs(GtkAction *action, AWeatherGui *self)
236 {
237         /* get prefs */
238         gchar *is = g_key_file_get_string(self->prefs,  "general", "initial_site", NULL);
239         gchar *nu = g_key_file_get_string(self->prefs,  "general", "nexrad_url",   NULL);
240         gint   ll = g_key_file_get_integer(self->prefs, "general", "log_level",    NULL);
241         load_window("prefs_window", self);
242         /* set prefs */
243         GtkWidget *isw = aweather_gui_get_widget(self, "initial_site");
244         GtkWidget *nuw = aweather_gui_get_widget(self, "nexrad_url");
245         GtkWidget *llw = aweather_gui_get_widget(self, "log_level");
246         if (is) gtk_entry_set_text(GTK_ENTRY(isw), is), g_free(is);
247         if (nu) gtk_entry_set_text(GTK_ENTRY(nuw), nu), g_free(nu);
248         if (ll) gtk_spin_button_set_value(GTK_SPIN_BUTTON(llw), ll);
249
250         /* Plugins */
251         GtkTreeView       *tview = GTK_TREE_VIEW(aweather_gui_get_widget(self, "plugins_view"));
252         GtkCellRenderer   *rend1 = gtk_cell_renderer_text_new();
253         GtkCellRenderer   *rend2 = gtk_cell_renderer_toggle_new();
254         GtkTreeViewColumn *col1  = gtk_tree_view_column_new_with_attributes(
255                         "Plugin",  rend1, "text",   0, NULL);
256         GtkTreeViewColumn *col2  = gtk_tree_view_column_new_with_attributes(
257                         "Enabled", rend2, "active", 1, NULL);
258         gtk_tree_view_append_column(tview, col1);
259         gtk_tree_view_append_column(tview, col2);
260         g_signal_connect(rend2, "toggled", G_CALLBACK(on_plugin_toggled), self);
261         gtk_tree_view_set_model(GTK_TREE_VIEW(tview), GTK_TREE_MODEL(self->gtk_plugins));
262 }
263
264 void on_time_changed(GtkTreeView *view, GtkTreePath *path,
265                 GtkTreeViewColumn *column, AWeatherGui *self)
266 {
267         gchar *time;
268         GtkTreeIter iter;
269         GtkTreeModel *model = gtk_tree_view_get_model(view);
270         gtk_tree_model_get_iter(model, &iter, path);
271         gtk_tree_model_get(model, &iter, 0, &time, -1);
272         gis_view_set_time(self->view, time);
273         g_free(time);
274 }
275
276 void on_site_changed(GtkComboBox *combo, AWeatherGui *self)
277 {
278         gchar *site;
279         GtkTreeIter iter;
280         GtkTreeModel *model = gtk_combo_box_get_model(combo);
281         gtk_combo_box_get_active_iter(combo, &iter);
282         gtk_tree_model_get(model, &iter, 1, &site, -1);
283         gis_view_set_site(self->view, site);
284         g_free(site);
285 }
286
287 /* TODO: replace the code in these with `gtk_tree_model_find' utility */
288 static void update_time_widget(GisView *view, const char *time, AWeatherGui *self)
289 {
290         g_debug("AWeatherGui: update_time_widget - time=%s", time);
291         GtkTreeView  *tview = GTK_TREE_VIEW(aweather_gui_get_widget(self, "time"));
292         GtkTreeModel *model = GTK_TREE_MODEL(gtk_tree_view_get_model(tview));
293         for (int i = 0; i < gtk_tree_model_iter_n_children(model, NULL); i++) {
294                 char *text;
295                 GtkTreeIter iter;
296                 gtk_tree_model_iter_nth_child(model, &iter, NULL, i);
297                 gtk_tree_model_get(model, &iter, 0, &text, -1);
298                 if (g_str_equal(text, time)) {
299                         GtkTreePath *path = gtk_tree_model_get_path(model, &iter);
300                         g_signal_handlers_block_by_func(tview,
301                                         G_CALLBACK(on_site_changed), self);
302                         gtk_tree_view_set_cursor(tview, path, NULL, FALSE);
303                         g_signal_handlers_unblock_by_func(tview,
304                                         G_CALLBACK(on_site_changed), self);
305                         gtk_tree_path_free(path);
306                         g_free(text);
307                         return;
308                 }
309                 g_free(text);
310         }
311 }
312 static void update_site_widget(GisView *view, char *site, AWeatherGui *self)
313 {
314         g_debug("AWeatherGui: update_site_widget - site=%s", site);
315         GtkComboBox  *combo = GTK_COMBO_BOX(aweather_gui_get_widget(self, "site"));
316         GtkTreeModel *model = GTK_TREE_MODEL(gtk_combo_box_get_model(combo));
317         for (int i = 0; i < gtk_tree_model_iter_n_children(model, NULL); i++) {
318                 GtkTreeIter iter1;
319                 gtk_tree_model_iter_nth_child(model, &iter1, NULL, i);
320                 for (int i = 0; i < gtk_tree_model_iter_n_children(model, &iter1); i++) {
321                         GtkTreeIter iter2;
322                         gtk_tree_model_iter_nth_child(model, &iter2, &iter1, i);
323                         char *text;
324                         gtk_tree_model_get(model, &iter2, 1, &text, -1);
325                         if (text == NULL)
326                                 continue;
327                         if (g_str_equal(text, site)) {
328                                 g_signal_handlers_block_by_func(combo,
329                                                 G_CALLBACK(on_site_changed), self);
330                                 gtk_combo_box_set_active_iter(combo, &iter2);
331                                 g_signal_handlers_unblock_by_func(combo,
332                                                 G_CALLBACK(on_site_changed), self);
333                                 g_free(text);
334                                 return;
335                         }
336                         g_free(text);
337                 }
338         }
339 }
340 /* Prefs callbacks */
341 void on_offline(GtkToggleAction *action, AWeatherGui *self)
342 {
343         gboolean value = gtk_toggle_action_get_active(action);
344         g_debug("AWeatherGui: on_offline - offline=%d", value);
345         g_key_file_set_boolean(self->prefs, "general", "offline", value);
346         gis_world_set_offline(self->world, value);
347 }
348 void on_initial_site_changed(GtkEntry *entry, AWeatherGui *self)
349 {
350         const gchar *text = gtk_entry_get_text(entry);
351         g_debug("AWeatherGui: on_initial_site_changed - site=%s", text);
352         g_key_file_set_string(self->prefs, "general", "initial_site", text);
353 }
354 void on_nexrad_url_changed(GtkEntry *entry, AWeatherGui *self)
355 {
356         const gchar *text = gtk_entry_get_text(entry);
357         g_debug("AWeatherGui: on_nexrad_url_changed - url=%s", text);
358         g_key_file_set_string(self->prefs, "general", "nexrad_url", text);
359 }
360 int on_log_level_changed(GtkSpinButton *spinner, AWeatherGui *self)
361 {
362         gint value = gtk_spin_button_get_value_as_int(spinner);
363         g_debug("AWeatherGui: on_log_level_changed - %p, level=%d", self, value);
364         g_key_file_set_integer(self->prefs, "general", "log_level", value);
365         g_debug("test");
366         return TRUE;
367 }
368 // plugins
369
370 /*****************
371  * Setup helpers *
372  *****************/
373 static void combo_sensitive(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
374                 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
375 {
376         gboolean sensitive = !gtk_tree_model_iter_has_child(tree_model, iter);
377         g_object_set(cell, "sensitive", sensitive, NULL);
378 }
379
380 static void site_setup(AWeatherGui *self)
381 {
382         GtkTreeIter state, city;
383         GtkTreeStore *store = GTK_TREE_STORE(aweather_gui_get_object(self, "sites"));
384         for (int i = 0; cities[i].label; i++) {
385                 if (cities[i].type == LOCATION_STATE) {
386                         gtk_tree_store_append(store, &state, NULL);
387                         gtk_tree_store_set   (store, &state, 0, cities[i].label, 
388                                                              1, cities[i].code,  -1);
389                 } else {
390                         gtk_tree_store_append(store, &city, &state);
391                         gtk_tree_store_set   (store, &city, 0, cities[i].label, 
392                                                             1, cities[i].code,  -1);
393                 }
394         }
395
396         GtkWidget *combo    = aweather_gui_get_widget(self, "site");
397         GObject   *renderer = aweather_gui_get_object(self, "site_rend");
398         gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(combo),
399                         GTK_CELL_RENDERER(renderer), combo_sensitive, NULL, NULL);
400
401         g_signal_connect(self->view, "site-changed",
402                         G_CALLBACK(update_site_widget), self);
403 }
404
405 static void time_setup(AWeatherGui *self)
406 {
407         GtkTreeView       *tview = GTK_TREE_VIEW(aweather_gui_get_widget(self, "time"));
408         GtkCellRenderer   *rend  = gtk_cell_renderer_text_new();
409         GtkTreeViewColumn *col   = gtk_tree_view_column_new_with_attributes(
410                                         "Time", rend, "text", 0, NULL);
411
412         gtk_tree_view_append_column(tview, col);
413         g_object_set(rend, "size-points", 8.0, NULL);
414
415         g_signal_connect(self->view, "time-changed",
416                         G_CALLBACK(update_time_widget), self);
417 }
418
419
420 /***********
421  * Methods *
422  ***********/
423 AWeatherGui *aweather_gui_new()
424 {
425         g_debug("AWeatherGui: new");
426         return g_object_new(AWEATHER_TYPE_GUI, NULL);
427 }
428 GisWorld *aweather_gui_get_world(AWeatherGui *self)
429 {
430         g_assert(AWEATHER_IS_GUI(self));
431         return self->world;
432 }
433 GisView *aweather_gui_get_view(AWeatherGui *self)
434 {
435         g_assert(AWEATHER_IS_GUI(self));
436         return self->view;
437 }
438 GisOpenGL *aweather_gui_get_opengl(AWeatherGui *self)
439 {
440         g_assert(AWEATHER_IS_GUI(self));
441         return self->opengl;
442 }
443 GtkBuilder *aweather_gui_get_builder(AWeatherGui *self)
444 {
445         g_debug("AWeatherGui: get_builder");
446         g_assert(AWEATHER_IS_GUI(self));
447         return self->builder;
448 }
449 GtkWidget *aweather_gui_get_widget(AWeatherGui *self, const gchar *name)
450 {
451         g_debug("AWeatherGui: get_widget - name=%s", name);
452         g_assert(AWEATHER_IS_GUI(self));
453         GObject *widget = gtk_builder_get_object(self->builder, name);
454         if (!GTK_IS_WIDGET(widget))
455                 g_error("Failed to get widget `%s'", name);
456         return GTK_WIDGET(widget);
457 }
458 GObject *aweather_gui_get_object(AWeatherGui *self, const gchar *name)
459 {
460         g_debug("AWeatherGui: get_widget - name=%s", name);
461         g_assert(AWEATHER_IS_GUI(self));
462         return gtk_builder_get_object(self->builder, name);
463 }
464 gboolean aweather_gui_load_plugin(AWeatherGui *self, const char *name)
465 {
466         gchar *path = g_strdup_printf("%s/%s.%s", PLUGINDIR, name, G_MODULE_SUFFIX);
467         GModule *module = g_module_open(path, 0);
468         g_free(path);
469         if (module == NULL) {
470                 g_warning("Unable to load module %s: %s", name, g_module_error());
471                 return FALSE;
472         }
473
474         AWeatherPlugin *(*constructor)();
475         gchar *constructor_str = g_strconcat("aweather_", name, "_new", NULL);
476         if (!g_module_symbol(module, constructor_str, (gpointer*)&constructor)) {
477                 g_warning("Unable to load symbol %s from %s: %s",
478                                 constructor_str, name, g_module_error());
479                 g_module_close(module);
480                 g_free(constructor_str);
481                 return FALSE;
482         }
483         g_free(constructor_str);
484
485         self->plugins = g_list_append(self->plugins, constructor(self));
486         self->opengl->plugins = self->plugins;
487         return TRUE;
488 }
489 gboolean aweather_gui_unload_plugin(AWeatherGui *self, const char *name)
490 {
491         return FALSE;
492 }