]> Pileus Git - grits/blob - src/gis-viewer.c
Sort of fix panning when rotated
[grits] / src / gis-viewer.c
1 /*
2  * Copyright (C) 2009-2010 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 #include <config.h>
19 #include <math.h>
20 #include <gtk/gtk.h>
21 #include <gdk/gdkkeysyms.h>
22
23 #include "gis-marshal.h"
24 #include "gis-viewer.h"
25
26 #include "gis-util.h"
27
28
29 /* Constants */
30 enum {
31         SIG_TIME_CHANGED,
32         SIG_LOCATION_CHANGED,
33         SIG_ROTATION_CHANGED,
34         SIG_REFRESH,
35         SIG_OFFLINE,
36         NUM_SIGNALS,
37 };
38 static guint signals[NUM_SIGNALS];
39
40
41 /***********
42  * Helpers *
43  ***********/
44 /* Misc helpers */
45 static void _gis_viewer_fix_location(GisViewer *self)
46 {
47         while (self->location[0] <  -90) self->location[0] += 180;
48         while (self->location[0] >   90) self->location[0] -= 180;
49         while (self->location[1] < -180) self->location[1] += 360;
50         while (self->location[1] >  180) self->location[1] -= 360;
51         self->location[2] = ABS(self->location[2]);
52 }
53
54 /* Signal helpers */
55 static void _gis_viewer_emit_location_changed(GisViewer *self)
56 {
57         g_signal_emit(self, signals[SIG_LOCATION_CHANGED], 0,
58                         self->location[0],
59                         self->location[1],
60                         self->location[2]);
61 }
62 static void _gis_viewer_emit_rotation_changed(GisViewer *self)
63 {
64         g_signal_emit(self, signals[SIG_ROTATION_CHANGED], 0,
65                         self->rotation[0],
66                         self->rotation[1],
67                         self->rotation[2]);
68 }
69 static void _gis_viewer_emit_time_changed(GisViewer *self)
70 {
71         g_signal_emit(self, signals[SIG_TIME_CHANGED], 0,
72                         self->time);
73 }
74 static void _gis_viewer_emit_refresh(GisViewer *self)
75 {
76         g_signal_emit(self, signals[SIG_REFRESH], 0);
77 }
78 static void _gis_viewer_emit_offline(GisViewer *self)
79 {
80         g_signal_emit(self, signals[SIG_OFFLINE], 0,
81                         self->offline);
82 }
83
84 /*************
85  * Callbacks *
86  *************/
87 static gboolean on_key_press(GisViewer *self, GdkEventKey *event, gpointer _)
88 {
89         g_debug("GisViewer: on_key_press - key=%x, state=%x, plus=%x",
90                         event->keyval, event->state, GDK_plus);
91
92         double lat, lon, elev, pan;
93         gis_viewer_get_location(self, &lat, &lon, &elev);
94         pan = MIN(elev/(EARTH_R/2), 30);
95         guint kv = event->keyval;
96         gdk_threads_leave();
97         if      (kv == GDK_Left  || kv == GDK_h) gis_viewer_pan(self,  0,  -pan, 0);
98         else if (kv == GDK_Down  || kv == GDK_j) gis_viewer_pan(self, -pan, 0,   0);
99         else if (kv == GDK_Up    || kv == GDK_k) gis_viewer_pan(self,  pan, 0,   0);
100         else if (kv == GDK_Right || kv == GDK_l) gis_viewer_pan(self,  0,   pan, 0);
101         else if (kv == GDK_minus || kv == GDK_o) gis_viewer_zoom(self, 10./9);
102         else if (kv == GDK_plus  || kv == GDK_i) gis_viewer_zoom(self, 9./10);
103         else if (kv == GDK_H) gis_viewer_rotate(self,  0, 0, -2);
104         else if (kv == GDK_J) gis_viewer_rotate(self,  2, 0,  0);
105         else if (kv == GDK_K) gis_viewer_rotate(self, -2, 0,  0);
106         else if (kv == GDK_L) gis_viewer_rotate(self,  0, 0,  2);
107         return FALSE;
108 }
109
110 enum {
111         GIS_DRAG_NONE,
112         GIS_DRAG_PAN,
113         GIS_DRAG_ZOOM,
114         GIS_DRAG_TILT,
115 };
116
117 static gboolean on_button_press(GisViewer *self, GdkEventButton *event, gpointer _)
118 {
119         g_debug("GisViewer: on_button_press - %d", event->button);
120         gtk_widget_grab_focus(GTK_WIDGET(self));
121         switch (event->button) {
122         case 1:  self->drag_mode = GIS_DRAG_PAN;  break;
123         case 2:  self->drag_mode = GIS_DRAG_ZOOM; break;
124         case 3:  self->drag_mode = GIS_DRAG_TILT; break;
125         defualt: self->drag_mode = GIS_DRAG_NONE; break;
126         }
127         self->drag_x = event->x;
128         self->drag_y = event->y;
129         return FALSE;
130 }
131
132 static gboolean on_button_release(GisViewer *self, GdkEventButton *event, gpointer _)
133 {
134         g_debug("GisViewer: on_button_release");
135         self->drag_mode = GIS_DRAG_NONE;
136         return FALSE;
137 }
138
139 static gboolean on_motion_notify(GisViewer *self, GdkEventMotion *event, gpointer _)
140 {
141         gdouble x_dist = self->drag_x - event->x;
142         gdouble y_dist = self->drag_y - event->y;
143         gdouble lat, lon, elev, scale;
144         gis_viewer_get_location(GIS_VIEWER(self), &lat, &lon, &elev);
145         scale = elev/EARTH_R/15;
146         switch (self->drag_mode) {
147         case GIS_DRAG_PAN:
148                 gis_viewer_pan(self, -y_dist*scale, x_dist*scale, 0);
149                 break;
150         case GIS_DRAG_ZOOM:
151                 gis_viewer_zoom(self, pow(2, -y_dist/500));
152                 break;
153         case GIS_DRAG_TILT:
154                 gis_viewer_rotate(self, y_dist/10, 0, x_dist/10);
155                 break;
156         }
157         self->drag_x = event->x;
158         self->drag_y = event->y;
159         return FALSE;
160 }
161
162 static void on_view_changed(GisViewer *self,
163                 gdouble _1, gdouble _2, gdouble _3)
164 {
165         gtk_widget_queue_draw(GTK_WIDGET(self));
166 }
167
168 /***********
169  * Methods *
170  ***********/
171 void gis_viewer_setup(GisViewer *self, GisPlugins *plugins, GisPrefs *prefs)
172 {
173         self->plugins = plugins;
174         self->prefs   = prefs;
175         self->offline = gis_prefs_get_boolean(prefs, "gis/offline", NULL);
176 }
177
178 void gis_viewer_set_time(GisViewer *self, const char *time)
179 {
180         g_assert(GIS_IS_VIEWER(self));
181         g_debug("GisViewer: set_time - time=%s", time);
182         g_free(self->time);
183         self->time = g_strdup(time);
184         _gis_viewer_emit_time_changed(self);
185 }
186
187 gchar *gis_viewer_get_time(GisViewer *self)
188 {
189         g_assert(GIS_IS_VIEWER(self));
190         g_debug("GisViewer: get_time");
191         return self->time;
192 }
193
194 void gis_viewer_set_location(GisViewer *self, gdouble lat, gdouble lon, gdouble elev)
195 {
196         g_assert(GIS_IS_VIEWER(self));
197         g_debug("GisViewer: set_location");
198         self->location[0] = lat;
199         self->location[1] = lon;
200         self->location[2] = elev;
201         _gis_viewer_fix_location(self);
202         _gis_viewer_emit_location_changed(self);
203 }
204
205 void gis_viewer_get_location(GisViewer *self, gdouble *lat, gdouble *lon, gdouble *elev)
206 {
207         g_assert(GIS_IS_VIEWER(self));
208         //g_debug("GisViewer: get_location");
209         *lat  = self->location[0];
210         *lon  = self->location[1];
211         *elev = self->location[2];
212 }
213
214 void gis_viewer_pan(GisViewer *self, gdouble forward, gdouble sideways, gdouble up)
215 {
216         g_assert(GIS_IS_VIEWER(self));
217         g_debug("GisViewer: pan - forward=%8.3f, sideways=%8.3f, up=%8.3f",
218                         forward, sideways, up);
219         gdouble dist   = sqrt(forward*forward + sideways*sideways);
220         gdouble angle1 = deg2rad(self->rotation[2]);
221         gdouble angle2 = atan2(sideways, forward);
222         gdouble angle  = angle1 + angle2;
223         g_message("pan: dist=%f, angle=%f+%f=%f move=%f,%f",
224                         dist, angle1, angle2, angle,
225                         dist*cos(angle),
226                         dist*sin(angle));
227         /* This isn't accurate, but it's usable */
228         self->location[0] += dist*cos(angle);
229         self->location[1] += dist*sin(angle);
230         self->location[2] += up;
231         _gis_viewer_fix_location(self);
232         _gis_viewer_emit_location_changed(self);
233 }
234
235 void gis_viewer_zoom(GisViewer *self, gdouble scale)
236 {
237         g_assert(GIS_IS_VIEWER(self));
238         g_debug("GisViewer: zoom");
239         self->location[2] *= scale;
240         _gis_viewer_emit_location_changed(self);
241 }
242
243 void gis_viewer_set_rotation(GisViewer *self, gdouble x, gdouble y, gdouble z)
244 {
245         g_assert(GIS_IS_VIEWER(self));
246         g_debug("GisViewer: set_rotation");
247         self->rotation[0] = x;
248         self->rotation[1] = y;
249         self->rotation[2] = z;
250         _gis_viewer_emit_rotation_changed(self);
251 }
252
253 void gis_viewer_get_rotation(GisViewer *self, gdouble *x, gdouble *y, gdouble *z)
254 {
255         g_assert(GIS_IS_VIEWER(self));
256         g_debug("GisViewer: get_rotation");
257         *x = self->rotation[0];
258         *y = self->rotation[1];
259         *z = self->rotation[2];
260 }
261
262 void gis_viewer_rotate(GisViewer *self, gdouble x, gdouble y, gdouble z)
263 {
264         g_assert(GIS_IS_VIEWER(self));
265         g_debug("GisViewer: rotate - x=%.0f, y=%.0f, z=%.0f", x, y, z);
266         self->rotation[0] += x;
267         self->rotation[1] += y;
268         self->rotation[2] += z;
269         _gis_viewer_emit_rotation_changed(self);
270 }
271
272 void gis_viewer_refresh(GisViewer *self)
273 {
274         g_debug("GisViewer: refresh");
275         _gis_viewer_emit_refresh(self);
276 }
277
278 void gis_viewer_set_offline(GisViewer *self, gboolean offline)
279 {
280         g_assert(GIS_IS_VIEWER(self));
281         g_debug("GisViewer: set_offline - %d", offline);
282         gis_prefs_set_boolean(self->prefs, "gis/offline", offline);
283         self->offline = offline;
284         _gis_viewer_emit_offline(self);
285 }
286
287 gboolean gis_viewer_get_offline(GisViewer *self)
288 {
289         g_assert(GIS_IS_VIEWER(self));
290         g_debug("GisViewer: get_offline - %d", self->offline);
291         return self->offline;
292 }
293
294 /* To be implemented by subclasses */
295 void gis_viewer_center_position(GisViewer *self,
296                 gdouble lat, gdouble lon, gdouble elev)
297 {
298         GisViewerClass *klass = GIS_VIEWER_GET_CLASS(self);
299         if (!klass->center_position)
300                 g_warning("GisViewer: center_position - Unimplemented");
301         klass->center_position(self, lat, lon, elev);
302 }
303
304 void gis_viewer_project(GisViewer *self,
305                 gdouble lat, gdouble lon, gdouble elev,
306                 gdouble *px, gdouble *py, gdouble *pz)
307 {
308         GisViewerClass *klass = GIS_VIEWER_GET_CLASS(self);
309         if (!klass->project)
310                 g_warning("GisViewer: project - Unimplemented");
311         klass->project(self, lat, lon, elev, px, py, pz);
312 }
313
314 void gis_viewer_clear_height_func(GisViewer *self)
315 {
316         GisViewerClass *klass = GIS_VIEWER_GET_CLASS(self);
317         if (!klass->clear_height_func)
318                 g_warning("GisViewer: clear_height_func - Unimplemented");
319         klass->clear_height_func(self);
320 }
321
322 void gis_viewer_set_height_func(GisViewer *self, GisTile *tile,
323                 GisHeightFunc height_func, gpointer user_data,
324                 gboolean update)
325 {
326         GisViewerClass *klass = GIS_VIEWER_GET_CLASS(self);
327         if (!klass->set_height_func)
328                 g_warning("GisViewer: set_height_func - Unimplemented");
329         klass->set_height_func(self, tile, height_func, user_data, update);
330 }
331
332 gpointer gis_viewer_add(GisViewer *self, GisObject *object,
333                 gint level, gboolean sort)
334 {
335         GisViewerClass *klass = GIS_VIEWER_GET_CLASS(self);
336         if (!klass->add)
337                 g_warning("GisViewer: add - Unimplemented");
338         return klass->add(self, object, level, sort);
339 }
340
341 GisObject *gis_viewer_remove(GisViewer *self, gpointer ref)
342 {
343         GisViewerClass *klass = GIS_VIEWER_GET_CLASS(self);
344         if (!klass->remove)
345                 g_warning("GisViewer: remove - Unimplemented");
346         return klass->remove(self, ref);
347 }
348
349 /****************
350  * GObject code *
351  ****************/
352 G_DEFINE_ABSTRACT_TYPE(GisViewer, gis_viewer, GTK_TYPE_DRAWING_AREA);
353 static void gis_viewer_init(GisViewer *self)
354 {
355         g_debug("GisViewer: init");
356         /* Default values */
357         self->time = g_strdup("");
358         self->location[0] = 40;
359         self->location[1] = -100;
360         self->location[2] = 1.5*EARTH_R;
361         self->rotation[0] = 0;
362         self->rotation[1] = 0;
363         self->rotation[2] = 0;
364
365         g_object_set(self, "can-focus", TRUE, NULL);
366         gtk_widget_add_events(GTK_WIDGET(self),
367                         GDK_BUTTON_PRESS_MASK |
368                         GDK_BUTTON_RELEASE_MASK |
369                         GDK_POINTER_MOTION_MASK |
370                         GDK_KEY_PRESS_MASK);
371
372         g_signal_connect(self, "key-press-event",      G_CALLBACK(on_key_press),      NULL);
373
374         g_signal_connect(self, "button-press-event",   G_CALLBACK(on_button_press),   NULL);
375         g_signal_connect(self, "button-release-event", G_CALLBACK(on_button_release), NULL);
376         g_signal_connect(self, "motion-notify-event",  G_CALLBACK(on_motion_notify),  NULL);
377
378         g_signal_connect(self, "location-changed",     G_CALLBACK(on_view_changed),   NULL);
379         g_signal_connect(self, "rotation-changed",     G_CALLBACK(on_view_changed),   NULL);
380 }
381 static void gis_viewer_finalize(GObject *gobject)
382 {
383         g_debug("GisViewer: finalize");
384         GisViewer *self = GIS_VIEWER(gobject);
385         g_free(self->time);
386         G_OBJECT_CLASS(gis_viewer_parent_class)->finalize(gobject);
387 }
388 static void gis_viewer_class_init(GisViewerClass *klass)
389 {
390         g_debug("GisViewer: class_init");
391         GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
392         gobject_class->finalize     = gis_viewer_finalize;
393         signals[SIG_TIME_CHANGED] = g_signal_new(
394                         "time-changed",
395                         G_TYPE_FROM_CLASS(gobject_class),
396                         G_SIGNAL_RUN_LAST,
397                         0,
398                         NULL,
399                         NULL,
400                         g_cclosure_marshal_VOID__STRING,
401                         G_TYPE_NONE,
402                         1,
403                         G_TYPE_STRING);
404         signals[SIG_LOCATION_CHANGED] = g_signal_new(
405                         "location-changed",
406                         G_TYPE_FROM_CLASS(gobject_class),
407                         G_SIGNAL_RUN_LAST,
408                         0,
409                         NULL,
410                         NULL,
411                         gis_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
412                         G_TYPE_NONE,
413                         3,
414                         G_TYPE_DOUBLE,
415                         G_TYPE_DOUBLE,
416                         G_TYPE_DOUBLE);
417         signals[SIG_ROTATION_CHANGED] = g_signal_new(
418                         "rotation-changed",
419                         G_TYPE_FROM_CLASS(gobject_class),
420                         G_SIGNAL_RUN_LAST,
421                         0,
422                         NULL,
423                         NULL,
424                         gis_cclosure_marshal_VOID__DOUBLE_DOUBLE_DOUBLE,
425                         G_TYPE_NONE,
426                         3,
427                         G_TYPE_DOUBLE,
428                         G_TYPE_DOUBLE,
429                         G_TYPE_DOUBLE);
430         signals[SIG_REFRESH] = g_signal_new(
431                         "refresh",
432                         G_TYPE_FROM_CLASS(gobject_class),
433                         G_SIGNAL_RUN_LAST,
434                         0,
435                         NULL,
436                         NULL,
437                         g_cclosure_marshal_VOID__VOID,
438                         G_TYPE_NONE,
439                         0);
440         signals[SIG_OFFLINE] = g_signal_new(
441                         "offline",
442                         G_TYPE_FROM_CLASS(gobject_class),
443                         G_SIGNAL_RUN_LAST,
444                         0,
445                         NULL,
446                         NULL,
447                         g_cclosure_marshal_VOID__BOOLEAN,
448                         G_TYPE_NONE,
449                         1,
450                         G_TYPE_BOOLEAN);
451 }