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

Parent Directory Parent Directory | Revision Log Revision Log


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