]> Pileus Git - grits/blob - src/grits-viewer.c
Add thread safe grits_viewer_queue_draw function
[grits] / src / grits-viewer.c
1 /*
2  * Copyright (C) 2009-2011 Andy Spencer <andy753421@gmail.com>
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 /**
19  * SECTION:grits-viewer
20  * @short_description: Virtual globe base class
21  *
22  * #GritsViewer is the base class for the virtual globe widget. It handles
23  * everything not directly related to drawing the globe. Plugins and
24  * applications using the viewer should normally talk to the viewer and not care
25  * how it is implemented. 
26  */
27
28 #include <config.h>
29 #include <math.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdkkeysyms.h>
32
33 #include "grits-marshal.h"
34 #include "grits-viewer.h"
35
36 #include "grits-util.h"
37
38
39 /* Constants */
40 enum {
41         SIG_TIME_CHANGED,
42         SIG_LOCATION_CHANGED,
43         SIG_ROTATION_CHANGED,
44         SIG_REFRESH,
45         SIG_OFFLINE,
46         NUM_SIGNALS,
47 };
48 static guint signals[NUM_SIGNALS];
49
50
51 /***********
52  * Helpers *
53  ***********/
54 /* Misc helpers */
55 static void _grits_viewer_fix_location(GritsViewer *viewer)
56 {
57         while (viewer->location[0] <  -90) viewer->location[0] += 180;
58         while (viewer->location[0] >   90) viewer->location[0] -= 180;
59         while (viewer->location[1] < -180) viewer->location[1] += 360;
60         while (viewer->location[1] >  180) viewer->location[1] -= 360;
61         viewer->location[2] = ABS(viewer->location[2]);
62 }
63
64 static void _grits_viewer_fix_rotation(GritsViewer *viewer)
65 {
66         while (viewer->rotation[0] < -180) viewer->rotation[0] += 360;
67         while (viewer->rotation[0] >  180) viewer->rotation[0] -= 360;
68         while (viewer->rotation[1] < -180) viewer->rotation[1] += 360;
69         while (viewer->rotation[1] >  180) viewer->rotation[1] -= 360;
70         while (viewer->rotation[2] < -180) viewer->rotation[2] += 360;
71         while (viewer->rotation[2] >  180) viewer->rotation[2] -= 360;
72 }
73
74 static gboolean _grits_viewer_queue_draw_cb(gpointer _viewer)
75 {
76         GritsViewer *viewer = _viewer;
77         gtk_widget_queue_draw(GTK_WIDGET(viewer));
78         viewer->draw_source = 0;
79         return FALSE;
80 }
81
82 /* Signal helpers */
83 static void _grits_viewer_emit_location_changed(GritsViewer *viewer)
84 {
85         g_signal_emit(viewer, signals[SIG_LOCATION_CHANGED], 0,
86                         viewer->location[0],
87                         viewer->location[1],
88                         viewer->location[2]);
89 }
90 static void _grits_viewer_emit_rotation_changed(GritsViewer *viewer)
91 {
92         g_signal_emit(viewer, signals[SIG_ROTATION_CHANGED], 0,
93                         viewer->rotation[0],
94                         viewer->rotation[1],
95                         viewer->rotation[2]);
96 }
97 static void _grits_viewer_emit_time_changed(GritsViewer *viewer)
98 {
99         g_signal_emit(viewer, signals[SIG_TIME_CHANGED], 0,
100                         viewer->time);
101 }
102 static void _grits_viewer_emit_refresh(GritsViewer *viewer)
103 {
104         g_signal_emit(viewer, signals[SIG_REFRESH], 0);
105 }
106 static void _grits_viewer_emit_offline(GritsViewer *viewer)
107 {
108         g_signal_emit(viewer, signals[SIG_OFFLINE], 0,
109                         viewer->offline);
110 }
111
112 /*************
113  * Callbacks *
114  *************/
115 static gboolean on_key_press(GritsViewer *viewer, GdkEventKey *event, gpointer _)
116 {
117         g_debug("GritsViewer: on_key_press - key=%x, state=%x, plus=%x",
118                         event->keyval, event->state, GDK_plus);
119
120         double lat, lon, elev, pan;
121         grits_viewer_get_location(viewer, &lat, &lon, &elev);
122         pan = MIN(elev/(EARTH_R/2), 30);
123         switch (event->keyval) {
124         case GDK_Left:  case GDK_h: grits_viewer_pan(viewer,  0,  -pan, 0); break;
125         case GDK_Down:  case GDK_j: grits_viewer_pan(viewer, -pan, 0,   0); break;
126         case GDK_Up:    case GDK_k: grits_viewer_pan(viewer,  pan, 0,   0); break;
127         case GDK_Right: case GDK_l: grits_viewer_pan(viewer,  0,   pan, 0); break;
128         case GDK_minus: case GDK_o: grits_viewer_zoom(viewer, 10./9); break;
129         case GDK_plus:  case GDK_i: grits_viewer_zoom(viewer, 9./10); break;
130         case GDK_H: grits_viewer_rotate(viewer,  0, 0, -2); break;
131         case GDK_J: grits_viewer_rotate(viewer,  2, 0,  0); break;
132         case GDK_K: grits_viewer_rotate(viewer, -2, 0,  0); break;
133         case GDK_L: grits_viewer_rotate(viewer,  0, 0,  2); break;
134         }
135         return FALSE;
136 }
137
138 static gboolean on_scroll(GritsViewer *viewer, GdkEventScroll *event, gpointer _)
139 {
140         switch (event->direction) {
141         case GDK_SCROLL_DOWN: grits_viewer_zoom(viewer, 10./9); break;
142         case GDK_SCROLL_UP:   grits_viewer_zoom(viewer, 9./10); break;
143         default: break;
144         }
145         return FALSE;
146 }
147
148 enum {
149         GRITS_DRAG_NONE,
150         GRITS_DRAG_PAN,
151         GRITS_DRAG_ZOOM,
152         GRITS_DRAG_TILT,
153 };
154
155 static gboolean on_button_press(GritsViewer *viewer, GdkEventButton *event, gpointer _)
156 {
157         g_debug("GritsViewer: on_button_press - %d", event->button);
158         gtk_widget_grab_focus(GTK_WIDGET(viewer));
159         switch (event->button) {
160         case 1:  viewer->drag_mode = GRITS_DRAG_PAN;  break;
161         case 2:  viewer->drag_mode = GRITS_DRAG_ZOOM; break;
162         case 3:  viewer->drag_mode = GRITS_DRAG_TILT; break;
163         default: viewer->drag_mode = GRITS_DRAG_NONE; break;
164         }
165         viewer->drag_x = event->x;
166         viewer->drag_y = event->y;
167         return FALSE;
168 }
169
170 static gboolean on_button_release(GritsViewer *viewer, GdkEventButton *event, gpointer _)
171 {
172         g_debug("GritsViewer: on_button_release");
173         viewer->drag_mode = GRITS_DRAG_NONE;
174         return FALSE;
175 }
176
177 static gboolean on_motion_notify(GritsViewer *viewer, GdkEventMotion *event, gpointer _)
178 {
179         gdouble x = viewer->drag_x - event->x;
180         gdouble y = viewer->drag_y - event->y;
181         gdouble lat, lon, elev, scale, rx, ry, rz;
182         grits_viewer_get_location(GRITS_VIEWER(viewer), &lat, &lon, &elev);
183         grits_viewer_get_rotation(GRITS_VIEWER(viewer), &rx,  &ry,  &rz);
184         scale = (elev/EARTH_R/14.1) * (sin(deg2rad(ABS(rx)))*4+1);
185         switch (viewer->drag_mode) {
186         case GRITS_DRAG_PAN:  grits_viewer_pan(viewer, -y*scale*0.782, x*scale, 0); break;
187         case GRITS_DRAG_ZOOM: grits_viewer_zoom(viewer, pow(2, -y/500)); break;
188         case GRITS_DRAG_TILT: grits_viewer_rotate(viewer, y/10, 0, x/10); break;
189         }
190         viewer->drag_x = event->x;
191         viewer->drag_y = event->y;
192         return FALSE;
193 }
194
195 static void on_view_changed(GritsViewer *viewer,
196                 gdouble _1, gdouble _2, gdouble _3)
197 {
198         grits_viewer_queue_draw(viewer);
199 }
200
201 /***********
202  * Methods *
203  ***********/
204 /**
205  * grits_viewer_setup:
206  * @viewer:  the viewer
207  * @plugins: a plugins store
208  * @prefs:   a prefs store
209  *
210  * This should be called by objects which implement GritsViewer somewhere in their
211  * constructor.
212  */
213 void grits_viewer_setup(GritsViewer *viewer, GritsPlugins *plugins, GritsPrefs *prefs)
214 {
215         viewer->plugins = plugins;
216         viewer->prefs   = prefs;
217         viewer->offline = grits_prefs_get_boolean(prefs, "grits/offline", NULL);
218 }
219
220 /**
221  * grits_viewer_set_time:
222  * @viewer: the viewer
223  * @time: the time to set the view to
224  *
225  * Set the current time for the view
226  */
227 void grits_viewer_set_time(GritsViewer *viewer, time_t time)
228 {
229         g_assert(GRITS_IS_VIEWER(viewer));
230         g_debug("GritsViewer: set_time - time=%ld", time);
231         viewer->time = time;
232         _grits_viewer_emit_time_changed(viewer);
233 }
234
235 /**
236  * grits_viewer_get_time:
237  * @viewer: the viewer
238  * 
239  * Get the time that is being viewed
240  *
241  * Returns: the current time
242  */
243 time_t grits_viewer_get_time(GritsViewer *viewer)
244 {
245         g_assert(GRITS_IS_VIEWER(viewer));
246         g_debug("GritsViewer: get_time");
247         return viewer->time;
248 }
249
250 /**
251  * grits_viewer_set_location:
252  * @viewer: the viewer
253  * @lat:  the new latitude
254  * @lon:  the new longitude
255  * @elev: the new elevation
256  *
257  * Set the location for the camera
258  */
259 void grits_viewer_set_location(GritsViewer *viewer, gdouble lat, gdouble lon, gdouble elev)
260 {
261         g_assert(GRITS_IS_VIEWER(viewer));
262         g_debug("GritsViewer: set_location");
263         viewer->location[0] = lat;
264         viewer->location[1] = lon;
265         viewer->location[2] = elev;
266         _grits_viewer_fix_location(viewer);
267         _grits_viewer_emit_location_changed(viewer);
268 }
269
270 /**
271  * grits_viewer_get_location:
272  * @viewer: the viewer
273  * @lat:  the location to store the latitude
274  * @lon:  the location to store the longitude
275  * @elev: the location to store the elevation
276  *
277  * Get the location of the camera
278  */
279 void grits_viewer_get_location(GritsViewer *viewer, gdouble *lat, gdouble *lon, gdouble *elev)
280 {
281         g_assert(GRITS_IS_VIEWER(viewer));
282         //g_debug("GritsViewer: get_location");
283         *lat  = viewer->location[0];
284         *lon  = viewer->location[1];
285         *elev = viewer->location[2];
286 }
287
288 /**
289  * grits_viewer_pan:
290  * @viewer: the viewer
291  * @forward:  distance to move forward in meters
292  * @right:    distance to move right in meters
293  * @up:       distance to move up in meters
294  *
295  * Pan the location by a number of meters long the surface.
296  *
297  * Bugs: the distances are not in meters
298  * Bugs: panning does not move in strait lines
299  */
300 void grits_viewer_pan(GritsViewer *viewer, gdouble forward, gdouble sideways, gdouble up)
301 {
302         g_assert(GRITS_IS_VIEWER(viewer));
303         g_debug("GritsViewer: pan - forward=%8.3f, sideways=%8.3f, up=%8.3f",
304                         forward, sideways, up);
305         gdouble dist   = sqrt(forward*forward + sideways*sideways);
306         gdouble angle1 = deg2rad(viewer->rotation[2]);
307         gdouble angle2 = atan2(sideways, forward);
308         gdouble angle  = angle1 + angle2;
309         /* This isn't accurate, but it's usable */
310         viewer->location[0] += dist*cos(angle);
311         viewer->location[1] += dist*sin(angle);
312         viewer->location[2] += up;
313         _grits_viewer_fix_location(viewer);
314         _grits_viewer_emit_location_changed(viewer);
315 }
316
317 /**
318  * grits_viewer_zoom:
319  * @viewer: the viewer
320  * @scale: the scale to multiple the elevation by
321  *
322  * Multiple the elevation by a scale.
323  */
324 void grits_viewer_zoom(GritsViewer *viewer, gdouble scale)
325 {
326         g_assert(GRITS_IS_VIEWER(viewer));
327         g_debug("GritsViewer: zoom");
328         viewer->location[2] *= scale;
329         _grits_viewer_emit_location_changed(viewer);
330 }
331
332 /**
333  * grits_viewer_set_rotation:
334  * @viewer: the viewer
335  * @x: rotation new around the x axes
336  * @y: rotation new around the y axes
337  * @z: rotation new around the z axes
338  *
339  * Set the rotations in degrees around the x, y, and z axes.
340  */
341 void grits_viewer_set_rotation(GritsViewer *viewer, gdouble x, gdouble y, gdouble z)
342 {
343         g_assert(GRITS_IS_VIEWER(viewer));
344         g_debug("GritsViewer: set_rotation");
345         viewer->rotation[0] = x;
346         viewer->rotation[1] = y;
347         viewer->rotation[2] = z;
348         _grits_viewer_fix_rotation(viewer);
349         _grits_viewer_emit_rotation_changed(viewer);
350 }
351
352 /**
353  * grits_viewer_get_rotation:
354  * @viewer: the viewer
355  * @x: rotation around the x axes
356  * @y: rotation around the y axes
357  * @z: rotation around the z axes
358  *
359  * Get the rotations in degrees around the x, y, and z axes.
360  */
361 void grits_viewer_get_rotation(GritsViewer *viewer, gdouble *x, gdouble *y, gdouble *z)
362 {
363         g_assert(GRITS_IS_VIEWER(viewer));
364         //g_debug("GritsViewer: get_rotation");
365         *x = viewer->rotation[0];
366         *y = viewer->rotation[1];
367         *z = viewer->rotation[2];
368 }
369
370 /**
371  * grits_viewer_rotate:
372  * @viewer: the viewer
373  * @x: rotation around the x axes
374  * @y: rotation around the y axes
375  * @z: rotation around the z axes
376  *
377  * Add to the rotation around the x, y, and z axes.
378  */
379 void grits_viewer_rotate(GritsViewer *viewer, gdouble x, gdouble y, gdouble z)
380 {
381         g_assert(GRITS_IS_VIEWER(viewer));
382         g_debug("GritsViewer: rotate - x=%.0f, y=%.0f, z=%.0f", x, y, z);
383         viewer->rotation[0] += x;
384         viewer->rotation[1] += y;
385         viewer->rotation[2] += z;
386         _grits_viewer_fix_rotation(viewer);
387         _grits_viewer_emit_rotation_changed(viewer);
388 }
389
390 /**
391  * grits_viewer_refresh:
392  * @viewer: the viewer
393  *
394  * Trigger the refresh signal. This will cause any remote data to be checked for
395  * updates. 
396  */
397 void grits_viewer_refresh(GritsViewer *viewer)
398 {
399         g_debug("GritsViewer: refresh");
400         _grits_viewer_emit_refresh(viewer);
401 }
402
403 /**
404  * grits_viewer_set_offline:
405  * @viewer: the viewer
406  * @offline: %TRUE to enter offline mode
407  *
408  * Set the offline mode. If @offline is %TRUE, only locally cached data will be
409  * used.
410  */
411 void grits_viewer_set_offline(GritsViewer *viewer, gboolean offline)
412 {
413         g_assert(GRITS_IS_VIEWER(viewer));
414         g_debug("GritsViewer: set_offline - %d", offline);
415         grits_prefs_set_boolean(viewer->prefs, "grits/offline", offline);
416         viewer->offline = offline;
417         _grits_viewer_emit_offline(viewer);
418 }
419
420 /**
421  * grits_viewer_get_offline:
422  * @viewer: the viewer
423  *
424  * Check if the viewer is in offline mode.
425  *
426  * Returns: %TRUE if the viewer is in offline mode.
427  */
428 gboolean grits_viewer_get_offline(GritsViewer *viewer)
429 {
430         g_assert(GRITS_IS_VIEWER(viewer));
431         g_debug("GritsViewer: get_offline - %d", viewer->offline);
432         return viewer->offline;
433 }
434
435 /**
436  * grits_viewer_queue_draw:
437  * @viewer: the viewer
438  *
439  * Causes the viewer to redraw the screen. This has the safe effect as
440  * gtk_widget_queue_draw, but is thread safe, and probably faster.
441  */
442 void grits_viewer_queue_draw(GritsViewer *viewer)
443 {
444         if (!viewer->draw_source)
445                 viewer->draw_source = g_idle_add_full(G_PRIORITY_HIGH,
446                                 _grits_viewer_queue_draw_cb, viewer, NULL);
447 }
448
449 /***********************************
450  * To be implemented by subclasses *
451  ***********************************/
452 /**
453  * grits_viewer_center_position:
454  * @viewer: the viewer
455  * @lat:  the latitude
456  * @lon:  the longitude
457  * @elev: the elevation
458  *
459  * Center the viewer on a point. This can be used before drawing operations to
460  * center the items a particular location.
461  */
462 void grits_viewer_center_position(GritsViewer *viewer,
463                 gdouble lat, gdouble lon, gdouble elev)
464 {
465         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
466         if (!klass->center_position)
467                 g_warning("GritsViewer: center_position - Unimplemented");
468         klass->center_position(viewer, lat, lon, elev);
469 }
470
471 /**
472  * grits_viewer_project:
473  * @viewer: the viewer
474  * @lat:  the latitude
475  * @lon:  the longitude
476  * @elev: the elevation
477  * @px:   the project x coordinate
478  * @py:   the project y coordinate
479  * @pz:   the project z coordinate
480  *
481  * Project a latitude, longitude, elevation point to to x, y, and z coordinates
482  * in screen space. Useful for drawing orthographic data over a particular point
483  * in space. E.g. #GritsMarker.
484  */
485 void grits_viewer_project(GritsViewer *viewer,
486                 gdouble lat, gdouble lon, gdouble elev,
487                 gdouble *px, gdouble *py, gdouble *pz)
488 {
489         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
490         if (!klass->project)
491                 g_warning("GritsViewer: project - Unimplemented");
492         klass->project(viewer, lat, lon, elev, px, py, pz);
493 }
494
495 /**
496  * grits_viewer_unproject:
497  * @viewer: the viewer
498  * @x:    x coordinate in screen space
499  * @y:    y coordinate in screen space
500  * @z:    z coordinate in screen space, or -1 to use the value
501  *        from the depth buffer at x and y as the z value
502  * @lat:  the latitude
503  * @lon:  the longitude
504  * @elev: the elevation
505  *
506  * Project a x, y point in screen space to a latitude, longitude, and elevation
507  * point. Useful for finding the position of the cursor or another on-screen
508  * object in world coordinates.
509  */
510 void grits_viewer_unproject(GritsViewer *viewer,
511                 gdouble px, gdouble py, gdouble pz,
512                 gdouble *lat, gdouble *lon, gdouble *elev)
513 {
514         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
515         if (!klass->unproject)
516                 g_warning("GritsViewer: unproject - Unimplemented");
517         klass->unproject(viewer, px, py, pz, lat, lon, elev);
518 }
519
520 /**
521  * grits_viewer_clear_height_func:
522  * @viewer: the viewer
523  *
524  * Clears the height function for the entire viewer. Useful when an elevation
525  * plugin is unloaded.
526  */
527 void grits_viewer_clear_height_func(GritsViewer *viewer)
528 {
529         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
530         if (!klass->clear_height_func)
531                 g_warning("GritsViewer: clear_height_func - Unimplemented");
532         klass->clear_height_func(viewer);
533 }
534
535 /**
536  * grits_viewer_set_height_func:
537  * @viewer:      the viewer
538  * @bounds:      the area to set the height function for
539  * @height_func: the height function 
540  * @user_data:   user data to pass to the height function
541  * @update:      %TRUE if the heights inside the bounds should be updated.
542  *
543  * Set the height function to be used for a given part of the surface..
544  */
545 void grits_viewer_set_height_func(GritsViewer *viewer, GritsBounds *bounds,
546                 GritsHeightFunc height_func, gpointer user_data,
547                 gboolean update)
548 {
549         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
550         if (!klass->set_height_func)
551                 g_warning("GritsViewer: set_height_func - Unimplemented");
552         klass->set_height_func(viewer, bounds, height_func, user_data, update);
553 }
554
555 /**
556  * grits_viewer_add:
557  * @viewer: the viewer
558  * @object: the object to add
559  * @level:  the level to add the object to
560  * @sort:   %TRUE if the object should be depth-sorted prior to being drawn
561  *
562  * Objects which are added to the viewer will be drawn on subsequent renderings
563  * if their level of details is adequate.
564  *
565  * The @level represents the order the object should be drawn in, this is
566  * unrelated to the objects actual position in the world.
567  *
568  * Semi-transparent objects should set @sort to %TRUE so that they are rendered
569  * correctly when they overlap other semi-transparent objects.
570  *
571  * The viewer steals the objects reference. Call g_object_ref if you plan on
572  * holding a reference as well.
573  */
574 void grits_viewer_add(GritsViewer *viewer, GritsObject *object,
575                 gint level, gboolean sort)
576 {
577         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
578         if (!klass->add)
579                 g_warning("GritsViewer: add - Unimplemented");
580         object->viewer = viewer;
581         klass->add(viewer, object, level, sort);
582 }
583
584 /**
585  * grits_viewer_remove:
586  * @viewer: the viewer
587  * @object: the object to remove
588  *
589  * Remove an object from the viewer.
590  */
591 void grits_viewer_remove(GritsViewer *viewer, GritsObject *object)
592 {
593         GritsViewerClass *klass = GRITS_VIEWER_GET_CLASS(viewer);
594         if (!object->viewer)
595                 return;
596         if (!klass->remove)
597                 g_warning("GritsViewer: remove - Unimplemented");
598         object->viewer = NULL;
599         klass->remove(viewer, object);
600 }
601
602 /****************
603  * GObject code *
604  ****************/
605 G_DEFINE_ABSTRACT_TYPE(GritsViewer, grits_viewer, GTK_TYPE_DRAWING_AREA);
606 static void grits_viewer_init(GritsViewer *viewer)
607 {
608         g_debug("GritsViewer: init");
609         /* Default values */
610         viewer->time        = time(NULL);
611         viewer->location[0] = 40;
612         viewer->location[1] = -100;
613         viewer->location[2] = EARTH_R;
614         viewer->rotation[0] = 0;
615         viewer->rotation[1] = 0;
616         viewer->rotation[2] = 0;
617
618         g_object_set(viewer, "can-focus", TRUE, NULL);
619         gtk_widget_add_events(GTK_WIDGET(viewer),
620                         GDK_BUTTON_PRESS_MASK |
621                         GDK_BUTTON_RELEASE_MASK |
622                         GDK_POINTER_MOTION_MASK |
623                         GDK_KEY_PRESS_MASK);
624
625         g_signal_connect(viewer, "key-press-event",      G_CALLBACK(on_key_press),      NULL);
626         g_signal_connect(viewer, "scroll-event",         G_CALLBACK(on_scroll),         NULL);
627
628         g_signal_connect(viewer, "button-press-event",   G_CALLBACK(on_button_press),   NULL);
629         g_signal_connect(viewer, "button-release-event", G_CALLBACK(on_button_release), NULL);
630         g_signal_connect(viewer, "motion-notify-event",  G_CALLBACK(on_motion_notify),  NULL);
631
632         g_signal_connect(viewer, "location-changed",     G_CALLBACK(on_view_changed),   NULL);
633         g_signal_connect(viewer, "rotation-changed",     G_CALLBACK(on_view_changed),   NULL);
634 }
635 static void grits_viewer_dispose(GObject *gobject)
636 {
637         g_debug("GritsViewer: dispose");
638         GritsViewer *viewer = GRITS_VIEWER(gobject);
639         if (viewer->draw_source)
640                 g_source_remove(viewer->draw_source);
641         G_OBJECT_CLASS(grits_viewer_parent_class)->dispose(gobject);
642 }
643 static void grits_viewer_finalize(GObject *gobject)
644 {
645         g_debug("GritsViewer: finalize");
646         G_OBJECT_CLASS(grits_viewer_parent_class)->finalize(gobject);
647         g_debug("GritsViewer: finalize - done");
648 }
649 static void grits_viewer_class_init(GritsViewerClass *klass)
650 {
651         g_debug("GritsViewer: class_init");
652         GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
653         gobject_class->dispose      = grits_viewer_dispose;
654         gobject_class->finalize     = grits_viewer_finalize;
655
656         /**
657          * GritsViewer::time-changed:
658          * @viewer: the viewer.
659          * @time:   the new time.
660          *
661          * The ::time-changed signal is emitted when the viewers current time
662          * changers.
663          */
664         signals[SIG_TIME_CHANGED] = g_signal_new(
665                         "time-changed",
666                         G_TYPE_FROM_CLASS(gobject_class),
667                         G_SIGNAL_RUN_LAST,
668                         0,
669                         NULL,
670                         NULL,
671                         g_cclosure_marshal_VOID__LONG,
672                         G_TYPE_NONE,
673                         1,
674                         G_TYPE_LONG);
675
676         /**
677          * GritsViewer::location-changed:
678          * @viewer: the viewer.
679          * @lat:    the new latitude.
680          * @lon:    the new longitude.
681          * @elev:   the new elevation.
682          *
683          * The ::location-changed signal is emitted when the viewers camera
684          * location changes.
685          */
686         signals[SIG_LOCATION_CHANGED] = g_signal_new(
687                         "location-changed",
688                         G_TYPE_FROM_CLASS(gobject_class),
689                         G_SIGNAL_RUN_LAST,
690                         0,
691                         NULL,
692                         NULL,
693                         grits_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
694                         G_TYPE_NONE,
695                         3,
696                         G_TYPE_DOUBLE,
697                         G_TYPE_DOUBLE,
698                         G_TYPE_DOUBLE);
699
700         /**
701          * GritsViewer::rotation-changed:
702          * @viewer: the viewer.
703          * @x: rotation new around the x axes.
704          * @y: rotation new around the y axes.
705          * @z: rotation new around the z axes.
706          *
707          * The ::rotation-changed signal is emitted when the viewers cameras
708          * rotation changes.
709          */
710         signals[SIG_ROTATION_CHANGED] = g_signal_new(
711                         "rotation-changed",
712                         G_TYPE_FROM_CLASS(gobject_class),
713                         G_SIGNAL_RUN_LAST,
714                         0,
715                         NULL,
716                         NULL,
717                         grits_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
718                         G_TYPE_NONE,
719                         3,
720                         G_TYPE_DOUBLE,
721                         G_TYPE_DOUBLE,
722                         G_TYPE_DOUBLE);
723
724         /**
725          * GritsViewer::refresh:
726          * @viewer: the viewer.
727          *
728          * The ::refresh signal is emitted when a refresh is needed. If you are
729          * using real-time data from a remote server, you should connect to the
730          * refresh signal and update the data when necessary.
731          */
732         signals[SIG_REFRESH] = g_signal_new(
733                         "refresh",
734                         G_TYPE_FROM_CLASS(gobject_class),
735                         G_SIGNAL_RUN_LAST,
736                         0,
737                         NULL,
738                         NULL,
739                         g_cclosure_marshal_VOID__VOID,
740                         G_TYPE_NONE,
741                         0);
742
743         /**
744          * GritsViewer::offline:
745          * @viewer:  the viewer.
746          * @offline: %TRUE if the viewer going offline.
747          *
748          * The ::offline signal is emitted when the viewers offline mode
749          * changes.
750          */
751         signals[SIG_OFFLINE] = g_signal_new(
752                         "offline",
753                         G_TYPE_FROM_CLASS(gobject_class),
754                         G_SIGNAL_RUN_LAST,
755                         0,
756                         NULL,
757                         NULL,
758                         g_cclosure_marshal_VOID__BOOLEAN,
759                         G_TYPE_NONE,
760                         1,
761                         G_TYPE_BOOLEAN);
762 }