No issues found
Tool | Failure ID | Location | Function | Message | Data |
---|---|---|---|---|---|
clang-analyzer | no-output-found | e-map.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None | |
clang-analyzer | no-output-found | e-map.c | Message(text='Unable to locate XML output from invoke-clang-analyzer') | None |
1 /*
2 * Map widget.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with the program; if not, see <http://www.gnu.org/licenses/>
16 *
17 *
18 * Authors:
19 * Hans Petter Jansson <hpj@ximian.com>
20 *
21 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
22 *
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <math.h>
30 #include <stdlib.h>
31 #include <gdk/gdkkeysyms.h>
32 #include <glib/gi18n.h>
33
34 #include "e-util/e-util-private.h"
35 #include "e-util/e-util.h"
36
37 #include "e-map.h"
38
39 #define E_MAP_GET_PRIVATE(obj) \
40 (G_TYPE_INSTANCE_GET_PRIVATE \
41 ((obj), E_TYPE_MAP, EMapPrivate))
42
43 #define E_MAP_TWEEN_TIMEOUT_MSECS 25
44 #define E_MAP_TWEEN_DURATION_MSECS 150
45
46 /* Scroll step increment */
47
48 #define SCROLL_STEP_SIZE 32
49
50 /* */
51
52 #define E_MAP_GET_WIDTH(map) gtk_adjustment_get_upper((map)->priv->hadjustment)
53 #define E_MAP_GET_HEIGHT(map) gtk_adjustment_get_upper((map)->priv->vadjustment)
54
55 /* Zoom state - keeps track of animation hacks */
56
57 typedef enum
58 {
59 E_MAP_ZOOMED_IN,
60 E_MAP_ZOOMED_OUT,
61 E_MAP_ZOOMING_IN,
62 E_MAP_ZOOMING_OUT
63 }
64 EMapZoomState;
65
66 /* The Tween struct used for zooming */
67
68 typedef struct _EMapTween EMapTween;
69
70 struct _EMapTween {
71 guint start_time;
72 guint end_time;
73 gdouble longitude_offset;
74 gdouble latitude_offset;
75 gdouble zoom_factor;
76 };
77
78 /* Private part of the EMap structure */
79
80 struct _EMapPrivate {
81 /* Pointer to map image */
82 GdkPixbuf *map_pixbuf;
83 cairo_surface_t *map_render_surface;
84
85 /* Settings */
86 gboolean frozen, smooth_zoom;
87
88 /* Adjustments for scrolling */
89 GtkAdjustment *hadjustment;
90 GtkAdjustment *vadjustment;
91
92 /* GtkScrollablePolicy needs to be checked when
93 * driving the scrollable adjustment values */
94 guint hscroll_policy : 1;
95 guint vscroll_policy : 1;
96
97 /* Current scrolling offsets */
98 gint xofs, yofs;
99
100 /* Realtime zoom data */
101 EMapZoomState zoom_state;
102 gdouble zoom_target_long, zoom_target_lat;
103
104 /* Dots */
105 GPtrArray *points;
106
107 /* Tweens */
108 GSList *tweens;
109 GTimer *timer;
110 guint timer_current_ms;
111 guint tween_id;
112 };
113
114 /* Properties */
115
116 enum {
117 PROP_0,
118
119 /* For scrollable interface */
120 PROP_HADJUSTMENT,
121 PROP_VADJUSTMENT,
122 PROP_HSCROLL_POLICY,
123 PROP_VSCROLL_POLICY
124 };
125
126 /* Internal prototypes */
127
128 static void update_render_surface (EMap *map, gboolean render_overlays);
129 static void set_scroll_area (EMap *map, gint width, gint height);
130 static void center_at (EMap *map, gdouble longitude, gdouble latitude);
131 static void scroll_to (EMap *map, gint x, gint y);
132 static gint load_map_background (EMap *map, gchar *name);
133 static void update_and_paint (EMap *map);
134 static void update_render_point (EMap *map, EMapPoint *point);
135 static void repaint_point (EMap *map, EMapPoint *point);
136
137 /* ------ *
138 * Tweens *
139 * ------ */
140
141 static gboolean
142 e_map_is_tweening (EMap *map)
143 {
144 return map->priv->timer != NULL;
145 }
146
147 static void
148 e_map_stop_tweening (EMap *map)
149 {
150 g_assert (map->priv->tweens == NULL);
151
152 if (!e_map_is_tweening (map))
153 return;
154
155 g_timer_destroy (map->priv->timer);
156 map->priv->timer = NULL;
157 g_source_remove (map->priv->tween_id);
158 map->priv->tween_id = 0;
159 }
160
161 static void
162 e_map_tween_destroy (EMap *map,
163 EMapTween *tween)
164 {
165 map->priv->tweens = g_slist_remove (map->priv->tweens, tween);
166 g_slice_free (EMapTween, tween);
167
168 if (map->priv->tweens == NULL)
169 e_map_stop_tweening (map);
170 }
171
172 static gboolean
173 e_map_do_tween_cb (gpointer data)
174 {
175 EMap *map = data;
176 GSList *walk;
177
178 map->priv->timer_current_ms =
179 g_timer_elapsed (map->priv->timer, NULL) * 1000;
180 gtk_widget_queue_draw (GTK_WIDGET (map));
181
182 /* Can't use for loop here, because we need to advance
183 * the list before deleting.
184 */
185 walk = map->priv->tweens;
186 while (walk)
187 {
188 EMapTween *tween = walk->data;
189
190 walk = walk->next;
191
192 if (tween->end_time <= map->priv->timer_current_ms)
193 e_map_tween_destroy (map, tween);
194 }
195
196 return TRUE;
197 }
198
199 static void
200 e_map_start_tweening (EMap *map)
201 {
202 if (e_map_is_tweening (map))
203 return;
204
205 map->priv->timer = g_timer_new ();
206 map->priv->timer_current_ms = 0;
207 map->priv->tween_id = g_timeout_add (
208 E_MAP_TWEEN_TIMEOUT_MSECS, e_map_do_tween_cb, map);
209 g_timer_start (map->priv->timer);
210 }
211
212 static void
213 e_map_tween_new (EMap *map,
214 guint msecs,
215 gdouble longitude_offset,
216 gdouble latitude_offset,
217 gdouble zoom_factor)
218 {
219 EMapTween *tween;
220
221 if (!map->priv->smooth_zoom)
222 return;
223
224 e_map_start_tweening (map);
225
226 tween = g_slice_new (EMapTween);
227
228 tween->start_time = map->priv->timer_current_ms;
229 tween->end_time = tween->start_time + msecs;
230 tween->longitude_offset = longitude_offset;
231 tween->latitude_offset = latitude_offset;
232 tween->zoom_factor = zoom_factor;
233
234 map->priv->tweens = g_slist_prepend (map->priv->tweens, tween);
235
236 gtk_widget_queue_draw (GTK_WIDGET (map));
237 }
238
239 G_DEFINE_TYPE_WITH_CODE (
240 EMap,
241 e_map,
242 GTK_TYPE_WIDGET,
243 G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
244
245 static void
246 e_map_get_current_location (EMap *map,
247 gdouble *longitude,
248 gdouble *latitude)
249 {
250 GtkAllocation allocation;
251
252 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
253
254 e_map_window_to_world (
255 map, allocation.width / 2.0,
256 allocation.height / 2.0,
257 longitude, latitude);
258 }
259
260 static void
261 e_map_world_to_render_surface (EMap *map,
262 gdouble world_longitude,
263 gdouble world_latitude,
264 gdouble *win_x,
265 gdouble *win_y)
266 {
267 gint width, height;
268
269 width = E_MAP_GET_WIDTH (map);
270 height = E_MAP_GET_HEIGHT (map);
271
272 *win_x = (width / 2.0 + (width / 2.0) * world_longitude / 180.0);
273 *win_y = (height / 2.0 - (height / 2.0) * world_latitude / 90.0);
274 }
275
276 static void
277 e_map_tween_new_from (EMap *map,
278 guint msecs,
279 gdouble longitude,
280 gdouble latitude,
281 gdouble zoom)
282 {
283 gdouble current_longitude, current_latitude;
284
285 e_map_get_current_location (
286 map, ¤t_longitude, ¤t_latitude);
287
288 e_map_tween_new (
289 map, msecs,
290 longitude - current_longitude,
291 latitude - current_latitude,
292 zoom / e_map_get_magnification (map));
293 }
294
295 static gdouble
296 e_map_get_tween_effect (EMap *map,
297 EMapTween *tween)
298 {
299 gdouble elapsed;
300
301 elapsed = (gdouble)
302 (map->priv->timer_current_ms - tween->start_time) /
303 tween->end_time;
304
305 return MAX (0.0, 1.0 - elapsed);
306 }
307
308 static void
309 e_map_apply_tween (EMapTween *tween,
310 gdouble effect,
311 gdouble *longitude,
312 gdouble *latitude,
313 gdouble *zoom)
314 {
315 *zoom *= pow (tween->zoom_factor, effect);
316 *longitude += tween->longitude_offset * effect;
317 *latitude += tween->latitude_offset * effect;
318 }
319
320 static void
321 e_map_tweens_compute_matrix (EMap *map,
322 cairo_matrix_t *matrix)
323 {
324 GSList *walk;
325 gdouble zoom, x, y, latitude, longitude, effect;
326 GtkAllocation allocation;
327
328 if (!e_map_is_tweening (map)) {
329 cairo_matrix_init_translate (
330 matrix, -map->priv->xofs, -map->priv->yofs);
331 return;
332 }
333
334 e_map_get_current_location (map, &longitude, &latitude);
335 zoom = 1.0;
336
337 for (walk = map->priv->tweens; walk; walk = walk->next) {
338 EMapTween *tween = walk->data;
339
340 effect = e_map_get_tween_effect (map, tween);
341 e_map_apply_tween (tween, effect, &longitude, &latitude, &zoom);
342 }
343
344 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
345 cairo_matrix_init_translate (
346 matrix,
347 allocation.width / 2.0,
348 allocation.height / 2.0);
349
350 e_map_world_to_render_surface (map, longitude, latitude, &x, &y);
351 cairo_matrix_scale (matrix, zoom, zoom);
352 cairo_matrix_translate (matrix, -x, -y);
353 }
354
355 /* GtkScrollable implementation */
356
357 static void
358 e_map_adjustment_changed (GtkAdjustment *adjustment,
359 EMap *map)
360 {
361 EMapPrivate *priv = map->priv;
362
363 if (gtk_widget_get_realized (GTK_WIDGET (map))) {
364 gint hadj_value;
365 gint vadj_value;
366
367 hadj_value = gtk_adjustment_get_value (priv->hadjustment);
368 vadj_value = gtk_adjustment_get_value (priv->vadjustment);
369
370 scroll_to (map, hadj_value, vadj_value);
371 }
372 }
373
374 static void
375 e_map_set_hadjustment_values (EMap *map)
376 {
377 GtkAllocation allocation;
378 EMapPrivate *priv = map->priv;
379 GtkAdjustment *adj = priv->hadjustment;
380 gdouble old_value;
381 gdouble new_value;
382 gdouble new_upper;
383
384 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
385
386 old_value = gtk_adjustment_get_value (adj);
387 new_upper = MAX (allocation.width, gdk_pixbuf_get_width (priv->map_pixbuf));
388
389 g_object_set (
390 adj,
391 "lower", 0.0,
392 "upper", new_upper,
393 "page-size", (gdouble) allocation.height,
394 "step-increment", allocation.height * 0.1,
395 "page-increment", allocation.height * 0.9,
396 NULL);
397
398 new_value = CLAMP (old_value, 0, new_upper - allocation.width);
399 if (new_value != old_value)
400 gtk_adjustment_set_value (adj, new_value);
401 }
402
403 static void
404 e_map_set_vadjustment_values (EMap *map)
405 {
406 GtkAllocation allocation;
407 EMapPrivate *priv = map->priv;
408 GtkAdjustment *adj = priv->vadjustment;
409 gdouble old_value;
410 gdouble new_value;
411 gdouble new_upper;
412
413 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
414
415 old_value = gtk_adjustment_get_value (adj);
416 new_upper = MAX (allocation.height, gdk_pixbuf_get_height (priv->map_pixbuf));
417
418 g_object_set (
419 adj,
420 "lower", 0.0,
421 "upper", new_upper,
422 "page-size", (gdouble) allocation.height,
423 "step-increment", allocation.height * 0.1,
424 "page-increment", allocation.height * 0.9,
425 NULL);
426
427 new_value = CLAMP (old_value, 0, new_upper - allocation.height);
428 if (new_value != old_value)
429 gtk_adjustment_set_value (adj, new_value);
430 }
431
432 static void
433 e_map_set_hadjustment (EMap *map,
434 GtkAdjustment *adjustment)
435 {
436 EMapPrivate *priv = map->priv;
437
438 if (adjustment && priv->hadjustment == adjustment)
439 return;
440
441 if (priv->hadjustment != NULL) {
442 g_signal_handlers_disconnect_matched (
443 priv->hadjustment, G_SIGNAL_MATCH_DATA,
444 0, 0, NULL, NULL, map);
445 g_object_unref (priv->hadjustment);
446 }
447
448 if (!adjustment)
449 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
450
451 g_signal_connect (
452 adjustment, "value-changed",
453 G_CALLBACK (e_map_adjustment_changed), map);
454 priv->hadjustment = g_object_ref_sink (adjustment);
455 e_map_set_hadjustment_values (map);
456
457 g_object_notify (G_OBJECT (map), "hadjustment");
458 }
459
460 static void
461 e_map_set_vadjustment (EMap *map,
462 GtkAdjustment *adjustment)
463 {
464 EMapPrivate *priv = map->priv;
465
466 if (adjustment && priv->vadjustment == adjustment)
467 return;
468
469 if (priv->vadjustment != NULL) {
470 g_signal_handlers_disconnect_matched (
471 priv->vadjustment, G_SIGNAL_MATCH_DATA,
472 0, 0, NULL, NULL, map);
473 g_object_unref (priv->vadjustment);
474 }
475
476 if (!adjustment)
477 adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
478
479 g_signal_connect (
480 adjustment, "value-changed",
481 G_CALLBACK (e_map_adjustment_changed), map);
482 priv->vadjustment = g_object_ref_sink (adjustment);
483 e_map_set_vadjustment_values (map);
484
485 g_object_notify (G_OBJECT (map), "vadjustment");
486 }
487
488 /* ----------------- *
489 * Widget management *
490 * ----------------- */
491
492 static void
493 e_map_set_property (GObject *object,
494 guint property_id,
495 const GValue *value,
496 GParamSpec *pspec)
497 {
498 EMap *map;
499
500 map = E_MAP (object);
501
502 switch (property_id) {
503 case PROP_HADJUSTMENT:
504 e_map_set_hadjustment (map, g_value_get_object (value));
505 break;
506 case PROP_VADJUSTMENT:
507 e_map_set_vadjustment (map, g_value_get_object (value));
508 break;
509 case PROP_HSCROLL_POLICY:
510 map->priv->hscroll_policy = g_value_get_enum (value);
511 gtk_widget_queue_resize (GTK_WIDGET (map));
512 break;
513 case PROP_VSCROLL_POLICY:
514 map->priv->vscroll_policy = g_value_get_enum (value);
515 gtk_widget_queue_resize (GTK_WIDGET (map));
516 break;
517
518 default:
519 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
520 break;
521 }
522 }
523
524 static void
525 e_map_get_property (GObject *object,
526 guint property_id,
527 GValue *value,
528 GParamSpec *pspec)
529 {
530 EMap *map;
531
532 map = E_MAP (object);
533
534 switch (property_id) {
535 case PROP_HADJUSTMENT:
536 g_value_set_object (value, map->priv->hadjustment);
537 break;
538 case PROP_VADJUSTMENT:
539 g_value_set_object (value, map->priv->vadjustment);
540 break;
541 case PROP_HSCROLL_POLICY:
542 g_value_set_enum (value, map->priv->hscroll_policy);
543 break;
544 case PROP_VSCROLL_POLICY:
545 g_value_set_enum (value, map->priv->vscroll_policy);
546 break;
547
548 default:
549 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
550 break;
551 }
552 }
553
554 static void
555 e_map_finalize (GObject *object)
556 {
557 EMap *map;
558
559 map = E_MAP (object);
560
561 while (map->priv->tweens)
562 e_map_tween_destroy (map, map->priv->tweens->data);
563 e_map_stop_tweening (map);
564
565 if (map->priv->map_pixbuf) {
566 g_object_unref (map->priv->map_pixbuf);
567 map->priv->map_pixbuf = NULL;
568 }
569
570 /* gone in unrealize */
571 g_assert (map->priv->map_render_surface == NULL);
572
573 G_OBJECT_CLASS (e_map_parent_class)->finalize (object);
574 }
575
576 static void
577 e_map_realize (GtkWidget *widget)
578 {
579 GtkAllocation allocation;
580 GdkWindowAttr attr;
581 GdkWindow *window;
582 GtkStyle *style;
583 gint attr_mask;
584
585 g_return_if_fail (widget != NULL);
586 g_return_if_fail (E_IS_MAP (widget));
587
588 gtk_widget_set_realized (widget, TRUE);
589
590 gtk_widget_get_allocation (widget, &allocation);
591
592 attr.window_type = GDK_WINDOW_CHILD;
593 attr.x = allocation.x;
594 attr.y = allocation.y;
595 attr.width = allocation.width;
596 attr.height = allocation.height;
597 attr.wclass = GDK_INPUT_OUTPUT;
598 attr.visual = gtk_widget_get_visual (widget);
599 attr.event_mask = gtk_widget_get_events (widget) |
600 GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK |
601 GDK_POINTER_MOTION_MASK;
602
603 attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
604
605 window = gdk_window_new (
606 gtk_widget_get_parent_window (widget), &attr, attr_mask);
607 gtk_widget_set_window (widget, window);
608 gdk_window_set_user_data (window, widget);
609
610 style = gtk_widget_get_style (widget);
611 style = gtk_style_attach (style, window);
612 gtk_widget_set_style (widget, style);
613
614 update_render_surface (E_MAP (widget), TRUE);
615 }
616
617 static void
618 e_map_unrealize (GtkWidget *widget)
619 {
620 EMap *map = E_MAP (widget);
621
622 cairo_surface_destroy (map->priv->map_render_surface);
623 map->priv->map_render_surface = NULL;
624
625 if (GTK_WIDGET_CLASS (e_map_parent_class)->unrealize)
626 (*GTK_WIDGET_CLASS (e_map_parent_class)->unrealize) (widget);
627 }
628
629 static void
630 e_map_get_preferred_width (GtkWidget *widget,
631 gint *minimum,
632 gint *natural)
633 {
634 EMap *map;
635
636 g_return_if_fail (widget != NULL);
637 g_return_if_fail (E_IS_MAP (widget));
638
639 map = E_MAP (widget);
640
641 /* TODO: Put real sizes here. */
642
643 *minimum = *natural = gdk_pixbuf_get_width (map->priv->map_pixbuf);
644 }
645
646 static void
647 e_map_get_preferred_height (GtkWidget *widget,
648 gint *minimum,
649 gint *natural)
650 {
651 EMap *view;
652 EMapPrivate *priv;
653
654 g_return_if_fail (widget != NULL);
655 g_return_if_fail (E_IS_MAP (widget));
656
657 view = E_MAP (widget);
658 priv = view->priv;
659
660 /* TODO: Put real sizes here. */
661
662 *minimum = *natural = gdk_pixbuf_get_height (priv->map_pixbuf);
663 }
664
665 static void
666 e_map_size_allocate (GtkWidget *widget,
667 GtkAllocation *allocation)
668 {
669 EMap *map;
670
671 g_return_if_fail (widget != NULL);
672 g_return_if_fail (E_IS_MAP (widget));
673 g_return_if_fail (allocation != NULL);
674
675 map = E_MAP (widget);
676
677 /* Resize the window */
678
679 gtk_widget_set_allocation (widget, allocation);
680
681 if (gtk_widget_get_realized (widget)) {
682 GdkWindow *window;
683
684 window = gtk_widget_get_window (widget);
685
686 gdk_window_move_resize (
687 window, allocation->x, allocation->y,
688 allocation->width, allocation->height);
689
690 gtk_widget_queue_draw (widget);
691 }
692
693 update_render_surface (map, TRUE);
694 }
695
696 static gboolean
697 e_map_draw (GtkWidget *widget,
698 cairo_t *cr)
699 {
700 EMap *map;
701 cairo_matrix_t matrix;
702
703 if (!gtk_widget_is_drawable (widget))
704 return FALSE;
705
706 map = E_MAP (widget);
707
708 cairo_save (cr);
709
710 e_map_tweens_compute_matrix (map, &matrix);
711 cairo_transform (cr, &matrix);
712
713 cairo_set_source_surface (cr, map->priv->map_render_surface, 0, 0);
714 cairo_paint (cr);
715
716 cairo_restore (cr);
717
718 return FALSE;
719 }
720
721 static gint
722 e_map_button_press (GtkWidget *widget,
723 GdkEventButton *event)
724 {
725 if (!gtk_widget_has_focus (widget)) gtk_widget_grab_focus (widget);
726 return TRUE;
727 }
728
729 static gint
730 e_map_button_release (GtkWidget *widget,
731 GdkEventButton *event)
732 {
733 if (event->button != 1) return FALSE;
734
735 gdk_pointer_ungrab (event->time);
736 return TRUE;
737 }
738
739 static gint
740 e_map_motion (GtkWidget *widget,
741 GdkEventMotion *event)
742 {
743 return FALSE;
744 }
745
746 static gint
747 e_map_key_press (GtkWidget *widget,
748 GdkEventKey *event)
749 {
750 EMap *map;
751 gboolean do_scroll;
752 gint xofs, yofs;
753
754 map = E_MAP (widget);
755
756 switch (event->keyval)
757 {
758 case GDK_KEY_Up:
759 do_scroll = TRUE;
760 xofs = 0;
761 yofs = -SCROLL_STEP_SIZE;
762 break;
763
764 case GDK_KEY_Down:
765 do_scroll = TRUE;
766 xofs = 0;
767 yofs = SCROLL_STEP_SIZE;
768 break;
769
770 case GDK_KEY_Left:
771 do_scroll = TRUE;
772 xofs = -SCROLL_STEP_SIZE;
773 yofs = 0;
774 break;
775
776 case GDK_KEY_Right:
777 do_scroll = TRUE;
778 xofs = SCROLL_STEP_SIZE;
779 yofs = 0;
780 break;
781
782 default:
783 return FALSE;
784 }
785
786 if (do_scroll) {
787 gint page_size;
788 gint upper;
789 gint x, y;
790
791 page_size = gtk_adjustment_get_page_size (map->priv->hadjustment);
792 upper = gtk_adjustment_get_upper (map->priv->hadjustment);
793 x = CLAMP (map->priv->xofs + xofs, 0, upper - page_size);
794
795 page_size = gtk_adjustment_get_page_size (map->priv->vadjustment);
796 upper = gtk_adjustment_get_upper (map->priv->vadjustment);
797 y = CLAMP (map->priv->yofs + yofs, 0, upper - page_size);
798
799 scroll_to (map, x, y);
800
801 gtk_adjustment_set_value (map->priv->hadjustment, x);
802 gtk_adjustment_set_value (map->priv->vadjustment, y);
803 }
804
805 return TRUE;
806 }
807
808 static void
809 e_map_class_init (EMapClass *class)
810 {
811 GObjectClass *object_class;
812 GtkWidgetClass *widget_class;
813
814 g_type_class_add_private (class, sizeof (EMapPrivate));
815
816 object_class = G_OBJECT_CLASS (class);
817 object_class->set_property = e_map_set_property;
818 object_class->get_property = e_map_get_property;
819 object_class->finalize = e_map_finalize;
820
821 /* Scrollable interface properties */
822 g_object_class_override_property (
823 object_class, PROP_HADJUSTMENT, "hadjustment");
824 g_object_class_override_property (
825 object_class, PROP_VADJUSTMENT, "vadjustment");
826 g_object_class_override_property (
827 object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
828 g_object_class_override_property (
829 object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
830
831 widget_class = GTK_WIDGET_CLASS (class);
832 widget_class->realize = e_map_realize;
833 widget_class->unrealize = e_map_unrealize;
834 widget_class->get_preferred_height = e_map_get_preferred_height;
835 widget_class->get_preferred_width = e_map_get_preferred_width;
836 widget_class->size_allocate = e_map_size_allocate;
837 widget_class->draw = e_map_draw;
838 widget_class->button_press_event = e_map_button_press;
839 widget_class->button_release_event = e_map_button_release;
840 widget_class->motion_notify_event = e_map_motion;
841 widget_class->key_press_event = e_map_key_press;
842 }
843
844 static void
845 e_map_init (EMap *map)
846 {
847 GtkWidget *widget;
848 gchar *map_file_name;
849
850 map_file_name = g_build_filename (
851 EVOLUTION_IMAGESDIR, "world_map-960.png", NULL);
852
853 widget = GTK_WIDGET (map);
854
855 map->priv = E_MAP_GET_PRIVATE (map);
856
857 load_map_background (map, map_file_name);
858 g_free (map_file_name);
859 map->priv->frozen = FALSE;
860 map->priv->smooth_zoom = TRUE;
861 map->priv->zoom_state = E_MAP_ZOOMED_OUT;
862 map->priv->points = g_ptr_array_new ();
863
864 gtk_widget_set_can_focus (widget, TRUE);
865 gtk_widget_set_has_window (widget, TRUE);
866 }
867
868 /* ---------------- *
869 * Widget interface *
870 * ---------------- */
871
872 /**
873 * e_map_new:
874 * @void:
875 *
876 * Creates a new empty map widget.
877 *
878 * Return value: A newly-created map widget.
879 **/
880
881 EMap *
882 e_map_new (void)
883 {
884 GtkWidget *widget;
885 AtkObject *a11y;
886
887 widget = g_object_new (E_TYPE_MAP, NULL);
888 a11y = gtk_widget_get_accessible (widget);
889 atk_object_set_name (a11y, _("World Map"));
890 atk_object_set_role (a11y, ATK_ROLE_IMAGE);
891 atk_object_set_description (
892 a11y, _("Mouse-based interactive map widget for selecting "
893 "timezone. Keyboard users should instead select the timezone "
894 "from the drop-down combination box below."));
895 return (E_MAP (widget));
896 }
897
898 /* --- Coordinate translation --- */
899
900 /* These functions translate coordinates between longitude/latitude and
901 * the image x/y offsets, using the equidistant cylindrical projection.
902 *
903 * Longitude E <-180, 180]
904 * Latitude E <-90, 90] */
905
906 void
907 e_map_window_to_world (EMap *map,
908 gdouble win_x,
909 gdouble win_y,
910 gdouble *world_longitude,
911 gdouble *world_latitude)
912 {
913 gint width, height;
914
915 g_return_if_fail (map);
916
917 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
918
919 width = E_MAP_GET_WIDTH (map);
920 height = E_MAP_GET_HEIGHT (map);
921
922 *world_longitude = (win_x + map->priv->xofs - (gdouble) width / 2.0) /
923 ((gdouble) width / 2.0) * 180.0;
924 *world_latitude = ((gdouble) height / 2.0 - win_y - map->priv->yofs) /
925 ((gdouble) height / 2.0) * 90.0;
926 }
927
928 void
929 e_map_world_to_window (EMap *map,
930 gdouble world_longitude,
931 gdouble world_latitude,
932 gdouble *win_x,
933 gdouble *win_y)
934 {
935 g_return_if_fail (E_IS_MAP (map));
936 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
937 g_return_if_fail (world_longitude >= -180.0 && world_longitude <= 180.0);
938 g_return_if_fail (world_latitude >= -90.0 && world_latitude <= 90.0);
939
940 e_map_world_to_render_surface (
941 map, world_longitude, world_latitude, win_x, win_y);
942
943 *win_x -= map->priv->xofs;
944 *win_y -= map->priv->yofs;
945 }
946
947 /* --- Zoom --- */
948
949 gdouble
950 e_map_get_magnification (EMap *map)
951 {
952 if (map->priv->zoom_state == E_MAP_ZOOMED_IN) return 2.0;
953 else return 1.0;
954 }
955
956 static void
957 e_map_set_zoom (EMap *map,
958 EMapZoomState zoom)
959 {
960 if (map->priv->zoom_state == zoom)
961 return;
962
963 map->priv->zoom_state = zoom;
964 update_render_surface (map, TRUE);
965 gtk_widget_queue_draw (GTK_WIDGET (map));
966 }
967
968 void
969 e_map_zoom_to_location (EMap *map,
970 gdouble longitude,
971 gdouble latitude)
972 {
973 gdouble prevlong, prevlat;
974 gdouble prevzoom;
975
976 g_return_if_fail (map);
977 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
978
979 e_map_get_current_location (map, &prevlong, &prevlat);
980 prevzoom = e_map_get_magnification (map);
981
982 e_map_set_zoom (map, E_MAP_ZOOMED_IN);
983 center_at (map, longitude, latitude);
984
985 e_map_tween_new_from (
986 map, E_MAP_TWEEN_DURATION_MSECS,
987 prevlong, prevlat, prevzoom);
988 }
989
990 void
991 e_map_zoom_out (EMap *map)
992 {
993 gdouble longitude, latitude;
994 gdouble prevzoom;
995
996 g_return_if_fail (map);
997 g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
998
999 e_map_get_current_location (map, &longitude, &latitude);
1000 prevzoom = e_map_get_magnification (map);
1001 e_map_set_zoom (map, E_MAP_ZOOMED_OUT);
1002 center_at (map, longitude, latitude);
1003
1004 e_map_tween_new_from (
1005 map, E_MAP_TWEEN_DURATION_MSECS,
1006 longitude, latitude, prevzoom);
1007 }
1008
1009 void
1010 e_map_set_smooth_zoom (EMap *map,
1011 gboolean state)
1012 {
1013 ((EMapPrivate *) map->priv)->smooth_zoom = state;
1014 }
1015
1016 gboolean
1017 e_map_get_smooth_zoom (EMap *map)
1018 {
1019 return (((EMapPrivate *) map->priv)->smooth_zoom);
1020 }
1021
1022 void
1023 e_map_freeze (EMap *map)
1024 {
1025 ((EMapPrivate *) map->priv)->frozen = TRUE;
1026 }
1027
1028 void
1029 e_map_thaw (EMap *map)
1030 {
1031 ((EMapPrivate *) map->priv)->frozen = FALSE;
1032 update_and_paint (map);
1033 }
1034
1035 /* --- Point manipulation --- */
1036
1037 EMapPoint *
1038 e_map_add_point (EMap *map,
1039 gchar *name,
1040 gdouble longitude,
1041 gdouble latitude,
1042 guint32 color_rgba)
1043 {
1044 EMapPoint *point;
1045
1046 point = g_new0 (EMapPoint, 1);
1047
1048 point->name = name; /* Can be NULL */
1049 point->longitude = longitude;
1050 point->latitude = latitude;
1051 point->rgba = color_rgba;
1052
1053 g_ptr_array_add (map->priv->points, (gpointer) point);
1054
1055 if (!map->priv->frozen)
1056 {
1057 update_render_point (map, point);
1058 repaint_point (map, point);
1059 }
1060
1061 return point;
1062 }
1063
1064 void
1065 e_map_remove_point (EMap *map,
1066 EMapPoint *point)
1067 {
1068 g_ptr_array_remove (map->priv->points, point);
1069
1070 if (!((EMapPrivate *) map->priv)->frozen)
1071 {
1072 /* FIXME: Re-scaling the whole pixbuf is more than a little
1073 * overkill when just one point is removed */
1074
1075 update_render_surface (map, TRUE);
1076 repaint_point (map, point);
1077 }
1078
1079 g_free (point);
1080 }
1081
1082 void
1083 e_map_point_get_location (EMapPoint *point,
1084 gdouble *longitude,
1085 gdouble *latitude)
1086 {
1087 *longitude = point->longitude;
1088 *latitude = point->latitude;
1089 }
1090
1091 gchar *
1092 e_map_point_get_name (EMapPoint *point)
1093 {
1094 return point->name;
1095 }
1096
1097 guint32
1098 e_map_point_get_color_rgba (EMapPoint *point)
1099 {
1100 return point->rgba;
1101 }
1102
1103 void
1104 e_map_point_set_color_rgba (EMap *map,
1105 EMapPoint *point,
1106 guint32 color_rgba)
1107 {
1108 point->rgba = color_rgba;
1109
1110 if (!((EMapPrivate *) map->priv)->frozen)
1111 {
1112 /* TODO: Redraw area around point only */
1113
1114 update_render_point (map, point);
1115 repaint_point (map, point);
1116 }
1117 }
1118
1119 void
1120 e_map_point_set_data (EMapPoint *point,
1121 gpointer data)
1122 {
1123 point->user_data = data;
1124 }
1125
1126 gpointer
1127 e_map_point_get_data (EMapPoint *point)
1128 {
1129 return point->user_data;
1130 }
1131
1132 gboolean
1133 e_map_point_is_in_view (EMap *map,
1134 EMapPoint *point)
1135 {
1136 GtkAllocation allocation;
1137 gdouble x, y;
1138
1139 if (!map->priv->map_render_surface) return FALSE;
1140
1141 e_map_world_to_window (map, point->longitude, point->latitude, &x, &y);
1142 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
1143
1144 if (x >= 0 && x < allocation.width &&
1145 y >= 0 && y < allocation.height)
1146 return TRUE;
1147
1148 return FALSE;
1149 }
1150
1151 EMapPoint *
1152 e_map_get_closest_point (EMap *map,
1153 gdouble longitude,
1154 gdouble latitude,
1155 gboolean in_view)
1156 {
1157 EMapPoint *point_chosen = NULL, *point;
1158 gdouble min_dist = 0.0, dist;
1159 gdouble dx, dy;
1160 gint i;
1161
1162 for (i = 0; i < map->priv->points->len; i++)
1163 {
1164 point = g_ptr_array_index (map->priv->points, i);
1165 if (in_view && !e_map_point_is_in_view (map, point)) continue;
1166
1167 dx = point->longitude - longitude;
1168 dy = point->latitude - latitude;
1169 dist = dx * dx + dy * dy;
1170
1171 if (!point_chosen || dist < min_dist)
1172 {
1173 min_dist = dist;
1174 point_chosen = point;
1175 }
1176 }
1177
1178 return point_chosen;
1179 }
1180
1181 /* ------------------ *
1182 * Internal functions *
1183 * ------------------ */
1184
1185 static void
1186 update_and_paint (EMap *map)
1187 {
1188 update_render_surface (map, TRUE);
1189 gtk_widget_queue_draw (GTK_WIDGET (map));
1190 }
1191
1192 static gint
1193 load_map_background (EMap *map,
1194 gchar *name)
1195 {
1196 GdkPixbuf *pb0;
1197
1198 pb0 = gdk_pixbuf_new_from_file (name, NULL);
1199 if (!pb0)
1200 return FALSE;
1201
1202 if (map->priv->map_pixbuf) g_object_unref (map->priv->map_pixbuf);
1203 map->priv->map_pixbuf = pb0;
1204 update_render_surface (map, TRUE);
1205
1206 return TRUE;
1207 }
1208
1209 static void
1210 update_render_surface (EMap *map,
1211 gboolean render_overlays)
1212 {
1213 EMapPoint *point;
1214 GtkAllocation allocation;
1215 gint width, height, orig_width, orig_height;
1216 gdouble zoom;
1217 gint i;
1218
1219 if (!gtk_widget_get_realized (GTK_WIDGET (map)))
1220 return;
1221
1222 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
1223
1224 /* Set up value shortcuts */
1225
1226 width = allocation.width;
1227 height = allocation.height;
1228 orig_width = gdk_pixbuf_get_width (map->priv->map_pixbuf);
1229 orig_height = gdk_pixbuf_get_height (map->priv->map_pixbuf);
1230
1231 /* Compute scaled width and height based on the extreme dimension */
1232
1233 if ((gdouble) width / orig_width > (gdouble) height / orig_height)
1234 zoom = (gdouble) width / (gdouble) orig_width;
1235 else
1236 zoom = (gdouble) height / (gdouble) orig_height;
1237
1238 if (map->priv->zoom_state == E_MAP_ZOOMED_IN)
1239 zoom *= 2.0;
1240 height = (orig_height * zoom) + 0.5;
1241 width = (orig_width * zoom) + 0.5;
1242
1243 /* Reallocate the pixbuf */
1244
1245 if (map->priv->map_render_surface)
1246 cairo_surface_destroy (map->priv->map_render_surface);
1247 map->priv->map_render_surface = gdk_window_create_similar_surface (
1248 gtk_widget_get_window (GTK_WIDGET (map)),
1249 CAIRO_CONTENT_COLOR, width, height);
1250
1251 /* Scale the original map into the rendering pixbuf */
1252
1253 if (width > 1 && height > 1) {
1254 cairo_t *cr = cairo_create (map->priv->map_render_surface);
1255 cairo_scale (
1256 cr,
1257 (gdouble) width / orig_width,
1258 (gdouble) height / orig_height);
1259 gdk_cairo_set_source_pixbuf (cr, map->priv->map_pixbuf, 0, 0);
1260 cairo_paint (cr);
1261 cairo_destroy (cr);
1262 }
1263
1264 /* Compute image offsets with respect to window */
1265
1266 set_scroll_area (map, width, height);
1267
1268 if (render_overlays) {
1269 /* Add points */
1270
1271 for (i = 0; i < map->priv->points->len; i++) {
1272 point = g_ptr_array_index (map->priv->points, i);
1273 update_render_point (map, point);
1274 }
1275 }
1276 }
1277
1278 /* Redraw point in client surface */
1279
1280 static void
1281 update_render_point (EMap *map,
1282 EMapPoint *point)
1283 {
1284 cairo_t *cr;
1285 gdouble px, py;
1286 static guchar mask1[] = { 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
1287 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
1288 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
1289 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
1290 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 };
1291 static guchar mask2[] = { 0x00, 0xff, 0x00, 0x00,
1292 0xff, 0xff, 0xff, 0x00,
1293 0x00, 0xff, 0x00, 0x00 };
1294 cairo_surface_t *mask;
1295
1296 if (map->priv->map_render_surface == NULL)
1297 return;
1298
1299 cr = cairo_create (map->priv->map_render_surface);
1300 cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
1301
1302 e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
1303 px = floor (px + map->priv->xofs);
1304 py = floor (py + map->priv->yofs);
1305
1306 cairo_set_source_rgb (cr, 0, 0, 0);
1307 mask = cairo_image_surface_create_for_data (mask1, CAIRO_FORMAT_A8, 5, 5, 8);
1308 cairo_mask_surface (cr, mask, px - 2, py - 2);
1309 cairo_surface_destroy (mask);
1310
1311 cairo_set_source_rgba (
1312 cr,
1313 ((point->rgba >> 24) & 0xff) / 255.0,
1314 ((point->rgba >> 16) & 0xff) / 255.0,
1315 ((point->rgba >> 8) & 0xff) / 255.0,
1316 ( point->rgba & 0xff) / 255.0);
1317 mask = cairo_image_surface_create_for_data (mask2, CAIRO_FORMAT_A8, 3, 3, 4);
1318 cairo_mask_surface (cr, mask, px - 1, py - 1);
1319 cairo_surface_destroy (mask);
1320
1321 cairo_destroy (cr);
1322 }
1323
1324 /* Repaint point on X server */
1325
1326 static void
1327 repaint_point (EMap *map,
1328 EMapPoint *point)
1329 {
1330 gdouble px, py;
1331
1332 if (!gtk_widget_is_drawable (GTK_WIDGET (map)))
1333 return;
1334
1335 e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
1336
1337 gtk_widget_queue_draw_area (
1338 GTK_WIDGET (map),
1339 (gint) px - 2, (gint) py - 2,
1340 5, 5);
1341 }
1342
1343 static void
1344 center_at (EMap *map,
1345 gdouble longitude,
1346 gdouble latitude)
1347 {
1348 GtkAllocation allocation;
1349 gint pb_width, pb_height;
1350 gdouble x, y;
1351
1352 e_map_world_to_render_surface (map, longitude, latitude, &x, &y);
1353
1354 pb_width = E_MAP_GET_WIDTH (map);
1355 pb_height = E_MAP_GET_HEIGHT (map);
1356
1357 gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
1358
1359 x = CLAMP (x - (allocation.width / 2), 0, pb_width - allocation.width);
1360 y = CLAMP (y - (allocation.height / 2), 0, pb_height - allocation.height);
1361
1362 gtk_adjustment_set_value (map->priv->hadjustment, x);
1363 gtk_adjustment_set_value (map->priv->vadjustment, y);
1364
1365 gtk_widget_queue_draw (GTK_WIDGET (map));
1366 }
1367
1368 /* Scrolls the view to the specified offsets. Does not perform range checking! */
1369
1370 static void
1371 scroll_to (EMap *map,
1372 gint x,
1373 gint y)
1374 {
1375 gint xofs, yofs;
1376
1377 /* Compute offsets and check bounds */
1378
1379 xofs = x - map->priv->xofs;
1380 yofs = y - map->priv->yofs;
1381
1382 if (xofs == 0 && yofs == 0)
1383 return;
1384
1385 map->priv->xofs = x;
1386 map->priv->yofs = y;
1387
1388 gtk_widget_queue_draw (GTK_WIDGET (map));
1389 }
1390
1391 static void
1392 set_scroll_area (EMap *view,
1393 gint width,
1394 gint height)
1395 {
1396 EMapPrivate *priv;
1397 GtkAllocation allocation;
1398
1399 priv = view->priv;
1400
1401 if (!gtk_widget_get_realized (GTK_WIDGET (view)))
1402 return;
1403
1404 if (!priv->hadjustment || !priv->vadjustment)
1405 return;
1406
1407 g_object_freeze_notify (G_OBJECT (priv->hadjustment));
1408 g_object_freeze_notify (G_OBJECT (priv->vadjustment));
1409
1410 gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
1411
1412 priv->xofs = CLAMP (priv->xofs, 0, width - allocation.width);
1413 priv->yofs = CLAMP (priv->yofs, 0, height - allocation.height);
1414
1415 gtk_adjustment_configure (
1416 priv->hadjustment,
1417 priv->xofs,
1418 0, width,
1419 SCROLL_STEP_SIZE,
1420 allocation.width / 2,
1421 allocation.width);
1422 gtk_adjustment_configure (
1423 priv->vadjustment,
1424 priv->yofs,
1425 0, height,
1426 SCROLL_STEP_SIZE,
1427 allocation.height / 2,
1428 allocation.height);
1429
1430 g_object_thaw_notify (G_OBJECT (priv->hadjustment));
1431 g_object_thaw_notify (G_OBJECT (priv->vadjustment));
1432 }