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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 105 - (show annotations)
Wed Sep 9 19:57:45 2009 UTC (14 years, 8 months ago) by harbaum
File MIME type: text/plain
File size: 34171 byte(s)
OSD crosshair
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 <math.h> // M_PI/cos()
23
24 /* parameters that can be overwritten from the config file: */
25 /* OSD_DIAMETER */
26 /* OSD_X, OSD_Y */
27
28 #define OSD_CROSSHAIR
29
30 #ifndef USE_CAIRO
31 #error "OSD control display lacks a non-cairo implementation!"
32 #endif
33
34 #include <cairo.h>
35
36 #include "osm-gps-map.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 cairo_surface_t *overlay;
43
44 #ifdef OSD_SCALE
45 cairo_surface_t *scale;
46 int scale_zoom;
47 #endif
48
49 #ifdef OSD_CROSSHAIR
50 cairo_surface_t *crosshair;
51 #endif
52
53 #ifdef OSD_SOURCE_SEL
54 /* values to handle the "source" menu */
55 cairo_surface_t *map_source;
56 gboolean expanded;
57 gint shift, dir, count;
58 gint handler_id;
59 gint width, height;
60 #endif
61
62 } osd_priv_t;
63
64 /* position and extent of bounding box */
65 #ifndef OSD_X
66 #define OSD_X (10)
67 #endif
68
69 #ifndef OSD_Y
70 #define OSD_Y (10)
71 #endif
72
73 /* parameters of the direction shape */
74 #ifndef OSD_DIAMETER
75 #define D_RAD (30) // diameter of dpad
76 #else
77 #define D_RAD (OSD_DIAMETER)
78 #endif
79 #define D_TIP (4*D_RAD/5) // distance of arrow tip from dpad center
80 #define D_LEN (D_RAD/4) // length of arrow
81 #define D_WID (D_LEN) // width of arrow
82
83 /* parameters of the "zoom" pad */
84 #define Z_STEP (D_RAD/4) // distance between dpad and zoom
85 #define Z_RAD (D_RAD/2) // radius of "caps" of zoom bar
86
87 #ifdef OSD_SHADOW_ENABLE
88 /* shadow also depends on control size */
89 #define OSD_SHADOW (D_RAD/6)
90 #else
91 #define OSD_SHADOW (0)
92 #endif
93
94 /* normally the GPS button is in the center of the dpad. if there's */
95 /* no dpad it will go into the zoom area */
96 #if defined(OSD_GPS_BUTTON) && defined(OSD_NO_DPAD)
97 #define Z_GPS 1
98 #else
99 #define Z_GPS 0
100 #endif
101
102 /* total width and height of controls incl. shadow */
103 #define OSD_W (2*D_RAD + OSD_SHADOW + Z_GPS * 2 * Z_RAD)
104 #if !Z_GPS
105 #define OSD_H (2*D_RAD + Z_STEP + 2*Z_RAD + OSD_SHADOW)
106 #else
107 #define OSD_H (2*Z_RAD + OSD_SHADOW)
108 #endif
109
110 #ifdef OSD_SHADOW_ENABLE
111 #define OSD_LBL_SHADOW (OSD_SHADOW/2)
112 #endif
113
114 #define Z_TOP ((1-Z_GPS) * (2 * D_RAD + Z_STEP))
115
116 #define Z_MID (Z_TOP + Z_RAD)
117 #define Z_BOT (Z_MID + Z_RAD)
118 #define Z_LEFT (Z_RAD)
119 #define Z_RIGHT (2 * D_RAD - Z_RAD + Z_GPS * 2 * Z_RAD)
120 #define Z_CENTER ((Z_RIGHT + Z_LEFT)/2)
121
122 /* create the cairo shape used for the zoom buttons */
123 static void
124 osd_zoom_shape(cairo_t *cr, gint x, gint y)
125 {
126 cairo_move_to (cr, x+Z_LEFT, y+Z_TOP);
127 cairo_line_to (cr, x+Z_RIGHT, y+Z_TOP);
128 cairo_arc (cr, x+Z_RIGHT, y+Z_MID, Z_RAD, -M_PI/2, M_PI/2);
129 cairo_line_to (cr, x+Z_LEFT, y+Z_BOT);
130 cairo_arc (cr, x+Z_LEFT, y+Z_MID, Z_RAD, M_PI/2, -M_PI/2);
131 }
132
133 /* ------------------- color/shadow functions ----------------- */
134
135 #ifndef OSD_COLOR
136 /* if no color has been specified we just use the gdks default colors */
137 static void
138 osd_labels(cairo_t *cr, gint width, gboolean enabled,
139 GdkColor *fg, GdkColor *disabled) {
140 if(enabled) gdk_cairo_set_source_color(cr, fg);
141 else gdk_cairo_set_source_color(cr, disabled);
142 cairo_set_line_width (cr, width);
143 }
144 #else
145 static void
146 osd_labels(cairo_t *cr, gint width, gboolean enabled) {
147 if(enabled) cairo_set_source_rgb (cr, OSD_COLOR);
148 else cairo_set_source_rgb (cr, OSD_COLOR_DISABLED);
149 cairo_set_line_width (cr, width);
150 }
151 #endif
152
153 #ifdef OSD_SHADOW_ENABLE
154 static void
155 osd_labels_shadow(cairo_t *cr, gint width, gboolean enabled) {
156 cairo_set_source_rgba (cr, 0, 0, 0, enabled?0.3:0.15);
157 cairo_set_line_width (cr, width);
158 }
159 #endif
160
161 #ifndef OSD_NO_DPAD
162 /* create the cairo shape used for the dpad */
163 static void
164 osd_dpad_shape(cairo_t *cr, gint x, gint y)
165 {
166 cairo_arc (cr, x+D_RAD, y+D_RAD, D_RAD, 0, 2 * M_PI);
167 }
168 #endif
169
170 #ifdef OSD_SHADOW_ENABLE
171 static void
172 osd_shape_shadow(cairo_t *cr) {
173 cairo_set_source_rgba (cr, 0, 0, 0, 0.2);
174 cairo_fill (cr);
175 cairo_stroke (cr);
176 }
177 #endif
178
179 #ifndef OSD_COLOR
180 /* if no color has been specified we just use the gdks default colors */
181 static void
182 osd_shape(cairo_t *cr, GdkColor *bg, GdkColor *fg) {
183 gdk_cairo_set_source_color(cr, bg);
184 cairo_fill_preserve (cr);
185 gdk_cairo_set_source_color(cr, fg);
186 cairo_set_line_width (cr, 1);
187 cairo_stroke (cr);
188 }
189 #else
190 static void
191 osd_shape(cairo_t *cr) {
192 cairo_set_source_rgb (cr, OSD_COLOR_BG);
193 cairo_fill_preserve (cr);
194 cairo_set_source_rgb (cr, OSD_COLOR);
195 cairo_set_line_width (cr, 1);
196 cairo_stroke (cr);
197 }
198 #endif
199
200
201 static gboolean
202 osm_gps_map_in_circle(gint x, gint y, gint cx, gint cy, gint rad)
203 {
204 return( pow(cx - x, 2) + pow(cy - y, 2) < rad * rad);
205 }
206
207 #ifndef OSD_NO_DPAD
208 /* check whether x/y is within the dpad */
209 static osd_button_t
210 osd_check_dpad(gint x, gint y)
211 {
212 /* within entire dpad circle */
213 if( osm_gps_map_in_circle(x, y, D_RAD, D_RAD, D_RAD))
214 {
215 /* convert into position relative to dpads centre */
216 x -= D_RAD;
217 y -= D_RAD;
218
219 #ifdef OSD_GPS_BUTTON
220 /* check for dpad center goes here! */
221 if( osm_gps_map_in_circle(x, y, 0, 0, D_RAD/3))
222 return OSD_GPS;
223 #endif
224
225 if( y < 0 && abs(x) < abs(y))
226 return OSD_UP;
227
228 if( y > 0 && abs(x) < abs(y))
229 return OSD_DOWN;
230
231 if( x < 0 && abs(y) < abs(x))
232 return OSD_LEFT;
233
234 if( x > 0 && abs(y) < abs(x))
235 return OSD_RIGHT;
236
237 return OSD_BG;
238 }
239 return OSD_NONE;
240 }
241 #endif
242
243 /* check whether x/y is within the zoom pads */
244 static osd_button_t
245 osd_check_zoom(gint x, gint y) {
246 if( x > 0 && x < OSD_W && y > Z_TOP && y < Z_BOT) {
247
248 /* within circle around (-) label */
249 if( osm_gps_map_in_circle(x, y, Z_LEFT, Z_MID, Z_RAD))
250 return OSD_OUT;
251
252 /* within circle around (+) label */
253 if( osm_gps_map_in_circle(x, y, Z_RIGHT, Z_MID, Z_RAD))
254 return OSD_IN;
255
256 #if Z_GPS == 1
257 /* within square around center */
258 if( x > Z_CENTER - Z_RAD && x < Z_CENTER + Z_RAD)
259 return OSD_GPS;
260 #endif
261
262 /* between center of (-) button and center of entire zoom control area */
263 if(x > OSD_LEFT && x < D_RAD)
264 return OSD_OUT;
265
266 /* between center of (+) button and center of entire zoom control area */
267 if(x < OSD_RIGHT && x > D_RAD)
268 return OSD_IN;
269 }
270
271 return OSD_NONE;
272 }
273
274 #ifdef OSD_SOURCE_SEL
275
276 /* place source selection at right border */
277 #define OSD_S_RAD (Z_RAD)
278 #define OSD_S_X (-OSD_X)
279 #define OSD_S_Y (OSD_Y)
280 #define OSD_S_PW (2 * Z_RAD)
281 #define OSD_S_W (OSD_S_PW)
282 #define OSD_S_PH (2 * Z_RAD)
283 #define OSD_S_H (OSD_S_PH + OSD_SHADOW)
284
285 /* size of usable area when expanded */
286 #define OSD_S_AREA_W (priv->width)
287 #define OSD_S_AREA_H (priv->height)
288 #define OSD_S_EXP_W (OSD_S_PW + OSD_S_AREA_W + OSD_SHADOW)
289 #define OSD_S_EXP_H (OSD_S_AREA_H + OSD_SHADOW)
290
291 /* internal value to draw the arrow on the "puller" */
292 #define OSD_S_D0 (OSD_S_RAD/2)
293 #ifndef OSD_FONT_SIZE
294 #define OSD_FONT_SIZE 16.0
295 #endif
296 #define OSD_TEXT_BORDER (OSD_FONT_SIZE/2)
297 #define OSD_TEXT_SKIP (OSD_FONT_SIZE/8)
298
299 /* draw the shape of the source selection OSD, either only the puller (not expanded) */
300 /* or the entire menu incl. the puller (expanded) */
301 static void
302 osd_source_shape(osd_priv_t *priv, cairo_t *cr, gint x, gint y) {
303 if(!priv->expanded) {
304 /* just draw the puller */
305 cairo_move_to (cr, x + OSD_S_PW, y + OSD_S_PH);
306 cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2);
307 cairo_line_to (cr, x + OSD_S_PW, y);
308 } else {
309 /* draw the puller and the area itself */
310 cairo_move_to (cr, x + OSD_S_PW + OSD_S_AREA_W, y + OSD_S_AREA_H);
311 cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_AREA_H);
312 if(OSD_S_Y > 0) {
313 cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_PH);
314 cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2);
315 } else {
316 cairo_arc (cr, x+OSD_S_RAD, y+OSD_S_AREA_H-OSD_S_RAD, OSD_S_RAD, M_PI/2, -M_PI/2);
317 cairo_line_to (cr, x + OSD_S_PW, y + OSD_S_AREA_H - OSD_S_PH);
318 cairo_line_to (cr, x + OSD_S_PW, y);
319 }
320 cairo_line_to (cr, x + OSD_S_PW + OSD_S_AREA_W, y);
321 cairo_close_path (cr);
322 }
323 }
324
325 static void
326 osd_source_content(osm_gps_map_osd_t *osd, cairo_t *cr, gint offset) {
327 osd_priv_t *priv = (osd_priv_t*)osd->priv;
328
329 int py = offset + OSD_S_RAD - OSD_S_D0;
330
331 if(!priv->expanded) {
332 /* draw the "puller" open (<) arrow */
333 cairo_move_to (cr, offset + OSD_S_RAD + OSD_S_D0/2, py);
334 cairo_rel_line_to (cr, -OSD_S_D0, +OSD_S_D0);
335 cairo_rel_line_to (cr, +OSD_S_D0, +OSD_S_D0);
336 } else {
337 if(OSD_S_Y < 0)
338 py += OSD_S_AREA_H - OSD_S_PH;
339
340 /* draw the "puller" close (>) arrow */
341 cairo_move_to (cr, offset + OSD_S_RAD - OSD_S_D0/2, py);
342 cairo_rel_line_to (cr, +OSD_S_D0, +OSD_S_D0);
343 cairo_rel_line_to (cr, -OSD_S_D0, +OSD_S_D0);
344 cairo_stroke(cr);
345
346 /* don't draw a shadow for the text content */
347 if(offset == 1) {
348 gint source;
349 g_object_get(osd->widget, "map-source", &source, NULL);
350
351 cairo_select_font_face (cr, "Sans",
352 CAIRO_FONT_SLANT_NORMAL,
353 CAIRO_FONT_WEIGHT_BOLD);
354 cairo_set_font_size (cr, OSD_FONT_SIZE);
355
356 int i, step = (priv->height - 2*OSD_TEXT_BORDER) /
357 OSM_GPS_MAP_SOURCE_LAST;
358 for(i=OSM_GPS_MAP_SOURCE_NULL+1;i<=OSM_GPS_MAP_SOURCE_LAST;i++) {
359 cairo_text_extents_t extents;
360 const char *src = osm_gps_map_source_get_friendly_name(i);
361 cairo_text_extents (cr, src, &extents);
362
363 int x = offset + OSD_S_PW + OSD_TEXT_BORDER;
364 int y = offset + step * (i-1) + OSD_TEXT_BORDER;
365
366 /* draw filled rectangle if selected */
367 if(source == i) {
368 cairo_rectangle(cr, x - OSD_TEXT_BORDER/2,
369 y - OSD_TEXT_SKIP,
370 priv->width - OSD_TEXT_BORDER,
371 step + OSD_TEXT_SKIP);
372 cairo_fill(cr);
373
374 /* temprarily draw with background color */
375 #ifndef OSD_COLOR
376 GdkColor bg = osd->widget->style->bg[GTK_STATE_NORMAL];
377 gdk_cairo_set_source_color(cr, &bg);
378 #else
379 cairo_set_source_rgb (cr, OSD_COLOR_BG);
380 #endif
381 }
382
383 cairo_move_to (cr, x, y + OSD_TEXT_SKIP - extents.y_bearing);
384 cairo_show_text (cr, src);
385
386 /* restore color */
387 if(source == i) {
388 #ifndef OSD_COLOR
389 GdkColor fg = osd->widget->style->fg[GTK_STATE_NORMAL];
390 gdk_cairo_set_source_color(cr, &fg);
391 #else
392 cairo_set_source_rgb (cr, OSD_COLOR);
393 #endif
394 }
395 }
396 }
397 }
398 }
399
400 static void
401 osd_render_source_sel(osm_gps_map_osd_t *osd) {
402 osd_priv_t *priv = (osd_priv_t*)osd->priv;
403
404 #ifndef OSD_COLOR
405 GdkColor bg = GTK_WIDGET(osd->widget)->style->bg[GTK_STATE_NORMAL];
406 GdkColor fg = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_NORMAL];
407 GdkColor da = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_INSENSITIVE];
408 #endif
409
410 /* draw source selector */
411 cairo_t *cr = cairo_create(priv->map_source);
412 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
413 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
414 cairo_paint(cr);
415 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
416
417 #ifdef OSD_SHADOW_ENABLE
418 osd_source_shape(priv, cr, 1+OSD_SHADOW, 1+OSD_SHADOW);
419 osd_shape_shadow(cr);
420 #endif
421
422 osd_source_shape(priv, cr, 1, 1);
423 #ifndef OSD_COLOR
424 osd_shape(cr, &bg, &fg);
425 #else
426 osd_shape(cr);
427 #endif
428
429 #ifdef OSD_SHADOW_ENABLE
430 osd_labels_shadow(cr, Z_RAD/3, TRUE);
431 osd_source_content(osd, cr, 1+OSD_LBL_SHADOW);
432 cairo_stroke (cr);
433 #endif
434 #ifndef OSD_COLOR
435 osd_labels(cr, Z_RAD/3, TRUE, &fg, &da);
436 #else
437 osd_labels(cr, Z_RAD/3, TRUE);
438 #endif
439 osd_source_content(osd, cr, 1);
440 cairo_stroke (cr);
441
442 cairo_destroy(cr);
443 }
444
445 /* re-allocate the buffer used to draw the menu. This is used */
446 /* to collapse/expand the buffer */
447 static void
448 osd_source_reallocate(osm_gps_map_osd_t *osd) {
449 osd_priv_t *priv = (osd_priv_t*)osd->priv;
450
451 /* re-allocate offscreen bitmap */
452 g_assert (priv->map_source);
453
454 int w = OSD_S_W, h = OSD_S_H;
455 if(priv->expanded) {
456 cairo_text_extents_t extents;
457
458 /* determine content size */
459 cairo_t *cr = cairo_create(priv->map_source);
460 cairo_select_font_face (cr, "Sans",
461 CAIRO_FONT_SLANT_NORMAL,
462 CAIRO_FONT_WEIGHT_BOLD);
463 cairo_set_font_size (cr, OSD_FONT_SIZE);
464
465 /* calculate menu size */
466 int i, max_h = 0, max_w = 0;
467 for(i=OSM_GPS_MAP_SOURCE_NULL+1;i<=OSM_GPS_MAP_SOURCE_LAST;i++) {
468 const char *src = osm_gps_map_source_get_friendly_name(i);
469 cairo_text_extents (cr, src, &extents);
470
471 if(extents.width > max_w) max_w = extents.width;
472 if(extents.height > max_h) max_h = extents.height;
473 }
474 cairo_destroy(cr);
475
476 priv->width = max_w + 2*OSD_TEXT_BORDER;
477 priv->height = OSM_GPS_MAP_SOURCE_LAST *
478 (max_h + 2*OSD_TEXT_SKIP) + 2*OSD_TEXT_BORDER;
479
480 w = OSD_S_EXP_W;
481 h = OSD_S_EXP_H;
482 }
483
484 cairo_surface_destroy(priv->map_source);
485 priv->map_source =
486 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w+2, h+2);
487
488 osd_render_source_sel(osd);
489 }
490
491 #define OSD_HZ 15
492 #define OSD_TIME 500
493
494 static gboolean osd_source_animate(gpointer data) {
495 osm_gps_map_osd_t *osd = (osm_gps_map_osd_t*)data;
496 osd_priv_t *priv = (osd_priv_t*)osd->priv;
497 int diff = OSD_S_EXP_W - OSD_S_W - OSD_S_X;
498 gboolean done = FALSE;
499 priv->count += priv->dir;
500
501 /* shifting in */
502 if(priv->dir < 0) {
503 if(priv->count <= 0) {
504 priv->count = 0;
505 done = TRUE;
506 }
507 } else {
508 if(priv->count >= 1000) {
509 priv->expanded = FALSE;
510 osd_source_reallocate(osd);
511
512 priv->count = 1000;
513 done = TRUE;
514 }
515 }
516
517
518 /* count runs linearly from 0 to 1000, map this nicely onto a position */
519
520 /* nicer sinoid mapping */
521 float m = 0.5-cos(priv->count * M_PI / 1000.0)/2;
522 priv->shift = (osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X) +
523 m * diff;
524
525 osm_gps_map_repaint(OSM_GPS_MAP(osd->widget));
526
527 if(done)
528 priv->handler_id = 0;
529
530 return !done;
531 }
532
533 /* switch between expand and collapse mode of source selection */
534 static void
535 osd_source_toggle(osm_gps_map_osd_t *osd)
536 {
537 osd_priv_t *priv = (osd_priv_t*)osd->priv;
538
539 /* ignore clicks while animation is running */
540 if(priv->handler_id)
541 return;
542
543 /* expand immediately, collapse is handle at the end of the collapse animation */
544 if(!priv->expanded) {
545 priv->expanded = TRUE;
546 osd_source_reallocate(osd);
547
548 priv->count = 1000;
549 priv->shift = osd->widget->allocation.width - OSD_S_W;
550 priv->dir = -1000/OSD_HZ;
551 } else {
552 priv->count = 0;
553 priv->shift = osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X;
554 priv->dir = +1000/OSD_HZ;
555 }
556
557 priv->handler_id = gtk_timeout_add(OSD_TIME/OSD_HZ, osd_source_animate, osd);
558 }
559
560 /* check if the user clicked inside the source selection area */
561 static osd_button_t
562 osd_source_check(osm_gps_map_osd_t *osd, gint x, gint y) {
563 osd_priv_t *priv = (osd_priv_t*)osd->priv;
564
565 if(!priv->expanded)
566 x -= osd->widget->allocation.width - OSD_S_W;
567 else
568 x -= osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X;
569
570 if(OSD_S_Y > 0)
571 y -= OSD_S_Y;
572 else
573 y -= osd->widget->allocation.height - OSD_S_PH + OSD_S_Y;
574
575 /* within square around puller? */
576 if(y > 0 && y < OSD_S_PH && x > 0 && x < OSD_S_PW) {
577 /* really within puller shape? */
578 if(x > Z_RAD || osm_gps_map_in_circle(x, y, Z_RAD, Z_RAD, Z_RAD)) {
579 /* expand source selector */
580 osd_source_toggle(osd);
581
582 /* tell upper layers that user clicked some background element */
583 /* of the OSD */
584 return OSD_BG;
585 }
586 }
587
588 /* check for clicks into data area */
589 if(priv->expanded && !priv->handler_id) {
590 /* re-adjust from puller top to content top */
591 if(OSD_S_Y < 0)
592 y += OSD_S_EXP_H - OSD_S_PH;
593
594 if(x > OSD_S_PW &&
595 x < OSD_S_PW + OSD_S_EXP_W &&
596 y > 0 &&
597 y < OSD_S_EXP_H) {
598
599 int step = (priv->height - 2*OSD_TEXT_BORDER)
600 / OSM_GPS_MAP_SOURCE_LAST;
601
602 y -= OSD_TEXT_BORDER - OSD_TEXT_SKIP;
603 y /= step;
604 y += 1;
605
606 gint old = 0;
607 g_object_get(osd->widget, "map-source", &old, NULL);
608
609 if(y > OSM_GPS_MAP_SOURCE_NULL &&
610 y <= OSM_GPS_MAP_SOURCE_LAST &&
611 old != y) {
612 g_object_set(osd->widget, "map-source", y, NULL);
613
614 osd_render_source_sel(osd);
615 osm_gps_map_repaint(OSM_GPS_MAP(osd->widget));
616 }
617
618 /* return "clicked in OSD background" to prevent further */
619 /* processing by application */
620 return OSD_BG;
621 }
622 }
623
624 return OSD_NONE;
625 }
626 #endif // OSD_SOURCE_SEL
627
628 static osd_button_t
629 osd_check(osm_gps_map_osd_t *osd, gint x, gint y) {
630 osd_button_t but = OSD_NONE;
631
632 #ifdef OSD_SOURCE_SEL
633 /* the source selection area is handles internally */
634 but = osd_source_check(osd, x, y);
635 if(but != OSD_NONE)
636 return but;
637 #endif
638
639 x -= OSD_X;
640 y -= OSD_Y;
641
642 if(OSD_X < 0)
643 x -= (osd->widget->allocation.width - OSD_W);
644
645 if(OSD_Y < 0)
646 y -= (osd->widget->allocation.height - OSD_H);
647
648 /* first do a rough test for the OSD area. */
649 /* this is just to avoid an unnecessary detailed test */
650 if(x > 0 && x < OSD_W && y > 0 && y < OSD_H) {
651 #ifndef OSD_NO_DPAD
652 but = osd_check_dpad(x, y);
653 #endif
654
655 if(but == OSD_NONE)
656 but = osd_check_zoom(x, y);
657 }
658
659 return but;
660 }
661
662 #ifndef OSD_NO_DPAD
663 static void
664 osd_dpad_labels(cairo_t *cr, gint x, gint y) {
665 /* move reference to dpad center */
666 x += D_RAD;
667 y += D_RAD;
668
669 const static gint offset[][3][2] = {
670 /* left arrow/triangle */
671 { { -D_TIP+D_LEN, -D_WID }, { -D_LEN, D_WID }, { +D_LEN, D_WID } },
672 /* right arrow/triangle */
673 { { +D_TIP-D_LEN, -D_WID }, { +D_LEN, D_WID }, { -D_LEN, D_WID } },
674 /* top arrow/triangle */
675 { { -D_WID, -D_TIP+D_LEN }, { D_WID, -D_LEN }, { D_WID, +D_LEN } },
676 /* bottom arrow/triangle */
677 { { -D_WID, +D_TIP-D_LEN }, { D_WID, +D_LEN }, { D_WID, -D_LEN } }
678 };
679
680 int i;
681 for(i=0;i<4;i++) {
682 cairo_move_to (cr, x + offset[i][0][0], y + offset[i][0][1]);
683 cairo_rel_line_to (cr, offset[i][1][0], offset[i][1][1]);
684 cairo_rel_line_to (cr, offset[i][2][0], offset[i][2][1]);
685 }
686 }
687 #endif
688
689 #ifdef OSD_GPS_BUTTON
690 /* draw the satellite dish icon in the center of the dpad */
691 #define GPS_V0 (D_RAD/7)
692 #define GPS_V1 (D_RAD/10)
693 #define GPS_V2 (D_RAD/5)
694
695 /* draw a satellite receiver dish */
696 /* this is either drawn in the center of the dpad (if present) */
697 /* or in the middle of the zoom area */
698 static void
699 osd_dpad_gps(cairo_t *cr, gint x, gint y) {
700 /* move reference to dpad center */
701 x += (1-Z_GPS) * D_RAD + Z_GPS * Z_RAD * 3;
702 y += (1-Z_GPS) * D_RAD + Z_GPS * Z_RAD + GPS_V0;
703
704 cairo_move_to (cr, x-GPS_V0, y+GPS_V0);
705 cairo_rel_line_to (cr, +GPS_V0, -GPS_V0);
706 cairo_rel_line_to (cr, +GPS_V0, +GPS_V0);
707 cairo_close_path (cr);
708
709 cairo_move_to (cr, x+GPS_V1-GPS_V2, y-2*GPS_V2);
710 cairo_curve_to (cr, x-GPS_V2, y, x+GPS_V1, y+GPS_V1, x+GPS_V1+GPS_V2, y);
711 cairo_close_path (cr);
712
713 x += GPS_V1;
714 cairo_move_to (cr, x, y-GPS_V2);
715 cairo_rel_line_to (cr, +GPS_V1, -GPS_V1);
716 }
717 #endif
718
719 #define Z_LEN (2*Z_RAD/3)
720
721 static void
722 osd_zoom_labels(cairo_t *cr, gint x, gint y) {
723 cairo_move_to (cr, x + Z_LEFT - Z_LEN, y + Z_MID);
724 cairo_line_to (cr, x + Z_LEFT + Z_LEN, y + Z_MID);
725
726 cairo_move_to (cr, x + Z_RIGHT, y + Z_MID - Z_LEN);
727 cairo_line_to (cr, x + Z_RIGHT, y + Z_MID + Z_LEN);
728 cairo_move_to (cr, x + Z_RIGHT - Z_LEN, y + Z_MID);
729 cairo_line_to (cr, x + Z_RIGHT + Z_LEN, y + Z_MID);
730 }
731
732 #ifdef OSD_CROSSHAIR
733
734 #ifndef OSD_CROSSHAIR_RADIUS
735 #define OSD_CROSSHAIR_RADIUS 20
736 #endif
737
738 #define OSD_CROSSHAIR_W (OSD_CROSSHAIR_RADIUS*2)
739 #define OSD_CROSSHAIR_H (OSD_CROSSHAIR_RADIUS*2)
740
741 static void
742 osd_render_crosshair(osm_gps_map_osd_t *osd)
743 {
744 osd_priv_t *priv = (osd_priv_t*)osd->priv;
745
746 /* first fill with transparency */
747 cairo_t *cr = cairo_create(priv->crosshair);
748 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
749 // cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
750 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.2);
751 cairo_paint(cr);
752 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
753
754 cairo_destroy(cr);
755 }
756 #endif
757
758 #ifdef OSD_SCALE
759
760 #ifndef OSD_SCALE_FONT_SIZE
761 #define OSD_SCALE_FONT_SIZE 12
762 #endif
763 #define OSD_SCALE_W (10*OSD_SCALE_FONT_SIZE)
764 #define OSD_SCALE_H (5*OSD_SCALE_FONT_SIZE/2)
765
766 /* various parameters used to create the scale */
767 #define OSD_SCALE_H2 (OSD_SCALE_H/2)
768 #define OSD_SCALE_TICK (2*OSD_SCALE_FONT_SIZE/3)
769 #define OSD_SCALE_M (OSD_SCALE_H2 - OSD_SCALE_TICK)
770 #define OSD_SCALE_I (OSD_SCALE_H2 + OSD_SCALE_TICK)
771 #define OSD_SCALE_FD (OSD_SCALE_FONT_SIZE/4)
772
773 static void
774 osd_render_scale(osm_gps_map_osd_t *osd)
775 {
776 osd_priv_t *priv = (osd_priv_t*)osd->priv;
777
778 /* this only needs to be rendered if the zoom has changed */
779 gint zoom;
780 g_object_get(OSM_GPS_MAP(osd->widget), "zoom", &zoom, NULL);
781 if(zoom == priv->scale_zoom)
782 return;
783
784 priv->scale_zoom = zoom;
785
786 float m_per_pix = osm_gps_map_get_scale(OSM_GPS_MAP(osd->widget));
787
788 /* first fill with transparency */
789 cairo_t *cr = cairo_create(priv->scale);
790 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
791 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
792 // pink for testing: cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.2);
793 cairo_paint(cr);
794 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
795
796 /* determine the size of the scale width in meters */
797 float width = (OSD_SCALE_W-OSD_SCALE_FONT_SIZE/6) * m_per_pix;
798
799 /* scale this to useful values */
800 int exp = logf(width)*M_LOG10E;
801 int mant = width/pow(10,exp);
802 int width_metric = mant * pow(10,exp);
803 char *dist_str = NULL;
804 if(width_metric<1000)
805 dist_str = g_strdup_printf("%u m", width_metric);
806 else
807 dist_str = g_strdup_printf("%u km", width_metric/1000);
808 width_metric /= m_per_pix;
809
810 /* and now the hard part: scale for useful imperial values :-( */
811 /* try to convert to feet, 1ft == 0.3048 m */
812 width /= 0.3048;
813 float imp_scale = 0.3048;
814 char *dist_imp_unit = "ft";
815
816 if(width >= 100) {
817 /* 1yd == 3 feet */
818 width /= 3.0;
819 imp_scale *= 3.0;
820 dist_imp_unit = "yd";
821
822 if(width >= 1760.0) {
823 /* 1mi == 1760 yd */
824 width /= 1760.0;
825 imp_scale *= 1760.0;
826 dist_imp_unit = "mi";
827 }
828 }
829
830 /* also convert this to full tens/hundreds */
831 exp = logf(width)*M_LOG10E;
832 mant = width/pow(10,exp);
833 int width_imp = mant * pow(10,exp);
834 char *dist_str_imp = g_strdup_printf("%u %s", width_imp, dist_imp_unit);
835
836 /* convert back to pixels */
837 width_imp *= imp_scale;
838 width_imp /= m_per_pix;
839
840 cairo_select_font_face (cr, "Sans",
841 CAIRO_FONT_SLANT_NORMAL,
842 CAIRO_FONT_WEIGHT_BOLD);
843 cairo_set_font_size (cr, OSD_SCALE_FONT_SIZE);
844 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
845
846 cairo_text_extents_t extents;
847 cairo_text_extents (cr, dist_str, &extents);
848
849 cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
850 cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/6);
851 cairo_move_to (cr, 2*OSD_SCALE_FD, OSD_SCALE_H2-OSD_SCALE_FD);
852 cairo_text_path (cr, dist_str);
853 cairo_stroke (cr);
854 cairo_move_to (cr, 2*OSD_SCALE_FD,
855 OSD_SCALE_H2+OSD_SCALE_FD + extents.height);
856 cairo_text_path (cr, dist_str_imp);
857 cairo_stroke (cr);
858
859 cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
860 cairo_move_to (cr, 2*OSD_SCALE_FD, OSD_SCALE_H2-OSD_SCALE_FD);
861 cairo_show_text (cr, dist_str);
862 cairo_move_to (cr, 2*OSD_SCALE_FD,
863 OSD_SCALE_H2+OSD_SCALE_FD + extents.height);
864 cairo_show_text (cr, dist_str_imp);
865
866 g_free(dist_str);
867 g_free(dist_str_imp);
868
869 /* draw white line */
870 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
871 cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0);
872 cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/3);
873 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_M);
874 cairo_rel_line_to (cr, 0, OSD_SCALE_TICK);
875 cairo_rel_line_to (cr, width_metric, 0);
876 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
877 cairo_stroke(cr);
878 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_I);
879 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
880 cairo_rel_line_to (cr, width_imp, 0);
881 cairo_rel_line_to (cr, 0, +OSD_SCALE_TICK);
882 cairo_stroke(cr);
883
884 /* draw black line */
885 cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0);
886 cairo_set_line_width (cr, OSD_SCALE_FONT_SIZE/6);
887 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_M);
888 cairo_rel_line_to (cr, 0, OSD_SCALE_TICK);
889 cairo_rel_line_to (cr, width_metric, 0);
890 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
891 cairo_stroke(cr);
892 cairo_move_to (cr, OSD_SCALE_FONT_SIZE/6, OSD_SCALE_I);
893 cairo_rel_line_to (cr, 0, -OSD_SCALE_TICK);
894 cairo_rel_line_to (cr, width_imp, 0);
895 cairo_rel_line_to (cr, 0, +OSD_SCALE_TICK);
896 cairo_stroke(cr);
897
898 cairo_destroy(cr);
899 }
900 #endif
901
902 static void
903 osd_render(osm_gps_map_osd_t *osd)
904 {
905 osd_priv_t *priv = (osd_priv_t*)osd->priv;
906
907 #ifndef OSD_COLOR
908 GdkColor bg = GTK_WIDGET(osd->widget)->style->bg[GTK_STATE_NORMAL];
909 GdkColor fg = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_NORMAL];
910 GdkColor da = GTK_WIDGET(osd->widget)->style->fg[GTK_STATE_INSENSITIVE];
911 #endif
912
913 /* first fill with transparency */
914 cairo_t *cr = cairo_create(priv->overlay);
915 cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
916 cairo_set_source_rgba(cr, 1.0, 0.0, 0.0, 0.0);
917 cairo_paint(cr);
918 cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
919
920 /* --------- draw zoom and dpad shape shadow ----------- */
921 #ifdef OSD_SHADOW_ENABLE
922 osd_zoom_shape(cr, 1+OSD_SHADOW, 1+OSD_SHADOW);
923 osd_shape_shadow(cr);
924 #ifndef OSD_NO_DPAD
925 osd_dpad_shape(cr, 1+OSD_SHADOW, 1+OSD_SHADOW);
926 osd_shape_shadow(cr);
927 #endif
928 #endif
929
930 /* --------- draw zoom and dpad shape ----------- */
931
932 osd_zoom_shape(cr, 1, 1);
933 #ifndef OSD_COLOR
934 osd_shape(cr, &bg, &fg);
935 #else
936 osd_shape(cr);
937 #endif
938 #ifndef OSD_NO_DPAD
939 osd_dpad_shape(cr, 1, 1);
940 #ifndef OSD_COLOR
941 osd_shape(cr, &bg, &fg);
942 #else
943 osd_shape(cr);
944 #endif
945 #endif
946
947 /* --------- draw zoom and dpad labels --------- */
948
949 #ifdef OSD_SHADOW_ENABLE
950 osd_labels_shadow(cr, Z_RAD/3, TRUE);
951 osd_zoom_labels(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW);
952 #ifndef OSD_NO_DPAD
953 osd_dpad_labels(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW);
954 #endif
955 cairo_stroke(cr);
956 #ifdef OSD_GPS_BUTTON
957 osd_labels_shadow(cr, Z_RAD/6, osd->cb != NULL);
958 osd_dpad_gps(cr, 1+OSD_LBL_SHADOW, 1+OSD_LBL_SHADOW);
959 cairo_stroke(cr);
960 #endif
961 #endif
962
963 #ifndef OSD_COLOR
964 osd_labels(cr, Z_RAD/3, TRUE, &fg, &da);
965 #else
966 osd_labels(cr, Z_RAD/3, TRUE);
967 #endif
968 osd_zoom_labels(cr, 1, 1);
969 #ifndef OSD_NO_DPAD
970 osd_dpad_labels(cr, 1, 1);
971 #endif
972 cairo_stroke(cr);
973
974 #ifndef OSD_COLOR
975 osd_labels(cr, Z_RAD/6, osd->cb != NULL, &fg, &da);
976 #else
977 osd_labels(cr, Z_RAD/6, osd->cb != NULL);
978 #endif
979 #ifdef OSD_GPS_BUTTON
980 osd_dpad_gps(cr, 1, 1);
981 #endif
982 cairo_stroke(cr);
983
984 cairo_destroy(cr);
985
986 #ifdef OSD_SOURCE_SEL
987 osd_render_source_sel(osd);
988 #endif
989
990 #ifdef OSD_SCALE
991 osd_render_scale(osd);
992 #endif
993
994 #ifdef OSD_CROSSHAIR
995 osd_render_crosshair(osd);
996 #endif
997 }
998
999 static void
1000 osd_draw(osm_gps_map_osd_t *osd, GdkDrawable *drawable)
1001 {
1002 osd_priv_t *priv = (osd_priv_t*)osd->priv;
1003
1004 /* OSD itself uses some off-screen rendering, so check if the */
1005 /* offscreen buffer is present and create it if not */
1006 if(!priv->overlay) {
1007 /* create overlay ... */
1008 priv->overlay =
1009 cairo_image_surface_create(CAIRO_FORMAT_ARGB32, OSD_W+2, OSD_H+2);
1010
1011 #ifdef OSD_SOURCE_SEL
1012 /* the initial OSD state is alway not-expanded */
1013 priv->map_source =
1014 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1015 OSD_S_W+2, OSD_S_H+2);
1016 #endif
1017
1018 #ifdef OSD_SCALE
1019 priv->scale =
1020 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1021 OSD_SCALE_W, OSD_SCALE_H);
1022 priv->scale_zoom = -1;
1023 #endif
1024
1025 #ifdef OSD_CROSSHAIR
1026 priv->crosshair =
1027 cairo_image_surface_create(CAIRO_FORMAT_ARGB32,
1028 OSD_CROSSHAIR_W, OSD_CROSSHAIR_H);
1029 #endif
1030
1031 /* ... and render it */
1032 osd_render(osd);
1033 }
1034
1035 // now draw this onto the original context
1036 cairo_t *cr = gdk_cairo_create(drawable);
1037
1038 int x = OSD_X, y = OSD_Y;
1039 if(OSD_X < 0)
1040 x = osd->widget->allocation.width - OSD_W + OSD_X;
1041
1042 if(OSD_Y < 0)
1043 y = osd->widget->allocation.height - OSD_H + OSD_Y;
1044
1045 cairo_set_source_surface(cr, priv->overlay, x, y);
1046 cairo_paint(cr);
1047
1048 #ifdef OSD_SOURCE_SEL
1049 if(!priv->handler_id) {
1050 /* the OSD source selection is not being animated */
1051 if(!priv->expanded)
1052 x = osd->widget->allocation.width - OSD_S_W;
1053 else
1054 x = osd->widget->allocation.width - OSD_S_EXP_W + OSD_S_X;
1055 } else
1056 x = priv->shift;
1057
1058 y = OSD_S_Y;
1059 if(OSD_S_Y < 0) {
1060 if(!priv->expanded)
1061 y = osd->widget->allocation.height - OSD_S_H + OSD_S_Y;
1062 else
1063 y = osd->widget->allocation.height - OSD_S_EXP_H + OSD_S_Y;
1064 }
1065
1066 cairo_set_source_surface(cr, priv->map_source, x, y);
1067 cairo_paint(cr);
1068 #endif
1069
1070 #ifdef OSD_SCALE
1071 x = OSD_X;
1072 y = -OSD_Y;
1073 if(x < 0) x += osd->widget->allocation.width - OSD_SCALE_W;
1074 if(y < 0) y += osd->widget->allocation.height - OSD_SCALE_H;
1075
1076 cairo_set_source_surface(cr, priv->scale, x, y);
1077 cairo_paint(cr);
1078 #endif
1079
1080 #ifdef OSD_CROSSHAIR
1081 x = (osd->widget->allocation.width - OSD_CROSSHAIR_W)/2;
1082 y = (osd->widget->allocation.height - OSD_CROSSHAIR_H)/2;
1083
1084 cairo_set_source_surface(cr, priv->crosshair, x, y);
1085 cairo_paint(cr);
1086 #endif
1087
1088 cairo_destroy(cr);
1089 }
1090
1091 static void
1092 osd_free(osm_gps_map_osd_t *osd)
1093 {
1094 osd_priv_t *priv = (osd_priv_t *)(osd->priv);
1095
1096 if (priv->overlay)
1097 cairo_surface_destroy(priv->overlay);
1098
1099 #ifdef OSD_SOURCE_SEL
1100 if(priv->handler_id)
1101 gtk_timeout_remove(priv->handler_id);
1102
1103 if (priv->map_source)
1104 cairo_surface_destroy(priv->map_source);
1105 #endif
1106
1107 #ifdef OSD_SCALE
1108 if (priv->scale)
1109 cairo_surface_destroy(priv->scale);
1110 #endif
1111
1112 #ifdef OSD_CROSSHAIR
1113 if (priv->crosshair)
1114 cairo_surface_destroy(priv->crosshair);
1115 #endif
1116
1117 g_free(priv);
1118 }
1119
1120 static gboolean
1121 osd_busy(osm_gps_map_osd_t *osd)
1122 {
1123 #ifdef OSD_SOURCE_SEL
1124 osd_priv_t *priv = (osd_priv_t *)(osd->priv);
1125 return (priv->handler_id != 0);
1126 #else
1127 return FALSE;
1128 #endif
1129 }
1130
1131 static osm_gps_map_osd_t osd_classic = {
1132 .widget = NULL,
1133
1134 .draw = osd_draw,
1135 .check = osd_check,
1136 .render = osd_render,
1137 .free = osd_free,
1138 .busy = osd_busy,
1139
1140 .cb = NULL,
1141 .data = NULL,
1142
1143 .priv = NULL
1144 };
1145
1146 /* this is the only function that's externally visible */
1147 void
1148 osm_gps_map_osd_classic_init(OsmGpsMap *map)
1149 {
1150 osd_priv_t *priv = osd_classic.priv = g_new0(osd_priv_t, 1);
1151
1152 osd_classic.priv = priv;
1153
1154 osm_gps_map_register_osd(map, &osd_classic);
1155 }
1156
1157 #ifdef OSD_GPS_BUTTON
1158 /* below are osd specific functions which aren't used by osm-gps-map */
1159 /* but instead are to be used by the main application */
1160 void osm_gps_map_osd_enable_gps (OsmGpsMap *map, OsmGpsMapOsdCallback cb,
1161 gpointer data) {
1162 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
1163 g_return_if_fail (osd);
1164
1165 osd->cb = cb;
1166 osd->data = data;
1167
1168 /* this may have changed the state of the gps button */
1169 /* we thus re-render the overlay */
1170 osd->render(osd);
1171
1172 osm_gps_map_redraw(map);
1173 }
1174 #endif
1175
1176 osd_button_t
1177 osm_gps_map_osd_check(OsmGpsMap *map, gint x, gint y) {
1178 osm_gps_map_osd_t *osd = osm_gps_map_osd_get(map);
1179 g_return_val_if_fail (osd, OSD_NONE);
1180
1181 return osd_check(osd, x, y);
1182 }