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