Contents of /trunk/src/osm-gps-map-osd-classic.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 136 - (show annotations)
Mon Oct 19 13:02:41 2009 UTC (14 years, 6 months ago) by harbaum
File MIME type: text/plain
File size: 61222 byte(s)
Balloon callback extensions
1 /* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*- */
2 /* vim:set et sw=4 ts=4 cino=t0,(0: */
3 /*
4 * Copyright (C) Till Harbaum 2009 <till@harbaum.org>
5 *
6 * osm-gps-map is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * osm-gps-map is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14 * See the GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 #include "config.h"
21 #include <stdlib.h> // abs
22 #include <string.h>
23 #include <math.h> // M_PI/cos()
24
25 /* parameters that can be overwritten from the config file: */
26 /* OSD_DIAMETER */
27 /* OSD_X, OSD_Y */
28
29 #ifndef USE_CAIRO
30 #error "OSD control display lacks a non-cairo implementation!"
31 #endif
32
33 #include <cairo.h>
34
35 #include "osm-gps-map.h"
36 #include "converter.h"
37 #include "osm-gps-map-osd-classic.h"
38
39 //the osd controls
40 typedef struct {
41 /* the offscreen representation of the OSD */
42 struct {
43 cairo_surface_t *surface;
44 gboolean rendered;
45 #ifdef OSD_GPS_BUTTON
46 gboolean gps_enabled;
47 #endif
48 } controls;
49
50 #ifdef OSD_BALLOON
51 //a balloon with additional info
52 struct {
53 cairo_surface_t *surface;
54 int orientation, offset_x, offset_y;
55
56 gboolean just_created;
57 float lat, lon;
58 OsmGpsMapRect_t rect;
59
60 /* function called to have the user app draw the contents */
61 OsmGpsMapBalloonCallback cb;
62 gpointer data;
63 } balloon;
64 #endif
65
66 #ifdef OSD_SCALE
67 struct {
68 cairo_surface_t *surface;
69 int zoom;
70 } scale;
71 #endif
72
73 #ifdef OSD_CROSSHAIR
74 struct {
75 cairo_surface_t *surface;
76 gboolean rendered;
77 } crosshair;
78 #endif
79
80 #ifdef OSD_NAV
81 struct {
82 cairo_surface_t *surface;
83 float lat, lon;
84 char *name;
85 gboolean imperial; // display distance imperial/metric
86 } nav;
87 #endif
88
89 #ifdef OSD_COORDINATES
90 struct {
91 cairo_surface_t *surface;
92 float lat, lon;
93 } coordinates;
94 #endif
95
96 #ifdef OSD_SOURCE_SEL
97 struct {
98 /* values to handle the "source" menu */
99 cairo_surface_t *surface;
100 gboolean expanded;
101 gint shift, dir, count;
102 gint handler_id;
103 gint width, height;
104 gboolean rendered;
105 } source_sel;
106 #endif
107
108 } osd_priv_t;
109
110 #ifdef OSD_BALLOON
111 /* most visual effects are hardcoded by now, but may be made */
112 /* available via properties later */
113 #ifndef BALLOON_AREA_WIDTH
114 #define BALLOON_AREA_WIDTH 290
115 #endif
116 #ifndef BALLOON_AREA_HEIGHT
117 #define BALLOON_AREA_HEIGHT 75
118 #endif
119 #ifndef BALLOON_CORNER_RADIUS
120 #define BALLOON_CORNER_RADIUS 10
121 #endif
122
123 #define BALLOON_BORDER (BALLOON_CORNER_RADIUS/2)
124 #define BALLOON_WIDTH (BALLOON_AREA_WIDTH + 2 * BALLOON_BORDER)
125 #define BALLOON_HEIGHT (BALLOON_AREA_HEIGHT + 2 * BALLOON_BORDER)
126 #define BALLOON_TRANSPARENCY 0.8
127 #define POINTER_HEIGHT 20
128 #define POINTER_FOOT_WIDTH 20
129 #define POINTER_OFFSET (BALLOON_CORNER_RADIUS*3/4)
130 #define BALLOON_SHADOW (BALLOON_CORNER_RADIUS/2)
131 #define BALLOON_SHADOW_TRANSPARENCY 0.2
132
133 #define BALLOON_W (BALLOON_WIDTH + BALLOON_SHADOW)
134 #define BALLOON_H (BALLOON_HEIGHT + POINTER_HEIGHT + BALLOON_SHADOW)
135
136 #define CLOSE_BUTTON_RADIUS (BALLOON_CORNER_RADIUS)
137
138
139 /* draw the bubble shape. this is used twice, once for the shape and once */
140 /* for the shadow */
141 static void
142 osm_gps_map_draw_balloon_shape (cairo_t *cr, int x0, int y0, int x1, int y1,
143 gboolean bottom, int px, int py, int px0, int px1) {
144
145 cairo_move_to (cr, x0, y0 + BALLOON_CORNER_RADIUS);
146 cairo_arc (cr, x0 + BALLOON_CORNER_RADIUS, y0 + BALLOON_CORNER_RADIUS,
147 BALLOON_CORNER_RADIUS, -M_PI, -M_PI/2);
148 if(!bottom) {
149 /* insert top pointer */
150 cairo_line_to (cr, px1, y0);
151 cairo_line_to (cr, px, py);
152 cairo_line_to (cr, px0, y0);
153 }
154
155 cairo_line_to (cr, x1 - BALLOON_CORNER_RADIUS, y0);
156 cairo_arc (cr, x1 - BALLOON_CORNER_RADIUS, y0 + BALLOON_CORNER_RADIUS,
157 BALLOON_CORNER_RADIUS, -M_PI/2, 0);
158 cairo_line_to (cr, x1 , y1 - BALLOON_CORNER_RADIUS);
159 cairo_arc (cr, x1 - BALLOON_CORNER_RADIUS, y1 - BALLOON_CORNER_RADIUS,
160 BALLOON_CORNER_RADIUS, 0, M_PI/2);
161 if(bottom) {
162 /* insert bottom pointer */
163 cairo_line_to (cr, px0, y1);
164 cairo_line_to (cr, px, py);
165 cairo_line_to (cr, px1, y1);
166 }
167
168 cairo_line_to (cr, x0 + BALLOON_CORNER_RADIUS, y1);
169 cairo_arc (cr, x0 + BALLOON_CORNER_RADIUS, y1 - BALLOON_CORNER_RADIUS,
170 BALLOON_CORNER_RADIUS, M_PI/2, M_PI);
171
172 cairo_close_path (cr);
173 }
174
175 static void
176 osd_render_balloon(osm_gps_map_osd_t *osd) {
177 osd_priv_t *priv = (osd_priv_t*)osd->priv;
178
179 /* get zoom */
180 gint zoom;
181 g_object_get(OSM_GPS_MAP(osd->widget), "zoom", &zoom, NULL);
182
183 /* ------- convert given coordinate into screen position --------- */
184 gint xs, ys;
185 osm_gps_map_geographic_to_screen (OSM_GPS_MAP(osd->widget),
186 priv->balloon.lat, priv->balloon.lon,
187 &xs, &ys);
188
189 gint x0 = 1, y0 = 1;
190
191 /* check position of this relative to screen center to determine */
192 /* pointer direction ... */
193 int pointer_x, pointer_x0, pointer_x1;
194 int pointer_y;
195
196 /* ... and calculate position */
197 int orientation = 0;
198 if(xs > osd->widget->allocation.width/2) {
199 priv->balloon.offset_x = -BALLOON_WIDTH + POINTER_OFFSET;
200 pointer_x = x0 - priv->balloon.offset_x;
201 pointer_x0 = pointer_x - (BALLOON_CORNER_RADIUS - POINTER_OFFSET);
202 pointer_x1 = pointer_x0 - POINTER_FOOT_WIDTH;
203 orientation |= 1;
204 } else {
205 priv->balloon.offset_x = -POINTER_OFFSET;
206 pointer_x = x0 - priv->balloon.offset_x;
207 pointer_x1 = pointer_x + (BALLOON_CORNER_RADIUS - POINTER_OFFSET);
208 pointer_x0 = pointer_x1 + POINTER_FOOT_WIDTH;
209 }
210
211 gboolean bottom = FALSE;
212 if(ys > osd->widget->allocation.height/2) {
213 priv->balloon.offset_y = -BALLOON_HEIGHT - POINTER_HEIGHT;
214 pointer_y = y0 - priv->balloon.offset_y;
215 bottom = TRUE;
216 orientation |= 2;
217 } else {
218 priv->balloon.offset_y = 0;
219 pointer_y = y0 - priv->balloon.offset_y;
220 y0 += POINTER_HEIGHT;
221 }
222
223 /* if required orientation equals current one, then don't render */
224 /* anything */
225 if(orientation == priv->balloon.orientation)
226 return;
227
228 priv->balloon.orientation = orientation;
229
230 /* calculate bottom/right of box */
231 int x1 = x0 + BALLOON_WIDTH, y1 = y0 + BALLOON_HEIGHT;
232
233 /* save balloon screen coordinates for later use */
234 priv->balloon.rect.x = x0 + BALLOON_BORDER;
235 priv->balloon.rect.y = y0 + BALLOON_BORDER;
236 priv->balloon.rect.w = x1 - x0 - 2*BALLOON_BORDER;
237 priv->balloon.rect.h = y1 - y0 - 2*BALLOON_BORDER;
238
239 cairo_t *cr = cairo_create(priv->balloon.surface);
240 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
241 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
242 cairo_paint(cr);
243 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
244
245 /* --------- draw shadow --------------- */
246 osm_gps_map_draw_balloon_shape (cr,
247 x0 + BALLOON_SHADOW, y0 + BALLOON_SHADOW,
248 x1 + BALLOON_SHADOW, y1 + BALLOON_SHADOW,
249 bottom, pointer_x, pointer_y,
250 pointer_x0 + BALLOON_SHADOW, pointer_x1 + BALLOON_SHADOW);
251
252 cairo_set_source_rgba (cr, 0, 0, 0, BALLOON_SHADOW_TRANSPARENCY);
253 cairo_fill_preserve (cr);
254 cairo_set_source_rgba (cr, 1, 0, 0, 1.0);
255 cairo_set_line_width (cr, 0);
256 cairo_stroke (cr);
257
258 /* --------- draw main shape ----------- */
259 osm_gps_map_draw_balloon_shape (cr, x0, y0, x1, y1,
260 bottom, pointer_x, pointer_y, pointer_x0, pointer_x1);
261
262 cairo_set_source_rgba (cr, 1, 1, 1, BALLOON_TRANSPARENCY);
263 cairo_fill_preserve (cr);
264 cairo_set_source_rgba (cr, 0, 0, 0, BALLOON_TRANSPARENCY);
265 cairo_set_line_width (cr, 1);
266 cairo_stroke (cr);
267
268 if (priv->balloon.cb) {
269 osm_gps_map_balloon_event_t event;
270
271 /* clip in case application tries to draw in */
272 /* exceed of the balloon */
273 cairo_rectangle (cr, priv->balloon.rect.x, priv->balloon.rect.y,
274 priv->balloon.rect.w, priv->balloon.rect.h);
275 cairo_clip (cr);
276 cairo_new_path (cr); /* current path is not consumed by cairo_clip */
277
278 /* request the application to draw the balloon contents */
279 event.type = OSM_GPS_MAP_BALLOON_EVENT_TYPE_DRAW;
280 event.data.draw.rect = &priv->balloon.rect;
281 event.data.draw.cr = cr;
282
283 priv->balloon.cb(&event, priv->balloon.data);
284 }
285
286 cairo_destroy(cr);
287 }
288
289 /* return true if balloon is being displayed and if */
290 /* the given coordinate is within this balloon */
291 static gboolean
292 osd_balloon_check(osm_gps_map_osd_t *osd, gboolean click, gboolean down, gint x, gint y)
293 {
294 osd_priv_t *priv = (osd_priv_t*)osd->priv;
295
296 if(!priv->balloon.surface)
297 return FALSE;
298
299 gint xs, ys;
300 osm_gps_map_geographic_to_screen (OSM_GPS_MAP(osd->widget),
301 priv->balloon.lat, priv->balloon.lon,
302 &xs, &ys);
303
304 xs += priv->balloon.rect.x + priv->balloon.offset_x;
305 ys += priv->balloon.rect.y + priv->balloon.offset_y;
306
307 gboolean is_in =
308 (x > xs) && (x < xs + priv->balloon.rect.w) &&
309 (y > ys) && (y < ys + priv->balloon.rect.h);
310
311 /* is this a real click or is the application just checking for something? */
312 if(click) {
313
314 /* handle the fact that the balloon may have been created by the */
315 /* button down event */
316 if(!is_in && !down && !priv->balloon.just_created) {
317 /* the user actually clicked outside the balloon */
318
319 /* close the balloon! */
320 osm_gps_map_osd_clear_balloon (OSM_GPS_MAP(osd->widget));
321
322 /* and inform application about this */
323 if(priv->balloon.cb) {
324 osm_gps_map_balloon_event_t event;
325 event.type = OSM_GPS_MAP_BALLOON_EVENT_TYPE_REMOVED;
326 priv->balloon.cb(&event, priv->balloon.data);
327 }
328
329 }
330
331 if(is_in && priv->balloon.cb) {
332 osm_gps_map_balloon_event_t event;
333
334 /* notify application of click */
335 event.type = OSM_GPS_MAP_BALLOON_EVENT_TYPE_CLICK;
336 event.data.click.x = x - xs;
337 event.data.click.y = y - ys;
338 event.data.click.down = down;
339
340 priv->balloon.cb(&event, priv->balloon.data);
341 }
342 }
343
344 return is_in;
345 }
346
347 void osm_gps_map_osd_clear_balloon (OsmGpsMap *map) {
348 g_return_if_fail (OSM_IS_GPS_MAP (map));
349
350 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
351 g_return_if_fail (osd);
352
353 osd_priv_t *priv = (osd_priv_t*)osd->priv;
354 g_return_if_fail (priv);
355
356 if(priv->balloon.surface) {
357 cairo_surface_destroy(priv->balloon.surface);
358 priv->balloon.surface = NULL;
359 priv->balloon.lat = OSM_GPS_MAP_INVALID;
360 priv->balloon.lon = OSM_GPS_MAP_INVALID;
361 }
362 osm_gps_map_redraw(map);
363 }
364
365 void
366 osm_gps_map_osd_draw_balloon (OsmGpsMap *map, float latitude, float longitude,
367 OsmGpsMapBalloonCallback cb, gpointer data) {
368 g_return_if_fail (OSM_IS_GPS_MAP (map));
369
370 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
371 g_return_if_fail (osd);
372
373 osd_priv_t *priv = (osd_priv_t*)osd->priv;
374 g_return_if_fail (priv);
375
376 osm_gps_map_osd_clear_balloon (map);
377
378 /* allocate balloon surface */
379 priv->balloon.surface =
380 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
381 BALLOON_W+2, BALLOON_H+2);
382
383 priv->balloon.lat = latitude;
384 priv->balloon.lon = longitude;
385 priv->balloon.cb = cb;
386 priv->balloon.data = data;
387 priv->balloon.just_created = TRUE;
388
389 priv->balloon.orientation = -1;
390
391 osd_render_balloon(osd);
392
393 osm_gps_map_redraw(map);
394 }
395
396 #endif // OSD_BALLOON
397
398 /* position and extent of bounding box */
399 #ifndef OSD_X
400 #define OSD_X (10)
401 #endif
402
403 #ifndef OSD_Y
404 #define OSD_Y (10)
405 #endif
406
407 /* parameters of the direction shape */
408 #ifndef OSD_DIAMETER
409 #define D_RAD (30) // diameter of dpad
410 #else
411 #define D_RAD (OSD_DIAMETER)
412 #endif
413 #define D_TIP (4*D_RAD/5) // distance of arrow tip from dpad center
414 #define D_LEN (D_RAD/4) // length of arrow
415 #define D_WID (D_LEN) // width of arrow
416
417 /* parameters of the "zoom" pad */
418 #define Z_STEP (D_RAD/4) // distance between dpad and zoom
419 #define Z_RAD (D_RAD/2) // radius of "caps" of zoom bar
420
421 #ifdef OSD_SHADOW_ENABLE
422 /* shadow also depends on control size */
423 #define OSD_SHADOW (D_RAD/6)
424 #else
425 #define OSD_SHADOW (0)
426 #endif
427
428 /* normally the GPS button is in the center of the dpad. if there's */
429 /* no dpad it will go into the zoom area */
430 #if defined(OSD_GPS_BUTTON) && defined(OSD_NO_DPAD)
431 #define Z_GPS 1
432 #else
433 #define Z_GPS 0
434 #endif
435
436 /* total width and height of controls incl. shadow */
437 #define OSD_W (2*D_RAD + OSD_SHADOW + Z_GPS * 2 * Z_RAD)
438 #if !Z_GPS
439 #define OSD_H (2*D_RAD + Z_STEP + 2*Z_RAD + OSD_SHADOW)
440 #else
441 #define OSD_H (2*Z_RAD + OSD_SHADOW)
442 #endif
443
444 #ifdef OSD_SHADOW_ENABLE
445 #define OSD_LBL_SHADOW (OSD_SHADOW/2)
446 #endif
447
448 #define Z_TOP ((1-Z_GPS) * (2 * D_RAD + Z_STEP))
449
450 #define Z_MID (Z_TOP + Z_RAD)
451 #define Z_BOT (Z_MID + Z_RAD)
452 #define Z_LEFT (Z_RAD)
453 #define Z_RIGHT (2 * D_RAD - Z_RAD + Z_GPS * 2 * Z_RAD)
454 #define Z_CENTER ((Z_RIGHT + Z_LEFT)/2)
455
456 /* create the cairo shape used for the zoom buttons */
457 static void
458 osd_zoom_shape(cairo_t *cr, gint x, gint y)
459 {
460 cairo_move_to (cr, x+Z_LEFT, y+Z_TOP);
461 cairo_line_to (cr, x+Z_RIGHT, y+Z_TOP);
462 cairo_arc (cr, x+Z_RIGHT, y+Z_MID, Z_RAD, -M_PI/2, M_PI/2);
463 cairo_line_to (cr, x+Z_LEFT, y+Z_BOT);
464 cairo_arc (cr, x+Z_LEFT, y+Z_MID, Z_RAD, M_PI/2, -M_PI/2);
465 }
466
467 /* ------------------- color/shadow functions ----------------- */
468
469 #ifndef OSD_COLOR
470 /* if no color has been specified we just use the gdks default colors */
471 static void
472 osd_labels(cairo_t *cr, gint width, gboolean enabled,
473 GdkColor *fg, GdkColor *disabled) {
474 if(enabled) gdk_cairo_set_source_color(cr, fg);
475 else gdk_cairo_set_source_color(cr, disabled);
476 cairo_set_line_width (cr, width);
477 }
478 #else
479 static void
480 osd_labels(cairo_t *cr, gint width, gboolean enabled) {
481 if(enabled) cairo_set_source_rgb (cr, OSD_COLOR);
482 else cairo_set_source_rgb (cr, OSD_COLOR_DISABLED);
483 cairo_set_line_width (cr, width);
484 }
485 #endif
486
487 #ifdef OSD_SHADOW_ENABLE
488 static void
489 osd_labels_shadow(cairo_t *cr, gint width, gboolean enabled) {
490 cairo_set_source_rgba (cr, 0, 0, 0, enabled?0.3:0.15);
491 cairo_set_line_width (cr, width);
492 }
493 #endif
494
495 #ifndef OSD_NO_DPAD
496 /* create the cairo shape used for the dpad */
497 static void
498 osd_dpad_shape(cairo_t *cr, gint x, gint y)
499 {
500 cairo_arc (cr, x+D_RAD, y+D_RAD, D_RAD, 0, 2 * M_PI);
501 }
502 #endif
503
504 #ifdef OSD_SHADOW_ENABLE
505 static void
506 osd_shape_shadow(cairo_t *cr) {
507 cairo_set_source_rgba (cr, 0, 0, 0, 0.2);
508 cairo_fill (cr);
509 cairo_stroke (cr);
510 }
511 #endif
512
513 #ifndef OSD_COLOR
514 /* if no color has been specified we just use the gdks default colors */
515 static void
516 osd_shape(cairo_t *cr, GdkColor *bg, GdkColor *fg) {
517 gdk_cairo_set_source_color(cr, bg);
518 cairo_fill_preserve (cr);
519 gdk_cairo_set_source_color(cr, fg);
520 cairo_set_line_width (cr, 1);
521 cairo_stroke (cr);
522 }
523 #else
524 static void
525 osd_shape(cairo_t *cr) {
526 cairo_set_source_rgb (cr, OSD_COLOR_BG);
527 cairo_fill_preserve (cr);
528 cairo_set_source_rgb (cr, OSD_COLOR);
529 cairo_set_line_width (cr, 1);
530 cairo_stroke (cr);
531 }
532 #endif
533
534
535 static gboolean
536 osm_gps_map_in_circle(gint x, gint y, gint cx, gint cy, gint rad)
537 {
538 return( pow(cx - x, 2) + pow(cy - y, 2) < rad * rad);
539 }
540
541 #ifndef OSD_NO_DPAD
542 /* check whether x/y is within the dpad */
543 static osd_button_t
544 osd_check_dpad(gint x, gint y)
545 {
546 /* within entire dpad circle */
547 if( osm_gps_map_in_circle(x, y, D_RAD, D_RAD, D_RAD))
548 {
549 /* convert into position relative to dpads centre */
550 x -= D_RAD;
551 y -= D_RAD;
552
553 #ifdef OSD_GPS_BUTTON
554 /* check for dpad center goes here! */
555 if( osm_gps_map_in_circle(x, y, 0, 0, D_RAD/3))
556 return OSD_GPS;
557 #endif
558
559 if( y < 0 && abs(x) < abs(y))
560 return OSD_UP;
561
562 if( y > 0 && abs(x) < abs(y))
563 return OSD_DOWN;
564
565 if( x < 0 && abs(y) < abs(x))
566 return OSD_LEFT;
567
568 if( x > 0 && abs(y) < abs(x))
569 return OSD_RIGHT;
570
571 return OSD_BG;
572 }
573 return OSD_NONE;
574 }
575 #endif
576
577 /* check whether x/y is within the zoom pads */
578 static osd_button_t
579 osd_check_zoom(gint x, gint y) {
580 if( x > 0 && x < OSD_W && y > Z_TOP && y < Z_BOT) {
581
582 /* within circle around (-) label */
583 if( osm_gps_map_in_circle(x, y, Z_LEFT, Z_MID, Z_RAD))
584 return OSD_OUT;
585
586 /* within circle around (+) label */
587 if( osm_gps_map_in_circle(x, y, Z_RIGHT, Z_MID, Z_RAD))
588 return OSD_IN;
589
590 #if Z_GPS == 1
591 /* within square around center */
592 if( x > Z_CENTER - Z_RAD && x < Z_CENTER + Z_RAD)
593 return OSD_GPS;
594 #endif
595
596 /* between center of (-) button and center of entire zoom control area */
597 if(x > OSD_LEFT && x < D_RAD)
598 return OSD_OUT;
599
600 /* between center of (+) button and center of entire zoom control area */
601 if(x < OSD_RIGHT && x > D_RAD)
602 return OSD_IN;
603 }
604
605 return OSD_NONE;
606 }
607
608 #ifdef OSD_SOURCE_SEL
609
610 /* place source selection at right border */
611 #define OSD_S_RAD (Z_RAD)
612 #define OSD_S_X (-OSD_X)
613 #define OSD_S_Y (OSD_Y)
614 #define OSD_S_PW (2 * Z_RAD)
615 #define OSD_S_W (OSD_S_PW)
616 #define OSD_S_PH (2 * Z_RAD)
617 #define OSD_S_H (OSD_S_PH + OSD_SHADOW)
618
619 /* size of usable area when expanded */
620 #define OSD_S_AREA_W (priv->source_sel.width)
621 #define OSD_S_AREA_H (priv->source_sel.height)
622 #define OSD_S_EXP_W (OSD_S_PW + OSD_S_AREA_W + OSD_SHADOW)
623 #define OSD_S_EXP_H (OSD_S_AREA_H + OSD_SHADOW)
624
625 /* internal value to draw the arrow on the "puller" */
626 #define OSD_S_D0 (OSD_S_RAD/2)
627 #ifndef OSD_FONT_SIZE
628 #define OSD_FONT_SIZE 16.0
629 #endif
630 #define OSD_TEXT_BORDER (OSD_FONT_SIZE/2)
631 #define OSD_TEXT_SKIP (OSD_FONT_SIZE/8)
632
633 /* draw the shape of the source selection OSD, either only the puller (not expanded) */
634 /* or the entire menu incl. the puller (expanded) */
635 static void
636 osd_source_shape(osd_priv_t *priv, cairo_t *cr, gint x, gint y) {
637 if(!priv->source_sel.expanded) {
638 /* just draw the puller */
639 cairo_move_to (cr, x + OSD_S_PW, y + OSD_S_PH);
640 cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2);
641 cairo_line_to (cr, x + OSD_S_PW, y);
642 } else {
643 /* draw the puller and the area itself */
644 cairo_move_to (cr, x + OSD_S_PW + OSD_S_AREA_W, y + OSD_S_AREA_H);
645 cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_AREA_H);
646 if(OSD_S_Y > 0) {
647 cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_PH);
648 cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2);
649 } else {
650 cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_AREA_H-OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2);
651 cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_AREA_H - OSD_S_PH);
652 cairo_line_to (cr, x + OSD_S_PW, y);
653 }
654 cairo_line_to (cr, x + OSD_S_PW + OSD_S_AREA_W, y);
655 cairo_close_path (cr);
656 }
657 }
658
659 static void
660 osd_source_content(osm_gps_map_osd_t *osd, cairo_t *cr, gint offset) {
661 osd_priv_t *priv = (osd_priv_t*)osd->priv;
662
663 int py = offset + OSD_S_RAD - OSD_S_D0;
664
665 if(!priv->source_sel.expanded) {
666 /* draw the "puller" open (<) arrow */
667 cairo_move_to (cr, offset + OSD_S_RAD + OSD_S_D0/2, py);
668 cairo_rel_line_to (cr, -OSD_S_D0, +OSD_S_D0);
669 cairo_rel_line_to (cr, +OSD_S_D0, +OSD_S_D0);
670 } else {
671 if(OSD_S_Y < 0)
672 py += OSD_S_AREA_H - OSD_S_PH;
673
674 /* draw the "puller" close (>) arrow */
675 cairo_move_to (cr, offset + OSD_S_RAD - OSD_S_D0/2, py);
676 cairo_rel_line_to (cr, +OSD_S_D0, +OSD_S_D0);
677 cairo_rel_line_to (cr, -OSD_S_D0, +OSD_S_D0);
678 cairo_stroke(cr);
679
680 /* don't draw a shadow for the text content */
681 if(offset == 1) {
682 gint source;
683 g_object_get(osd->widget, "map-source", &source, NULL);
684
685 cairo_select_font_face (cr, "Sans",
686 CAIRO_FONT_SLANT_NORMAL,
687 CAIRO_FONT_WEIGHT_BOLD);
688 cairo_set_font_size (cr, OSD_FONT_SIZE);
689
690 int i, step = (priv->source_sel.height - 2*OSD_TEXT_BORDER) /
691 OSM_GPS_MAP_SOURCE_LAST;
692 for(i=OSM_GPS_MAP_SOURCE_NULL+1;i<=OSM_GPS_MAP_SOURCE_LAST;i++) {
693 cairo_text_extents_t extents;
694 const char *src = osm_gps_map_source_get_friendly_name(i);
695 cairo_text_extents (cr, src, &extents);
696
697 int x = offset + OSD_S_PW + OSD_TEXT_BORDER;
698 int y = offset + step * (i-1) + OSD_TEXT_BORDER;
699
700 /* draw filled rectangle if selected */
701 if(source == i) {
702 cairo_rectangle(cr, x - OSD_TEXT_BORDER/2,
703 y - OSD_TEXT_SKIP,
704 priv->source_sel.width - OSD_TEXT_BORDER,
705 step + OSD_TEXT_SKIP);
706 cairo_fill(cr);
707
708 /* temprarily draw with background color */
709 #ifndef OSD_COLOR
710 GdkColor bg = osd->widget->style->bg[GTK_STATE_NORMAL];
711 gdk_cairo_set_source_color(cr, &bg);
712 #else
713 cairo_set_source_rgb (cr, OSD_COLOR_BG);
714 #endif
715 }
716
717 cairo_move_to (cr, x, y + OSD_TEXT_SKIP - extents.y_bearing);
718 cairo_show_text (cr, src);
719
720 /* restore color */
721 if(source == i) {
722 #ifndef OSD_COLOR
723 GdkColor fg = osd->widget->style->fg[GTK_STATE_NORMAL];
724 gdk_cairo_set_source_color(cr, &fg);
725 #else
726 cairo_set_source_rgb (cr, OSD_COLOR);
727 #endif
728 }
729 }
730 }
731 }
732 }
733
734 static void
735 osd_render_source_sel(osm_gps_map_osd_t *osd, gboolean force_rerender) {
736 osd_priv_t *priv = (osd_priv_t*)osd->priv;
737
738 if(priv->source_sel.rendered && !force_rerender)
739 return;
740
741 priv->source_sel.rendered = TRUE;
742
743 #ifndef OSD_COLOR
744 GdkColor bg = GTK_WIDGET(osd->widget)->style->bg[GTK_STATE_NORMAL];
745 GdkColor fg = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_NORMAL];
746 GdkColor da = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_INSENSITIVE];
747 #endif
748
749 /* draw source selector */
750 cairo_t *cr = cairo_create(priv->source_sel.surface);
751 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
752 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
753 cairo_paint(cr);
754 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
755
756 #ifdef OSD_SHADOW_ENABLE
757 osd_source_shape(priv, cr, 1+OSD_SHADOW, 1+OSD_SHADOW);
758 osd_shape_shadow(cr);
759 #endif
760
761 osd_source_shape(priv, cr, 1, 1);
762 #ifndef OSD_COLOR
763 osd_shape(cr, &bg, &fg);
764 #else
765 osd_shape(cr);
766 #endif
767
768 #ifdef OSD_SHADOW_ENABLE
769 osd_labels_shadow(cr, Z_RAD/3, TRUE);
770 osd_source_content(osd, cr, 1+OSD_LBL_SHADOW);
771 cairo_stroke (cr);
772 #endif
773 #ifndef OSD_COLOR
774 osd_labels(cr, Z_RAD/3, TRUE, &fg, &da);
775 #else
776 osd_labels(cr, Z_RAD/3, TRUE);
777 #endif
778 osd_source_content(osd, cr, 1);
779 cairo_stroke (cr);
780
781 cairo_destroy(cr);
782 }
783
784 /* re-allocate the buffer used to draw the menu. This is used */
785 /* to collapse/expand the buffer */
786 static void
787 osd_source_reallocate(osm_gps_map_osd_t *osd) {
788 osd_priv_t *priv = (osd_priv_t*)osd->priv;
789
790 /* re-allocate offscreen bitmap */
791 g_assert (priv->source_sel.surface);
792
793 int w = OSD_S_W, h = OSD_S_H;
794 if(priv->source_sel.expanded) {
795 cairo_text_extents_t extents;
796
797 /* determine content size */
798 cairo_t *cr = cairo_create(priv->source_sel.surface);
799 cairo_select_font_face (cr, "Sans",
800 CAIRO_FONT_SLANT_NORMAL,
801 CAIRO_FONT_WEIGHT_BOLD);
802 cairo_set_font_size (cr, OSD_FONT_SIZE);
803
804 /* calculate menu size */
805 int i, max_h = 0, max_w = 0;
806 for(i=OSM_GPS_MAP_SOURCE_NULL+1;i<=OSM_GPS_MAP_SOURCE_LAST;i++) {
807 const char *src = osm_gps_map_source_get_friendly_name(i);
808 cairo_text_extents (cr, src, &extents);
809
810 if(extents.width > max_w) max_w = extents.width;
811 if(extents.height > max_h) max_h = extents.height;
812 }
813 cairo_destroy(cr);
814
815 priv->source_sel.width = max_w + 2*OSD_TEXT_BORDER;
816 priv->source_sel.height = OSM_GPS_MAP_SOURCE_LAST *
817 (max_h + 2*OSD_TEXT_SKIP) + 2*OSD_TEXT_BORDER;
818
819 w = OSD_S_EXP_W;
820 h = OSD_S_EXP_H;
821 }
822
823 cairo_surface_destroy(priv->source_sel.surface);
824 priv->source_sel.surface =
825 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w+2, h+2);
826
827 osd_render_source_sel(osd, TRUE);
828 }
829
830 #define OSD_HZ 15
831 #define OSD_TIME 500
832
833 static gboolean osd_source_animate(gpointer data) {
834 osm_gps_map_osd_t *osd = (osm_gps_map_osd_t*)data;
835 osd_priv_t *priv = (osd_priv_t*)osd->priv;
836 int diff = OSD_S_EXP_W - OSD_S_W - OSD_S_X;
837 gboolean done = FALSE;
838 priv->source_sel.count += priv->source_sel.dir;
839
840 /* shifting in */
841 if(priv->source_sel.dir < 0) {
842 if(priv->source_sel.count <= 0) {
843 priv->source_sel.count = 0;
844 done = TRUE;
845 }
846 } else {
847 if(priv->source_sel.count >= 1000) {
848 priv->source_sel.expanded = FALSE;
849 osd_source_reallocate(osd);
850
851 priv->source_sel.count = 1000;
852 done = TRUE;
853 }
854 }
855
856
857 /* count runs linearly from 0 to 1000, map this nicely onto a position */
858
859 /* nice sinoid mapping */
860 float m = 0.5-cos(priv->source_sel.count * M_PI / 1000.0)/2;
861 priv->source_sel.shift =
862 (osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X) +
863 m * diff;
864
865 /* make sure the screen is updated */
866 osm_gps_map_repaint(OSM_GPS_MAP(osd->widget));
867
868 /* stop animation if done */
869 if(done)
870 priv->source_sel.handler_id = 0;
871
872 return !done;
873 }
874
875 /* switch between expand and collapse mode of source selection */
876 static void
877 osd_source_toggle(osm_gps_map_osd_t *osd)
878 {
879 osd_priv_t *priv = (osd_priv_t*)osd->priv;
880
881 /* ignore clicks while animation is running */
882 if(priv->source_sel.handler_id)
883 return;
884
885 /* expand immediately, collapse is handle at the end of the */
886 /* collapse animation */
887 if(!priv->source_sel.expanded) {
888 priv->source_sel.expanded = TRUE;
889 osd_source_reallocate(osd);
890
891 priv->source_sel.count = 1000;
892 priv->source_sel.shift = osd->widget->allocation.width - OSD_S_W;
893 priv->source_sel.dir = -1000/OSD_HZ;
894 } else {
895 priv->source_sel.count = 0;
896 priv->source_sel.shift = osd->widget->allocation.width -
897 OSD_S_EXP_W + OSD_S_X;
898 priv->source_sel.dir = +1000/OSD_HZ;
899 }
900
901 /* start timer to handle animation */
902 priv->source_sel.handler_id = gtk_timeout_add(OSD_TIME/OSD_HZ,
903 osd_source_animate, osd);
904 }
905
906 /* check if the user clicked inside the source selection area */
907 static osd_button_t
908 osd_source_check(osm_gps_map_osd_t *osd, gboolean down, gint x, gint y) {
909 osd_priv_t *priv = (osd_priv_t*)osd->priv;
910
911 if(!priv->source_sel.expanded)
912 x -= osd->widget->allocation.width - OSD_S_W;
913 else
914 x -= osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X;
915
916 if(OSD_S_Y > 0)
917 y -= OSD_S_Y;
918 else
919 y -= osd->widget->allocation.height - OSD_S_PH + OSD_S_Y;
920
921 /* within square around puller? */
922 if(y > 0 && y < OSD_S_PH && x > 0 && x < OSD_S_PW) {
923 /* really within puller shape? */
924 if(x > Z_RAD || osm_gps_map_in_circle(x, y, Z_RAD, Z_RAD, Z_RAD)) {
925 /* expand source selector */
926 if(down)
927 osd_source_toggle(osd);
928
929 /* tell upper layers that user clicked some background element */
930 /* of the OSD */
931 return OSD_BG;
932 }
933 }
934
935 /* check for clicks into data area */
936 if(priv->source_sel.expanded && !priv->source_sel.handler_id) {
937 /* re-adjust from puller top to content top */
938 if(OSD_S_Y < 0)
939 y += OSD_S_EXP_H - OSD_S_PH;
940
941 if(x > OSD_S_PW &&
942 x < OSD_S_PW + OSD_S_EXP_W &&
943 y > 0 &&
944 y < OSD_S_EXP_H) {
945
946 int step = (priv->source_sel.height - 2*OSD_TEXT_BORDER)
947 / OSM_GPS_MAP_SOURCE_LAST;
948
949 y -= OSD_TEXT_BORDER - OSD_TEXT_SKIP;
950 y /= step;
951 y += 1;
952
953 if(down) {
954 gint old = 0;
955 g_object_get(osd->widget, "map-source", &old, NULL);
956
957 if(y > OSM_GPS_MAP_SOURCE_NULL &&
958 y <= OSM_GPS_MAP_SOURCE_LAST &&
959 old != y) {
960 g_object_set(osd->widget, "map-source", y, NULL);
961
962 osd_render_source_sel(osd, TRUE);
963 osm_gps_map_repaint(OSM_GPS_MAP(osd->widget));
964 }
965 }
966
967 /* return "clicked in OSD background" to prevent further */
968 /* processing by application */
969 return OSD_BG;
970 }
971 }
972
973 return OSD_NONE;
974 }
975 #endif // OSD_SOURCE_SEL
976
977 static osd_button_t
978 osd_check_int(osm_gps_map_osd_t *osd, gboolean click, gboolean down, gint x, gint y) {
979 osd_button_t but = OSD_NONE;
980
981 #ifdef OSD_BALLOON
982 if(down) {
983 /* needed to handle balloons that are created at click */
984 osd_priv_t *priv = (osd_priv_t*)osd->priv;
985 priv->balloon.just_created = FALSE;
986 }
987 #endif
988
989 #ifdef OSD_SOURCE_SEL
990 /* the source selection area is handles internally */
991 but = osd_source_check(osd, down, x, y);
992 #endif
993
994 if(but == OSD_NONE) {
995 gint mx = x - OSD_X;
996 gint my = y - OSD_Y;
997
998 if(OSD_X < 0)
999 mx -= (osd->widget->allocation.width - OSD_W);
1000
1001 if(OSD_Y < 0)
1002 my -= (osd->widget->allocation.height - OSD_H);
1003
1004 /* first do a rough test for the OSD area. */
1005 /* this is just to avoid an unnecessary detailed test */
1006 if(mx > 0 && mx < OSD_W && my > 0 && my < OSD_H) {
1007 #ifndef OSD_NO_DPAD
1008 but = osd_check_dpad(mx, my);
1009 #endif
1010 }
1011
1012 if(but == OSD_NONE)
1013 but = osd_check_zoom(mx, my);
1014 }
1015
1016 #ifdef OSD_BALLOON
1017 if(but == OSD_NONE) {
1018 /* check if user clicked into balloon */
1019 if(osd_balloon_check(osd, click, down, x, y))
1020 but = OSD_BG;
1021 }
1022 #endif
1023
1024 return but;
1025 }
1026
1027 #ifndef OSD_NO_DPAD
1028 static void
1029 osd_dpad_labels(cairo_t *cr, gint x, gint y) {
1030 /* move reference to dpad center */
1031 x += D_RAD;
1032 y += D_RAD;
1033
1034 const static gint offset[][3][2] = {
1035 /* left arrow/triangle */
1036 { { -D_TIP+D_LEN, -D_WID }, { -D_LEN, D_WID }, { +D_LEN, D_WID } },
1037 /* right arrow/triangle */
1038 { { +D_TIP-D_LEN, -D_WID }, { +D_LEN, D_WID }, { -D_LEN, D_WID } },
1039 /* top arrow/triangle */
1040 { { -D_WID, -D_TIP+D_LEN }, { D_WID, -D_LEN }, { D_WID, +D_LEN } },
1041 /* bottom arrow/triangle */
1042 { { -D_WID, +D_TIP-D_LEN }, { D_WID, +D_LEN }, { D_WID, -D_LEN } }
1043 };
1044
1045 int i;
1046 for(i=0;i<4;i++) {
1047 cairo_move_to (cr, x + offset[i][0][0], y + offset[i][0][1]);
1048 cairo_rel_line_to (cr, offset[i][1][0], offset[i][1][1]);
1049 cairo_rel_line_to (cr, offset[i][2][0], offset[i][2][1]);
1050 }
1051 }
1052 #endif
1053
1054 #ifdef OSD_GPS_BUTTON
1055 /* draw the satellite dish icon in the center of the dpad */
1056 #define GPS_V0 (D_RAD/7)
1057 #define GPS_V1 (D_RAD/10)
1058 #define GPS_V2 (D_RAD/5)
1059
1060 /* draw a satellite receiver dish */
1061 /* this is either drawn in the center of the dpad (if present) */
1062 /* or in the middle of the zoom area */
1063 static void
1064 osd_dpad_gps(cairo_t *cr, gint x, gint y) {
1065 /* move reference to dpad center */
1066 x += (1-Z_GPS) * D_RAD + Z_GPS * Z_RAD * 3;
1067 y += (1-Z_GPS) * D_RAD + Z_GPS * Z_RAD + GPS_V0;
1068
1069 cairo_move_to (cr, x-GPS_V0, y+GPS_V0);
1070 cairo_rel_line_to (cr, +GPS_V0, -GPS_V0);
1071 cairo_rel_line_to (cr, +GPS_V0, +GPS_V0);
1072 cairo_close_path (cr);
1073
1074 cairo_move_to (cr, x+GPS_V1-GPS_V2, y-2*GPS_V2);
1075 cairo_curve_to (cr, x-GPS_V2, y, x+GPS_V1, y+GPS_V1, x+GPS_V1+GPS_V2, y);
1076 cairo_close_path (cr);
1077
1078 x += GPS_V1;
1079 cairo_move_to (cr, x, y-GPS_V2);
1080 cairo_rel_line_to (cr, +GPS_V1, -GPS_V1);
1081 }
1082 #endif
1083
1084 #define Z_LEN (2*Z_RAD/3)
1085
1086 static void
1087 osd_zoom_labels(cairo_t *cr, gint x, gint y) {
1088 cairo_move_to (cr, x + Z_LEFT - Z_LEN, y + Z_MID);
1089 cairo_line_to (cr, x + Z_LEFT + Z_LEN, y + Z_MID);
1090
1091 cairo_move_to (cr, x + Z_RIGHT, y + Z_MID - Z_LEN);
1092 cairo_line_to (cr, x + Z_RIGHT, y + Z_MID + Z_LEN);
1093 cairo_move_to (cr, x + Z_RIGHT - Z_LEN, y + Z_MID);
1094 cairo_line_to (cr, x + Z_RIGHT + Z_LEN, y + Z_MID);
1095 }
1096
1097 #ifdef OSD_COORDINATES
1098
1099 #ifndef OSD_COORDINATES_FONT_SIZE
1100 #define OSD_COORDINATES_FONT_SIZE 12
1101 #endif
1102
1103 #define OSD_COORDINATES_OFFSET (OSD_COORDINATES_FONT_SIZE/6)
1104
1105 #define OSD_COORDINATES_W (8*OSD_COORDINATES_FONT_SIZE+2*OSD_COORDINATES_OFFSET)
1106 #define OSD_COORDINATES_H (2*OSD_COORDINATES_FONT_SIZE+2*OSD_COORDINATES_OFFSET+OSD_COORDINATES_FONT_SIZE/4)
1107
1108 /* these can be overwritten with versions that support */
1109 /* localization */
1110 #ifndef OSD_COORDINATES_CHR_N
1111 #define OSD_COORDINATES_CHR_N "N"
1112 #endif
1113 #ifndef OSD_COORDINATES_CHR_S
1114 #define OSD_COORDINATES_CHR_S "S"
1115 #endif
1116 #ifndef OSD_COORDINATES_CHR_E
1117 #define OSD_COORDINATES_CHR_E "E"
1118 #endif
1119 #ifndef OSD_COORDINATES_CHR_W
1120 #define OSD_COORDINATES_CHR_W "W"
1121 #endif
1122
1123
1124
1125 /* this is the classic geocaching notation */
1126 static char
1127 *osd_latitude_str(float latitude) {
1128 char *c = OSD_COORDINATES_CHR_N;
1129 float integral, fractional;
1130
1131 if(isnan(latitude))
1132 return NULL;
1133
1134 if(latitude < 0) {
1135 latitude = fabs(latitude);
1136 c = OSD_COORDINATES_CHR_S;
1137 }
1138
1139 fractional = modff(latitude, &integral);
1140
1141 return g_strdup_printf("%s %02d° %06.3f'",
1142 c, (int)integral, fractional*60.0);
1143 }
1144
1145 static char
1146 *osd_longitude_str(float longitude) {
1147 char *c = OSD_COORDINATES_CHR_E;
1148 float integral, fractional;
1149
1150 if(isnan(longitude))
1151 return NULL;
1152
1153 if(longitude < 0) {
1154 longitude = fabs(longitude);
1155 c = OSD_COORDINATES_CHR_W;
1156 }
1157
1158 fractional = modff(longitude, &integral);
1159
1160 return g_strdup_printf("%s %03d° %06.3f'",
1161 c, (int)integral, fractional*60.0);
1162 }
1163
1164 /* render a string at the given screen position */
1165 static int
1166 osd_render_centered_text(cairo_t *cr, int y, int width, char *text) {
1167 char *p = g_strdup(text);
1168 cairo_text_extents_t extents;
1169 cairo_text_extents (cr, p, &extents);
1170
1171 /* check if text needs to be truncated */
1172 int len = strlen(text)-2;
1173 while(extents.width > width) {
1174 len--;
1175 strcpy(p+len, "...");
1176 cairo_text_extents (cr, p, &extents);
1177 }
1178
1179 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1180 cairo_set_line_width (cr, OSD_COORDINATES_FONT_SIZE/6);
1181 cairo_move_to (cr, (width - extents.width)/2, y - extents.y_bearing);
1182 cairo_text_path (cr, p);
1183 cairo_stroke (cr);
1184
1185 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
1186 cairo_move_to (cr, (width - extents.width)/2, y - extents.y_bearing);
1187 cairo_show_text (cr, p);
1188
1189 g_free(p);
1190
1191 /* skip + 1/4 line */
1192 return y + 5*OSD_COORDINATES_FONT_SIZE/4;
1193 }
1194
1195 static void
1196 osd_render_coordinates(osm_gps_map_osd_t *osd)
1197 {
1198 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1199
1200 /* get current map position */
1201 gfloat lat, lon;
1202 g_object_get(osd->widget, "latitude", &lat, "longitude", &lon, NULL);
1203
1204 /* check if position has changed enough to require redraw */
1205 if(!isnan(priv->coordinates.lat) && !isnan(priv->coordinates.lon))
1206 /* 1/60000 == 1/1000 minute */
1207 if((fabsf(lat - priv->coordinates.lat) < 1/60000) &&
1208 (fabsf(lon - priv->coordinates.lon) < 1/60000))
1209 return;
1210
1211 priv->coordinates.lat = lat;
1212 priv->coordinates.lon = lon;
1213
1214 /* first fill with transparency */
1215 cairo_t *cr = cairo_create(priv->coordinates.surface);
1216 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1217 // cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 0.5);
1218 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
1219 cairo_paint(cr);
1220 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1221
1222 cairo_select_font_face (cr, "Sans",
1223 CAIRO_FONT_SLANT_NORMAL,
1224 CAIRO_FONT_WEIGHT_BOLD);
1225 cairo_set_font_size (cr, OSD_COORDINATES_FONT_SIZE);
1226
1227 char *latitude = osd_latitude_str(lat);
1228 char *longitude = osd_longitude_str(lon);
1229
1230 int y = OSD_COORDINATES_OFFSET;
1231 y = osd_render_centered_text(cr, y, OSD_COORDINATES_W, latitude);
1232 y = osd_render_centered_text(cr, y, OSD_COORDINATES_W, longitude);
1233
1234 g_free(latitude);
1235 g_free(longitude);
1236
1237 cairo_destroy(cr);
1238 }
1239 #endif // OSD_COORDINATES
1240
1241 #ifdef OSD_NAV
1242 #define OSD_NAV_W (8*OSD_COORDINATES_FONT_SIZE+2*OSD_COORDINATES_OFFSET)
1243 #define OSD_NAV_H (11*OSD_COORDINATES_FONT_SIZE)
1244
1245 /* http://mathforum.org/library/drmath/view/55417.html */
1246 static float get_bearing(float lat1, float lon1, float lat2, float lon2) {
1247 return atan2( sin(lon2 - lon1) * cos(lat2),
1248 cos(lat1) * sin(lat2) -
1249 sin(lat1) * cos(lat2) * cos(lon2 - lon1));
1250 }
1251
1252 /* http://mathforum.org/library/drmath/view/51722.html */
1253 static float get_distance(float lat1, float lon1, float lat2, float lon2) {
1254 float aob = acos(cos(lat1) * cos(lat2) * cos(lon2 - lon1) +
1255 sin(lat1) * sin(lat2));
1256
1257 // return(aob * 3959.0); /* great circle radius in miles */
1258
1259 return(aob * 6371000.0); /* great circle radius in meters */
1260 }
1261
1262 static void
1263 osd_render_nav(osm_gps_map_osd_t *osd)
1264 {
1265 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1266
1267 if(!priv->nav.surface || isnan(priv->nav.lat) || isnan(priv->nav.lon))
1268 return;
1269
1270 /* first fill with transparency */
1271 cairo_t *cr = cairo_create(priv->nav.surface);
1272 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1273 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
1274 cairo_paint(cr);
1275 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1276
1277 cairo_select_font_face (cr, "Sans",
1278 CAIRO_FONT_SLANT_NORMAL,
1279 CAIRO_FONT_WEIGHT_BOLD);
1280 cairo_set_font_size (cr, OSD_COORDINATES_FONT_SIZE);
1281
1282 char *latitude = osd_latitude_str(priv->nav.lat);
1283 char *longitude = osd_longitude_str(priv->nav.lon);
1284
1285 int y = OSD_COORDINATES_OFFSET;
1286 y = osd_render_centered_text(cr, y, OSD_NAV_W, priv->nav.name);
1287 y = osd_render_centered_text(cr, y, OSD_NAV_W, latitude);
1288 y = osd_render_centered_text(cr, y, OSD_NAV_W, longitude);
1289
1290 g_free(latitude);
1291 g_free(longitude);
1292
1293 /* draw the compass */
1294 int radius = (OSD_NAV_H - y - 5*OSD_COORDINATES_FONT_SIZE/4)/2;
1295 if(radius > OSD_NAV_W/2)
1296 radius = OSD_NAV_W/2;
1297
1298 int x = OSD_NAV_W/2+1;
1299 y += radius;
1300
1301 cairo_stroke (cr);
1302
1303 /* draw background */
1304 cairo_arc(cr, x, y, radius, 0, 2*M_PI);
1305 cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
1306 cairo_fill_preserve (cr);
1307 cairo_set_source_rgb (cr, 0, 0, 0);
1308 cairo_set_line_width (cr, 1);
1309 cairo_stroke (cr);
1310
1311 /* draw pointer */
1312 #define ARROW_WIDTH 0.3
1313 #define ARROW_LENGTH 0.7
1314
1315 coord_t *gps = osm_gps_map_get_gps (OSM_GPS_MAP(osd->widget));
1316 if(gps) {
1317 float arot = get_bearing(gps->rlat, gps->rlon,
1318 deg2rad(priv->nav.lat), deg2rad(priv->nav.lon));
1319
1320 cairo_move_to(cr,
1321 x + radius * ARROW_LENGTH * sin(arot),
1322 y + radius * ARROW_LENGTH * -cos(arot));
1323
1324 cairo_line_to(cr,
1325 x + radius * -ARROW_LENGTH * sin(arot+ARROW_WIDTH),
1326 y + radius * -ARROW_LENGTH * -cos(arot+ARROW_WIDTH));
1327
1328 cairo_line_to(cr,
1329 x + radius * -0.5 * ARROW_LENGTH * sin(arot),
1330 y + radius * -0.5 * ARROW_LENGTH * -cos(arot));
1331
1332 cairo_line_to(cr,
1333 x + radius * -ARROW_LENGTH * sin(arot-ARROW_WIDTH),
1334 y + radius * -ARROW_LENGTH * -cos(arot-ARROW_WIDTH));
1335
1336 cairo_close_path(cr);
1337 cairo_set_source_rgb (cr, 0, 0, 0);
1338 cairo_fill (cr);
1339
1340 y += radius + OSD_COORDINATES_FONT_SIZE/4;
1341
1342 float dist = get_distance(gps->rlat, gps->rlon,
1343 deg2rad(priv->nav.lat), deg2rad(priv->nav.lon));
1344
1345 char *dist_str = NULL;
1346 if(!priv->nav.imperial) {
1347 /* metric is easy ... */
1348 if(dist<1000)
1349 dist_str = g_strdup_printf("%u m", (int)dist);
1350 else
1351 dist_str = g_strdup_printf("%.1f km", dist/1000);
1352 } else {
1353 /* and now the hard part: scale for useful imperial values :-( */
1354 /* try to convert to feet, 1ft == 0.3048 m */
1355
1356 if(dist/(3*0.3048) >= 1760.0) /* more than 1760 yard? */
1357 dist_str = g_strdup_printf("%.1f mi", dist/(0.3048*3*1760.0));
1358 else if(dist/0.3048 >= 100) /* more than 100 feet? */
1359 dist_str = g_strdup_printf("%.1f yd", dist/(0.3048*3));
1360 else
1361 dist_str = g_strdup_printf("%.0f ft", dist/0.3048);
1362 }
1363
1364 y = osd_render_centered_text(cr, y, OSD_NAV_W, dist_str);
1365 g_free(dist_str);
1366 }
1367
1368 cairo_destroy(cr);
1369 }
1370
1371 void osm_gps_map_osd_clear_nav (OsmGpsMap *map) {
1372 g_return_if_fail (OSM_IS_GPS_MAP (map));
1373
1374 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
1375 g_return_if_fail (osd);
1376
1377 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1378 g_return_if_fail (priv);
1379
1380 if(priv->nav.surface) {
1381 cairo_surface_destroy(priv->nav.surface);
1382 priv->nav.surface = NULL;
1383 priv->nav.lat = OSM_GPS_MAP_INVALID;
1384 priv->nav.lon = OSM_GPS_MAP_INVALID;
1385 if(priv->nav.name) g_free(priv->nav.name);
1386 }
1387 osm_gps_map_redraw(map);
1388 }
1389
1390 void
1391 osm_gps_map_osd_draw_nav (OsmGpsMap *map, gboolean imperial,
1392 float latitude, float longitude, char *name) {
1393 g_return_if_fail (OSM_IS_GPS_MAP (map));
1394
1395 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
1396 g_return_if_fail (osd);
1397
1398 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1399 g_return_if_fail (priv);
1400
1401 osm_gps_map_osd_clear_nav (map);
1402
1403 /* allocate balloon surface */
1404 priv->nav.surface =
1405 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1406 OSD_NAV_W+2, OSD_NAV_H+2);
1407
1408 priv->nav.lat = latitude;
1409 priv->nav.lon = longitude;
1410 priv->nav.name = g_strdup(name);
1411 priv->nav.imperial = imperial;
1412
1413 osd_render_nav(osd);
1414
1415 osm_gps_map_redraw(map);
1416 }
1417
1418 #endif // OSD_NAV
1419
1420
1421 #ifdef OSD_CROSSHAIR
1422
1423 #ifndef OSD_CROSSHAIR_RADIUS
1424 #define OSD_CROSSHAIR_RADIUS 10
1425 #endif
1426
1427 #define OSD_CROSSHAIR_TICK (OSD_CROSSHAIR_RADIUS/2)
1428 #define OSD_CROSSHAIR_BORDER (OSD_CROSSHAIR_TICK + OSD_CROSSHAIR_RADIUS/4)
1429 #define OSD_CROSSHAIR_W ((OSD_CROSSHAIR_RADIUS+OSD_CROSSHAIR_BORDER)*2)
1430 #define OSD_CROSSHAIR_H ((OSD_CROSSHAIR_RADIUS+OSD_CROSSHAIR_BORDER)*2)
1431
1432 static void
1433 osd_render_crosshair_shape(cairo_t *cr) {
1434 cairo_arc (cr, OSD_CROSSHAIR_W/2, OSD_CROSSHAIR_H/2,
1435 OSD_CROSSHAIR_RADIUS, 0, 2*M_PI);
1436
1437 cairo_move_to (cr, OSD_CROSSHAIR_W/2 - OSD_CROSSHAIR_RADIUS,
1438 OSD_CROSSHAIR_H/2);
1439 cairo_rel_line_to (cr, -OSD_CROSSHAIR_TICK, 0);
1440 cairo_move_to (cr, OSD_CROSSHAIR_W/2 + OSD_CROSSHAIR_RADIUS,
1441 OSD_CROSSHAIR_H/2);
1442 cairo_rel_line_to (cr, OSD_CROSSHAIR_TICK, 0);
1443
1444 cairo_move_to (cr, OSD_CROSSHAIR_W/2,
1445 OSD_CROSSHAIR_H/2 - OSD_CROSSHAIR_RADIUS);
1446 cairo_rel_line_to (cr, 0, -OSD_CROSSHAIR_TICK);
1447 cairo_move_to (cr, OSD_CROSSHAIR_W/2,
1448 OSD_CROSSHAIR_H/2 + OSD_CROSSHAIR_RADIUS);
1449 cairo_rel_line_to (cr, 0, OSD_CROSSHAIR_TICK);
1450
1451 cairo_stroke (cr);
1452 }
1453
1454 static void
1455 osd_render_crosshair(osm_gps_map_osd_t *osd)
1456 {
1457 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1458
1459 if(priv->crosshair.rendered)
1460 return;
1461
1462 priv->crosshair.rendered = TRUE;
1463
1464 /* first fill with transparency */
1465 cairo_t *cr = cairo_create(priv->crosshair.surface);
1466 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1467 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0);
1468 cairo_paint(cr);
1469 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1470
1471 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
1472
1473 cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.5);
1474 cairo_set_line_width (cr, OSD_CROSSHAIR_RADIUS/2);
1475 osd_render_crosshair_shape(cr);
1476
1477 cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1478 cairo_set_line_width (cr, OSD_CROSSHAIR_RADIUS/4);
1479 osd_render_crosshair_shape(cr);
1480
1481 cairo_destroy(cr);
1482 }
1483 #endif
1484
1485 #ifdef OSD_SCALE
1486
1487 #ifndef OSD_SCALE_FONT_SIZE
1488 #define OSD_SCALE_FONT_SIZE 12
1489 #endif
1490 #define OSD_SCALE_W (10*OSD_SCALE_FONT_SIZE)
1491 #define OSD_SCALE_H (5*OSD_SCALE_FONT_SIZE/2)
1492
1493 /* various parameters used to create the scale */
1494 #define OSD_SCALE_H2 (OSD_SCALE_H/2)
1495 #define OSD_SCALE_TICK (2*OSD_SCALE_FONT_SIZE/3)
1496 #define OSD_SCALE_M (OSD_SCALE_H2 - OSD_SCALE_TICK)
1497 #define OSD_SCALE_I (OSD_SCALE_H2 + OSD_SCALE_TICK)
1498 #define OSD_SCALE_FD (OSD_SCALE_FONT_SIZE/4)
1499
1500 static void
1501 osd_render_scale(osm_gps_map_osd_t *osd)
1502 {
1503 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1504
1505 /* this only needs to be rendered if the zoom has changed */
1506 gint zoom;
1507 g_object_get(OSM_GPS_MAP(osd->widget), "zoom", &zoom, NULL);
1508 if(zoom == priv->scale.zoom)
1509 return;
1510
1511 priv->scale.zoom = zoom;
1512
1513 float m_per_pix = osm_gps_map_get_scale(OSM_GPS_MAP(osd->widget));
1514
1515 /* first fill with transparency */
1516 cairo_t *cr = cairo_create(priv->scale.surface);
1517 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1518 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
1519 // pink for testing: cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.2);
1520 cairo_paint(cr);
1521 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1522
1523 /* determine the size of the scale width in meters */
1524 float width = (OSD_SCALE_W-OSD_SCALE_FONT_SIZE/6) * m_per_pix;
1525
1526 /* scale this to useful values */
1527 int exp = logf(width)*M_LOG10E;
1528 int mant = width/pow(10,exp);
1529 int width_metric = mant * pow(10,exp);
1530 char *dist_str = NULL;
1531 if(width_metric<1000)
1532 dist_str = g_strdup_printf("%u m", width_metric);
1533 else
1534 dist_str = g_strdup_printf("%u km", width_metric/1000);
1535 width_metric /= m_per_pix;
1536
1537 /* and now the hard part: scale for useful imperial values :-( */
1538 /* try to convert to feet, 1ft == 0.3048 m */
1539 width /= 0.3048;
1540 float imp_scale = 0.3048;
1541 char *dist_imp_unit = "ft";
1542
1543 if(width >= 100) {
1544 /* 1yd == 3 feet */
1545 width /= 3.0;
1546 imp_scale *= 3.0;
1547 dist_imp_unit = "yd";
1548
1549 if(width >= 1760.0) {
1550 /* 1mi == 1760 yd */
1551 width /= 1760.0;
1552 imp_scale *= 1760.0;
1553 dist_imp_unit = "mi";
1554 }
1555 }
1556
1557 /* also convert this to full tens/hundreds */
1558 exp = logf(width)*M_LOG10E;
1559 mant = width/pow(10,exp);
1560 int width_imp = mant * pow(10,exp);
1561 char *dist_str_imp = g_strdup_printf("%u %s", width_imp, dist_imp_unit);
1562
1563 /* convert back to pixels */
1564 width_imp *= imp_scale;
1565 width_imp /= m_per_pix;
1566
1567 cairo_select_font_face (cr, "Sans",
1568 CAIRO_FONT_SLANT_NORMAL,
1569 CAIRO_FONT_WEIGHT_BOLD);
1570 cairo_set_font_size (cr, OSD_SCALE_FONT_SIZE);
1571 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
1572
1573 cairo_text_extents_t extents;
1574 cairo_text_extents (cr, dist_str, &extents);
1575
1576 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
1577 cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/6);
1578 cairo_move_to (cr, 2*OSD_SCALE_FD, OSD_SCALE_H2-OSD_SCALE_FD);
1579 cairo_text_path (cr, dist_str);
1580 cairo_stroke (cr);
1581 cairo_move_to (cr, 2*OSD_SCALE_FD,
1582 OSD_SCALE_H2+OSD_SCALE_FD + extents.height);
1583 cairo_text_path (cr, dist_str_imp);
1584 cairo_stroke (cr);
1585
1586 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
1587 cairo_move_to (cr, 2*OSD_SCALE_FD, OSD_SCALE_H2-OSD_SCALE_FD);
1588 cairo_show_text (cr, dist_str);
1589 cairo_move_to (cr, 2*OSD_SCALE_FD,
1590 OSD_SCALE_H2+OSD_SCALE_FD + extents.height);
1591 cairo_show_text (cr, dist_str_imp);
1592
1593 g_free(dist_str);
1594 g_free(dist_str_imp);
1595
1596 /* draw white line */
1597 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
1598 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
1599 cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/3);
1600 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_M);
1601 cairo_rel_line_to (cr, 0, OSD_SCALE_TICK);
1602 cairo_rel_line_to (cr, width_metric, 0);
1603 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
1604 cairo_stroke(cr);
1605 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_I);
1606 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
1607 cairo_rel_line_to (cr, width_imp, 0);
1608 cairo_rel_line_to (cr, 0, +OSD_SCALE_TICK);
1609 cairo_stroke(cr);
1610
1611 /* draw black line */
1612 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
1613 cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/6);
1614 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_M);
1615 cairo_rel_line_to (cr, 0, OSD_SCALE_TICK);
1616 cairo_rel_line_to (cr, width_metric, 0);
1617 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
1618 cairo_stroke(cr);
1619 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_I);
1620 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
1621 cairo_rel_line_to (cr, width_imp, 0);
1622 cairo_rel_line_to (cr, 0, +OSD_SCALE_TICK);
1623 cairo_stroke(cr);
1624
1625 cairo_destroy(cr);
1626 }
1627 #endif
1628
1629 static void
1630 osd_render_controls(osm_gps_map_osd_t *osd)
1631 {
1632 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1633
1634 if(priv->controls.rendered
1635 #ifdef OSD_GPS_BUTTON
1636 && (priv->controls.gps_enabled == (osd->cb != NULL))
1637 #endif
1638 )
1639 return;
1640
1641 #ifdef OSD_GPS_BUTTON
1642 priv->controls.gps_enabled = (osd->cb != NULL);
1643 #endif
1644 priv->controls.rendered = TRUE;
1645
1646 #ifndef OSD_COLOR
1647 GdkColor bg = GTK_WIDGET(osd->widget)->style->bg[GTK_STATE_NORMAL];
1648 GdkColor fg = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_NORMAL];
1649 GdkColor da = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_INSENSITIVE];
1650 #endif
1651
1652 /* first fill with transparency */
1653 cairo_t *cr = cairo_create(priv->controls.surface);
1654 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
1655 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
1656 cairo_paint(cr);
1657 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
1658
1659 /* --------- draw zoom and dpad shape shadow ----------- */
1660 #ifdef OSD_SHADOW_ENABLE
1661 osd_zoom_shape(cr, 1+OSD_SHADOW, 1+OSD_SHADOW);
1662 osd_shape_shadow(cr);
1663 #ifndef OSD_NO_DPAD
1664 osd_dpad_shape(cr, 1+OSD_SHADOW, 1+OSD_SHADOW);
1665 osd_shape_shadow(cr);
1666 #endif
1667 #endif
1668
1669 /* --------- draw zoom and dpad shape ----------- */
1670
1671 osd_zoom_shape(cr, 1, 1);
1672 #ifndef OSD_COLOR
1673 osd_shape(cr, &bg, &fg);
1674 #else
1675 osd_shape(cr);
1676 #endif
1677 #ifndef OSD_NO_DPAD
1678 osd_dpad_shape(cr, 1, 1);
1679 #ifndef OSD_COLOR
1680 osd_shape(cr, &bg, &fg);
1681 #else
1682 osd_shape(cr);
1683 #endif
1684 #endif
1685
1686 /* --------- draw zoom and dpad labels --------- */
1687
1688 #ifdef OSD_SHADOW_ENABLE
1689 osd_labels_shadow(cr, Z_RAD/3, TRUE);
1690 osd_zoom_labels(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW);
1691 #ifndef OSD_NO_DPAD
1692 osd_dpad_labels(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW);
1693 #endif
1694 cairo_stroke(cr);
1695 #ifdef OSD_GPS_BUTTON
1696 osd_labels_shadow(cr, Z_RAD/6, osd->cb != NULL);
1697 osd_dpad_gps(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW);
1698 cairo_stroke(cr);
1699 #endif
1700 #endif
1701
1702 #ifndef OSD_COLOR
1703 osd_labels(cr, Z_RAD/3, TRUE, &fg, &da);
1704 #else
1705 osd_labels(cr, Z_RAD/3, TRUE);
1706 #endif
1707 osd_zoom_labels(cr, 1, 1);
1708 #ifndef OSD_NO_DPAD
1709 osd_dpad_labels(cr, 1, 1);
1710 #endif
1711 cairo_stroke(cr);
1712
1713 #ifndef OSD_COLOR
1714 osd_labels(cr, Z_RAD/6, osd->cb != NULL, &fg, &da);
1715 #else
1716 osd_labels(cr, Z_RAD/6, osd->cb != NULL);
1717 #endif
1718 #ifdef OSD_GPS_BUTTON
1719 osd_dpad_gps(cr, 1, 1);
1720 #endif
1721 cairo_stroke(cr);
1722
1723 cairo_destroy(cr);
1724 }
1725
1726 static void
1727 osd_render(osm_gps_map_osd_t *osd)
1728 {
1729 /* this function is actually called pretty often since the */
1730 /* OSD contents may have changed (due to a coordinate/zoom change). */
1731 /* The different OSD parts have to make sure that they don't */
1732 /* render unneccessarily often and thus waste CPU power */
1733
1734 osd_render_controls(osd);
1735
1736 #ifdef OSD_SOURCE_SEL
1737 osd_render_source_sel(osd, FALSE);
1738 #endif
1739
1740 #ifdef OSD_SCALE
1741 osd_render_scale(osd);
1742 #endif
1743
1744 #ifdef OSD_CROSSHAIR
1745 osd_render_crosshair(osd);
1746 #endif
1747
1748 #ifdef OSD_NAV
1749 osd_render_nav(osd);
1750 #endif
1751
1752 #ifdef OSD_COORDINATES
1753 osd_render_coordinates(osd);
1754 #endif
1755 }
1756
1757 static void
1758 osd_draw(osm_gps_map_osd_t *osd, GdkDrawable *drawable)
1759 {
1760 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1761
1762 /* OSD itself uses some off-screen rendering, so check if the */
1763 /* offscreen buffer is present and create it if not */
1764 if(!priv->controls.surface) {
1765 /* create overlay ... */
1766 priv->controls.surface =
1767 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, OSD_W+2, OSD_H+2);
1768
1769 priv->controls.rendered = FALSE;
1770 #ifdef OSD_GPS_BUTTON
1771 priv->controls.gps_enabled = FALSE;
1772 #endif
1773
1774 #ifdef OSD_SOURCE_SEL
1775 /* the initial OSD state is alway not-expanded */
1776 priv->source_sel.surface =
1777 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1778 OSD_S_W+2, OSD_S_H+2);
1779 priv->source_sel.rendered = FALSE;
1780 #endif
1781
1782 #ifdef OSD_SCALE
1783 priv->scale.surface =
1784 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1785 OSD_SCALE_W, OSD_SCALE_H);
1786 priv->scale.zoom = -1;
1787 #endif
1788
1789 #ifdef OSD_CROSSHAIR
1790 priv->crosshair.surface =
1791 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1792 OSD_CROSSHAIR_W, OSD_CROSSHAIR_H);
1793 priv->crosshair.rendered = FALSE;
1794 #endif
1795
1796 #ifdef OSD_COORDINATES
1797 priv->coordinates.surface =
1798 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1799 OSD_COORDINATES_W, OSD_COORDINATES_H);
1800
1801 priv->coordinates.lat = priv->coordinates.lon = OSM_GPS_MAP_INVALID;
1802 #endif
1803
1804 /* ... and render it */
1805 osd_render(osd);
1806 }
1807
1808 // now draw this onto the original context
1809 cairo_t *cr = gdk_cairo_create(drawable);
1810
1811 gint x, y;
1812
1813 #ifdef OSD_SCALE
1814 x = OSD_X;
1815 y = -OSD_Y;
1816 if(x < 0) x += osd->widget->allocation.width - OSD_SCALE_W;
1817 if(y < 0) y += osd->widget->allocation.height - OSD_SCALE_H;
1818
1819 cairo_set_source_surface(cr, priv->scale.surface, x, y);
1820 cairo_paint(cr);
1821 #endif
1822
1823 #ifdef OSD_CROSSHAIR
1824 x = (osd->widget->allocation.width - OSD_CROSSHAIR_W)/2;
1825 y = (osd->widget->allocation.height - OSD_CROSSHAIR_H)/2;
1826
1827 cairo_set_source_surface(cr, priv->crosshair.surface, x, y);
1828 cairo_paint(cr);
1829 #endif
1830
1831 #ifdef OSD_NAV
1832 if(priv->nav.surface) {
1833 x = OSD_X;
1834 if(x < 0) x += osd->widget->allocation.width - OSD_NAV_W;
1835 y = (osd->widget->allocation.height - OSD_NAV_H)/2;
1836
1837 cairo_set_source_surface(cr, priv->nav.surface, x, y);
1838 cairo_paint(cr);
1839 }
1840 #endif
1841
1842 #ifdef OSD_COORDINATES
1843 x = -OSD_X;
1844 y = -OSD_Y;
1845 if(x < 0) x += osd->widget->allocation.width - OSD_COORDINATES_W;
1846 if(y < 0) y += osd->widget->allocation.height - OSD_COORDINATES_H;
1847
1848 cairo_set_source_surface(cr, priv->coordinates.surface, x, y);
1849 cairo_paint(cr);
1850 #endif
1851
1852 #ifdef OSD_BALLOON
1853 if(priv->balloon.surface) {
1854
1855 /* convert given lat lon into screen coordinates */
1856 gint x, y;
1857 osm_gps_map_geographic_to_screen (OSM_GPS_MAP(osd->widget),
1858 priv->balloon.lat, priv->balloon.lon,
1859 &x, &y);
1860
1861 /* check if balloon needs to be rerendered */
1862 osd_render_balloon(osd);
1863
1864 cairo_set_source_surface(cr, priv->balloon.surface,
1865 x + priv->balloon.offset_x,
1866 y + priv->balloon.offset_y);
1867 cairo_paint(cr);
1868 }
1869 #endif
1870
1871 x = OSD_X;
1872 if(x < 0)
1873 x += osd->widget->allocation.width - OSD_W;
1874
1875 y = OSD_Y;
1876 if(y < 0)
1877 y += osd->widget->allocation.height - OSD_H;
1878
1879 cairo_set_source_surface(cr, priv->controls.surface, x, y);
1880 cairo_paint(cr);
1881
1882 #ifdef OSD_SOURCE_SEL
1883 if(!priv->source_sel.handler_id) {
1884 /* the OSD source selection is not being animated */
1885 if(!priv->source_sel.expanded)
1886 x = osd->widget->allocation.width - OSD_S_W;
1887 else
1888 x = osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X;
1889 } else
1890 x = priv->source_sel.shift;
1891
1892 y = OSD_S_Y;
1893 if(OSD_S_Y < 0) {
1894 if(!priv->source_sel.expanded)
1895 y = osd->widget->allocation.height - OSD_S_H + OSD_S_Y;
1896 else
1897 y = osd->widget->allocation.height - OSD_S_EXP_H + OSD_S_Y;
1898 }
1899
1900 cairo_set_source_surface(cr, priv->source_sel.surface, x, y);
1901 cairo_paint(cr);
1902 #endif
1903
1904 cairo_destroy(cr);
1905 }
1906
1907 static void
1908 osd_free(osm_gps_map_osd_t *osd)
1909 {
1910 osd_priv_t *priv = (osd_priv_t *)(osd->priv);
1911
1912 if (priv->controls.surface)
1913 cairo_surface_destroy(priv->controls.surface);
1914
1915 #ifdef OSD_SOURCE_SEL
1916 if(priv->source_sel.handler_id)
1917 gtk_timeout_remove(priv->source_sel.handler_id);
1918
1919 if (priv->source_sel.surface)
1920 cairo_surface_destroy(priv->source_sel.surface);
1921 #endif
1922
1923 #ifdef OSD_SCALE
1924 if (priv->scale.surface)
1925 cairo_surface_destroy(priv->scale.surface);
1926 #endif
1927
1928 #ifdef OSD_CROSSHAIR
1929 if (priv->crosshair.surface)
1930 cairo_surface_destroy(priv->crosshair.surface);
1931 #endif
1932
1933 #ifdef OSD_NAV
1934 if (priv->nav.surface)
1935 cairo_surface_destroy(priv->nav.surface);
1936 #endif
1937
1938 #ifdef OSD_COORDINATES
1939 if (priv->coordinates.surface)
1940 cairo_surface_destroy(priv->coordinates.surface);
1941 #endif
1942
1943 #ifdef OSD_BALLOON
1944 if (priv->balloon.surface)
1945 cairo_surface_destroy(priv->balloon.surface);
1946 #endif
1947
1948 g_free(priv);
1949 }
1950
1951 static gboolean
1952 osd_busy(osm_gps_map_osd_t *osd)
1953 {
1954 #ifdef OSD_SOURCE_SEL
1955 osd_priv_t *priv = (osd_priv_t *)(osd->priv);
1956 return (priv->source_sel.handler_id != 0);
1957 #else
1958 return FALSE;
1959 #endif
1960 }
1961
1962 static osd_button_t
1963 osd_check(osm_gps_map_osd_t *osd, gboolean down, gint x, gint y) {
1964 return osd_check_int(osd, TRUE, down, x, y);
1965 }
1966
1967 static osm_gps_map_osd_t osd_classic = {
1968 .widget = NULL,
1969
1970 .draw = osd_draw,
1971 .check = osd_check,
1972 .render = osd_render,
1973 .free = osd_free,
1974 .busy = osd_busy,
1975
1976 .cb = NULL,
1977 .data = NULL,
1978
1979 .priv = NULL
1980 };
1981
1982 /* this is the only function that's externally visible */
1983 void
1984 osm_gps_map_osd_classic_init(OsmGpsMap *map)
1985 {
1986 osd_priv_t *priv = osd_classic.priv = g_new0(osd_priv_t, 1);
1987
1988 #ifdef OSD_BALLOON
1989 priv->balloon.lat = OSM_GPS_MAP_INVALID;
1990 priv->balloon.lon = OSM_GPS_MAP_INVALID;
1991 #endif
1992
1993 osd_classic.priv = priv;
1994
1995 osm_gps_map_register_osd(map, &osd_classic);
1996 }
1997
1998 #ifdef OSD_GPS_BUTTON
1999 /* below are osd specific functions which aren't used by osm-gps-map */
2000 /* but instead are to be used by the main application */
2001 void osm_gps_map_osd_enable_gps (OsmGpsMap *map, OsmGpsMapOsdCallback cb,
2002 gpointer data) {
2003 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
2004 g_return_if_fail (osd);
2005
2006 osd->cb = cb;
2007 osd->data = data;
2008
2009 /* this may have changed the state of the gps button */
2010 /* we thus re-render the overlay */
2011 osd->render(osd);
2012
2013 osm_gps_map_redraw(map);
2014 }
2015 #endif
2016
2017 osd_button_t
2018 osm_gps_map_osd_check(OsmGpsMap *map, gint x, gint y) {
2019 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
2020 g_return_val_if_fail (osd, OSD_NONE);
2021
2022 return osd_check_int(osd, FALSE, TRUE, x, y);
2023 }