]> err.no Git - mapper/blob - src/gtkcompass.c
Refresh compass every 1 sec. Don't snap directly, animate towards real heading.
[mapper] / src / gtkcompass.c
1 /*
2  *
3  * GtkCompass: Display heading/compass information
4  * Also bearing to target and next waypoint.
5  *
6  * Copyright (C) 2007 Kaj-Michael Lang
7  * Originanl non-widget version Copyright Cezary Jackiewicz
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the Free
21  * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22  */
23
24 #include <math.h>
25 #include <stdlib.h>
26
27 #include <glib/gstdio.h>
28 #include <glib-object.h>
29 #include "gtkcompass.h"
30
31 #if 0
32 #define DEBUG
33 #endif
34
35 static void gtk_compass_finalize (GObject *object);
36 static void gtk_compass_size_request (GtkWidget *widget, GtkRequisition *requisition);
37 static void gtk_compass_size_allocate (GtkWidget *widget, GtkAllocation *allocate);
38 static gboolean gtk_compass_expose (GtkWidget *widget, GdkEventExpose *event);
39 static void gtk_compass_realize (GtkWidget *widget);
40 static void gtk_compass_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
41 static void gtk_compass_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
42 static void gtk_compass_paint(GtkCompass *compass);
43 static gboolean gtk_compass_refresh_cb(GtkWidget *widget);
44
45 G_DEFINE_TYPE(GtkCompass, gtk_compass, GTK_TYPE_WIDGET);
46
47 #define BOUND(x, a, b) { \
48     if((x) < (a)) \
49         (x) = (a); \
50     else if((x) > (b)) \
51         (x) = (b); \
52 }
53
54 static void
55 gtk_compass_class_init (GtkCompassClass *class)
56 {
57 GObjectClass *object_class;
58 GtkWidgetClass *widget_class;
59         
60 object_class = (GObjectClass*) class;
61 widget_class = (GtkWidgetClass*) class;
62         
63 object_class->finalize = gtk_compass_finalize;
64 object_class->set_property = gtk_compass_set_property;
65 object_class->get_property = gtk_compass_get_property;
66         
67 widget_class->size_request = gtk_compass_size_request;
68 widget_class->expose_event = gtk_compass_expose;
69 widget_class->realize = gtk_compass_realize;
70 widget_class->size_allocate = gtk_compass_size_allocate;
71 }
72
73 static void
74 gtk_compass_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
75 {
76 GtkCompass *compass;
77 g_return_if_fail(GTK_IS_COMPASS(object));
78 compass=GTK_COMPASS(object);
79 switch (prop_id) {
80         default:
81                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
82         break;
83 }
84 }
85
86 static void
87 gtk_compass_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec   *pspec)
88 {
89 GtkCompass *compass;
90 g_return_if_fail(GTK_IS_COMPASS(object));
91 compass=GTK_COMPASS(object);
92 switch (prop_id) {
93         default:
94                 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
95         break;
96 }
97 }
98
99 static void
100 gtk_compass_init(GtkCompass *compass)
101 {
102 compass->gc_h=NULL;
103 compass->gc_d=NULL;
104 compass->gc_w=NULL;
105 compass->dest_valid=FALSE;
106 compass->way_valid=FALSE;
107 compass->width=300;
108 compass->height=300;
109 compass->esid=0;
110 }
111
112 static gboolean 
113 gtk_compass_cb_button_press(GtkWidget *widget, GdkEventButton *event)
114 {
115 GtkCompass *compass;
116
117 compass=GTK_COMPASS(widget);
118
119 #ifdef DEBUG
120 compass->data->heading=0;
121 compass->heading=180;
122 #endif
123
124 return FALSE;
125 }
126
127 GtkWidget*
128 gtk_compass_new(GpsData *data)
129 {
130 GtkCompass *compass;
131 GtkWidget *widget;
132
133 compass=gtk_type_new(gtk_compass_get_type ());
134 widget=GTK_WIDGET(compass);
135 compass->data=data;
136 compass->heading=0;
137 g_signal_connect(G_OBJECT(widget), "button_press_event", G_CALLBACK(gtk_compass_cb_button_press), NULL);
138
139 compass->esid=g_timeout_add(1000,(GSourceFunc)gtk_compass_refresh_cb, compass);
140
141 return widget;
142 }
143
144 static void
145 gtk_compass_finalize(GObject *object)
146 {
147 GtkCompass *compass;
148         
149 g_return_if_fail(GTK_IS_COMPASS(object));
150 compass=GTK_COMPASS(object);
151 g_source_remove(compass->esid);
152
153 if (GTK_WIDGET(object)->parent && GTK_WIDGET_MAPPED(object)) {
154         gtk_widget_unmap(GTK_WIDGET(object));
155 }
156
157 G_OBJECT_CLASS(gtk_compass_parent_class)->finalize(object);
158 }
159
160 static void
161 gtk_compass_size_request(GtkWidget      *widget, GtkRequisition *requisition)
162 {
163 GtkCompass *compass;
164         
165 g_return_if_fail(GTK_IS_COMPASS(widget));
166 g_return_if_fail(requisition != NULL);
167         
168 compass=GTK_COMPASS(widget);
169         
170 requisition->width=400;
171 requisition->height=300;
172 compass->width=400;
173 compass->height=300;
174 }
175
176 static void
177 gtk_compass_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
178 {
179 GtkCompass *compass;
180
181 g_return_if_fail(GTK_IS_COMPASS(widget));
182 g_return_if_fail(allocation!=NULL);
183
184 compass=GTK_COMPASS(widget);
185
186 widget->allocation=*allocation;
187
188 if (GTK_WIDGET_REALIZED (widget)) {
189         gdk_window_move_resize (widget->window,
190                 allocation->x, allocation->y,
191                 allocation->width, allocation->height);
192 }
193
194 compass->width=allocation->width;
195 compass->height=allocation->height;
196
197 compass->size = MIN(widget->allocation.width, widget->allocation.height);
198 if (widget->allocation.width > widget->allocation.height) {
199         compass->xoffset = (widget->allocation.width - widget->allocation.height) / 2;
200         compass->yoffset = 0;
201 } else {
202         compass->xoffset = 0;
203         compass->yoffset = (widget->allocation.height - widget->allocation.width) / 2;
204 }
205
206 }
207
208 static void 
209 gtk_compass_realize (GtkWidget *widget)
210 {
211 GtkCompass *compass;
212 GdkColor color;
213 GdkWindowAttr attributes;
214 gint attributes_mask;
215
216 g_return_if_fail (GTK_IS_COMPASS(widget));
217 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
218 compass=GTK_COMPASS(widget);
219
220 attributes.x=widget->allocation.x;
221 attributes.y=widget->allocation.y;
222 attributes.width=widget->allocation.width;
223 attributes.height=widget->allocation.height;
224 attributes.wclass=GDK_INPUT_OUTPUT;
225 attributes.window_type=GDK_WINDOW_CHILD;
226 attributes.event_mask=gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK;
227 attributes.visual=gtk_widget_get_visual(widget);
228 attributes.colormap=gtk_widget_get_colormap(widget);
229
230 attributes_mask=GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
231
232 widget->window=gdk_window_new(gtk_widget_get_parent_window(widget), &attributes, attributes_mask);
233 widget->style=gtk_style_attach(widget->style, widget->window);
234
235 gdk_window_set_user_data(widget->window, widget);
236 gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE);
237
238 compass->context=gtk_widget_get_pango_context(widget);
239 compass->layout=pango_layout_new(compass->context);
240 compass->fontdesc=pango_font_description_new();
241 pango_font_description_set_family(compass->fontdesc, "Sans Serif");
242 pango_font_description_set_size(compass->fontdesc, 12*PANGO_SCALE);
243 pango_layout_set_font_description(compass->layout, compass->fontdesc);
244 pango_layout_set_alignment(compass->layout, PANGO_ALIGN_CENTER);
245
246 if (!compass->gc_h) {
247         color.red=0x0000;
248         color.green=0x0000;
249         color.blue=0x0000;
250         compass->gc_h=gdk_gc_new(widget->window);
251         gdk_gc_set_rgb_fg_color(compass->gc_h, &color);
252         gdk_gc_set_line_attributes(compass->gc_h, 2, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
253 }
254
255 if (!compass->gc_d) {
256         color.red=0xffff;
257         color.green=0x0000;
258         color.blue=0xffff;
259         compass->gc_d=gdk_gc_new(widget->window);
260         gdk_gc_set_rgb_fg_color(compass->gc_d, &color);
261         gdk_gc_set_line_attributes(compass->gc_d, 6, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
262 }
263
264 if (!compass->gc_w) {
265         color.red=0x0000;
266         color.green=0xffff;
267         color.blue=0x0000;
268         compass->gc_w=gdk_gc_new(widget->window);
269         gdk_gc_set_rgb_fg_color(compass->gc_w, &color);
270         gdk_gc_set_line_attributes(compass->gc_w, 6, GDK_LINE_SOLID, GDK_CAP_ROUND, GDK_JOIN_ROUND);
271 }
272
273 }
274
275 static void
276 gtk_compass_draw_mark(GtkCompass *compass, GdkGC *gc, gfloat angle, gint size)
277 {
278 GtkWidget *widget;
279 gint hs;
280
281 widget=GTK_WIDGET(compass);
282
283 hs=compass->size/2;
284
285 gdk_draw_line(widget->window,gc,
286         compass->xoffset+hs+((hs-size)*sinf(angle)),
287         compass->yoffset+compass->size-((hs-size)*cosf(angle)),
288         compass->xoffset+hs+((hs+size)*sinf(angle)),
289         compass->yoffset+compass->size-((hs+size)*cosf(angle)));
290 }
291
292 static void
293 gtk_compass_paint(GtkCompass *compass)
294 {
295 GtkWidget *widget;
296 guint i, x, y, size, hsize, fs;
297 gint dir;
298 gfloat tmp;
299 gchar *text;
300 gchar htext[8];
301 gint angle[5] = { -90, -45, 0, 45, 90 };
302 gint fsize[5] = { 0, 4, 10, 4, 0 };
303
304 widget=GTK_WIDGET(compass);
305 size=compass->size;
306 #if 1
307 hsize=size/2;
308 #else
309 hsize=0;
310 #endif
311
312 fs=size/42;
313 BOUND(fs, 1, 16);
314
315 pango_context_set_matrix (compass->context, NULL);
316
317 g_snprintf(htext, 8, "%3.0f°", compass->heading);
318 pango_font_description_set_size(compass->fontdesc, (10+fs) * PANGO_SCALE);
319 pango_layout_set_font_description(compass->layout, compass->fontdesc);
320 pango_layout_set_text(compass->layout, htext, -1);
321 pango_layout_get_pixel_size(compass->layout, &x, &y);
322
323 gdk_draw_layout(widget->window,
324         compass->gc_h,
325         compass->xoffset+hsize-x/2,
326         compass->yoffset+size-y-2, compass->layout);
327
328 gdk_draw_arc(widget->window,
329         compass->gc_h,
330         FALSE,
331         compass->xoffset, compass->yoffset+hsize, size - fs, size - fs, 0, 64 * 360);
332
333 /* Simple arrow for heading */
334 gdk_draw_line(widget->window,
335         compass->gc_h,
336         compass->xoffset + hsize + 3,
337         compass->yoffset + size - y - 5,
338         compass->xoffset + hsize, compass->yoffset + hsize + 5);
339
340 gdk_draw_line(widget->window,
341         compass->gc_h,
342         compass->xoffset + hsize - 3,
343         compass->yoffset + size - y - 5,
344         compass->xoffset + hsize, compass->yoffset + hsize + 5);
345
346 gdk_draw_line(widget->window,
347         compass->gc_h,
348         compass->xoffset + hsize - 3,
349         compass->yoffset + size - y - 5,
350         compass->xoffset + hsize, compass->yoffset + size - y - 8);
351
352 gdk_draw_line(widget->window,
353         compass->gc_h,
354         compass->xoffset + hsize + 3,
355         compass->yoffset + size - y - 5,
356         compass->xoffset + hsize, compass->yoffset + size - y - 8);
357
358 for (i = 0; i < 5; i++) {
359         PangoMatrix matrix = PANGO_MATRIX_INIT;
360         dir = (gint) (compass->heading / 45) * 45 + angle[i];
361
362         switch (dir) {
363         case 0:
364         case 360:
365                 text = "N";
366                 break;
367         case 45:
368         case 405:
369                 text = "NE";
370                 break;
371         case 90:
372                 text = "E";
373                 break;
374         case 135:
375                 text = "SE";
376                 break;
377         case 180:
378                 text = "S";
379                 break;
380         case 225:
381                 text = "SW";
382                 break;
383         case 270:
384         case -90:
385                 text = "W";
386                 break;
387         case 315:
388         case -45:
389                 text = "NW";
390                 break;
391         default:
392                 text = "??";
393                 break;
394         }
395
396         tmp = ((dir - compass->heading) * (1.f / 180.f)) * G_PI;
397
398         gtk_compass_draw_mark(compass, compass->gc_h, tmp, 6);
399
400         x = fsize[i];
401         if (abs((guint) (compass->heading / 45) * 45 - compass->heading)
402             > abs((guint) (compass->heading / 45) * 45 + 45 - compass->heading) && (i > 0))
403                         x = fsize[i - 1];
404
405         pango_font_description_set_size(compass->fontdesc, (10 + x + fs) * PANGO_SCALE);
406         pango_layout_set_font_description(compass->layout, compass->fontdesc);
407         pango_layout_set_text(compass->layout, text, -1);
408         pango_matrix_rotate (&matrix, -(dir-compass->heading));
409         pango_context_set_matrix (compass->context, &matrix);
410         pango_layout_get_pixel_size(compass->layout, &x, &y);
411         x = compass->xoffset + hsize + ((hsize + 15 + fs) * sinf(tmp)) - x / 2,
412     y = compass->yoffset + size - ((hsize + 15 + fs) * cosf(tmp)) - y / 2,
413     gdk_draw_layout(widget->window, compass->gc_h, x, y, compass->layout);
414 }
415
416 if (compass->dest_valid) {
417         tmp=((compass->dest_heading-compass->heading) * (1.f / 180.f)) * G_PI;
418         gtk_compass_draw_mark(compass, compass->gc_d, tmp, 10);
419 }
420
421 if (compass->way_valid) {
422         tmp=((compass->way_heading-compass->heading) * (1.f / 180.f)) * G_PI;
423         gtk_compass_draw_mark(compass, compass->gc_w, tmp, 10);
424 }
425
426 return;
427 }
428
429 static gboolean
430 gtk_compass_expose(GtkWidget *widget, GdkEventExpose *event)
431 {
432 GtkCompass *compass;
433
434 g_return_val_if_fail(GTK_IS_COMPASS(widget), FALSE);
435 g_return_val_if_fail(event != NULL, FALSE);
436
437 compass=GTK_COMPASS(widget);
438 gtk_compass_paint(compass);
439 return TRUE;
440 }
441
442 static gboolean
443 gtk_compass_refresh_cb(GtkWidget *widget)
444 {
445 GtkCompass *compass;
446 gfloat tmp;
447
448 g_return_val_if_fail(GTK_IS_COMPASS(widget), FALSE);
449
450 compass=GTK_COMPASS(widget);
451
452 if ((GTK_WIDGET_MAPPED(widget)==FALSE) || (GTK_WIDGET_VISIBLE(widget)==FALSE)) {
453         compass->heading=compass->data->heading;
454         return TRUE;
455 }
456
457 if (compass->heading==compass->data->heading)
458         return TRUE;
459
460 tmp=fabsf(compass->heading-compass->data->heading);
461 if (tmp>5)
462         tmp=tmp/2.2;
463 else
464         compass->heading=compass->data->heading;
465
466 if (compass->heading<compass->data->heading)
467         compass->heading+=tmp;
468
469 if (compass->heading>compass->data->heading)
470         compass->heading-=tmp;
471
472 #ifdef DEBUG
473 g_printf("%.2f %.2f\n", compass->heading, compass->data->heading);
474 #endif
475
476 gtk_widget_queue_draw_area(widget, 0, 0, compass->width, compass->height);
477 return TRUE;
478 }
479
480 void
481 gtk_compass_refresh(GtkWidget *widget)
482 {
483 GtkCompass *compass;
484
485 g_return_if_fail(GTK_IS_COMPASS(widget));
486
487 compass=GTK_COMPASS(widget);
488 gtk_widget_queue_draw_area(widget, 0, 0, compass->width, compass->height);
489 }
490
491 void 
492 gtk_compass_set_way_heading(GtkWidget *widget, gboolean valid, gfloat heading)
493 {
494 GtkCompass *compass;
495 g_return_if_fail(GTK_IS_COMPASS(widget));
496
497 compass=GTK_COMPASS(widget);
498
499 compass->way_valid=valid;
500 compass->way_heading=heading;
501
502 gtk_widget_queue_draw_area(widget, 0, 0, compass->width, compass->height);
503 }
504
505 void 
506 gtk_compass_set_dest_heading(GtkWidget *widget, gboolean valid, gfloat heading)
507 {
508 GtkCompass *compass;
509 g_return_if_fail(GTK_IS_COMPASS(widget));
510
511 compass=GTK_COMPASS(widget);
512
513 compass->dest_valid=valid;
514 compass->dest_heading=heading;
515
516 compass=GTK_COMPASS(widget);
517 gtk_widget_queue_draw_area(widget, 0, 0, compass->width, compass->height);
518 }
519