Branch data Line data Source code
1 : : /*
2 : : * Copyright (C) 2008-2009 Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
3 : : * Copyright (C) 2010-2013 Jiri Techet <techet@gmail.com>
4 : : * Copyright (C) 2012,2020 Collabora Ltd. (https://www.collabora.com/)
5 : : * Copyright (C) 2019 Marcus Lundblad <ml@update.uu.se>
6 : : * Copyright (C) 2020 Corentin Noël <corentin.noel@collabora.com>
7 : : *
8 : : *
9 : : * This library is free software; you can redistribute it and/or
10 : : * modify it under the terms of the GNU Lesser General Public
11 : : * License as published by the Free Software Foundation; either
12 : : * version 2.1 of the License, or (at your option) any later version.
13 : : *
14 : : * This library is distributed in the hope that it will be useful,
15 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 : : * Lesser General Public License for more details.
18 : : *
19 : : * You should have received a copy of the GNU Lesser General Public
20 : : * License along with this library; if not, write to the Free Software
21 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 : : */
23 : :
24 : : /**
25 : : * ShumateMap:
26 : : *
27 : : * The Map widget is a [class@Gtk.Widget] that show and allows interaction with
28 : : * the user.
29 : : *
30 : : * This is the base widget and doesn't have advanced features. You can check the
31 : : * [class@Shumate.SimpleMap] for a ready-to-use widget.
32 : : *
33 : : * By default, a [class@Shumate.Viewport] is created and can be accessed with
34 : : * [method@Shumate.Map.get_viewport].
35 : : *
36 : : * Unless created with [ctor@Shumate.Map.new_simple], the widget doesn't hold any
37 : : * layer and won't show anything. A [class@Shumate.Layer] can be added or removed
38 : : * using the [method@Shumate.Map.add_layer] or [method@Shumate.Map.remove_layer]
39 : : * methods.
40 : : */
41 : :
42 : : #include "shumate-map.h"
43 : :
44 : : #include "shumate.h"
45 : : #include "shumate-enum-types.h"
46 : : #include "shumate-inspector-page-private.h"
47 : : #include "shumate-inspector-settings-private.h"
48 : : #include "shumate-kinetic-scrolling-private.h"
49 : : #include "shumate-marshal.h"
50 : : #include "shumate-map-layer.h"
51 : : #include "shumate-map-source.h"
52 : : #include "shumate-map-source-registry.h"
53 : : #include "shumate-tile.h"
54 : : #include "shumate-license.h"
55 : : #include "shumate-location.h"
56 : : #include "shumate-viewport-private.h"
57 : :
58 : : #include <glib.h>
59 : : #include <glib-object.h>
60 : : #include <gtk/gtk.h>
61 : : #include <math.h>
62 : :
63 : : #define DECELERATION_FRICTION 4.0
64 : : #define ZOOM_ANIMATION_MS 200
65 : : #define SCROLL_PIXELS_PER_LEVEL 50
66 : :
67 : : enum
68 : : {
69 : : /* normal signals */
70 : : ANIMATION_COMPLETED,
71 : : LAST_SIGNAL
72 : : };
73 : :
74 : : enum
75 : : {
76 : : PROP_ZOOM_ON_DOUBLE_CLICK = 1,
77 : : PROP_ANIMATE_ZOOM,
78 : : PROP_STATE,
79 : : PROP_GO_TO_DURATION,
80 : : PROP_VIEWPORT,
81 : : N_PROPERTIES
82 : : };
83 : :
84 : : static guint signals[LAST_SIGNAL] = { 0, };
85 : :
86 : : static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
87 : : static GQuark go_to_quark;
88 : :
89 : : /* Between state values for go_to */
90 : : typedef struct
91 : : {
92 : : int64_t duration_us;
93 : : int64_t start_us;
94 : : double to_latitude;
95 : : double to_longitude;
96 : : double to_zoom;
97 : : double from_latitude;
98 : : double from_longitude;
99 : : double from_zoom;
100 : : guint tick_id;
101 : : gboolean zoom_animation : 1;
102 : : gboolean zoom_deceleration : 1;
103 : : } GoToContext;
104 : :
105 : : typedef struct
106 : : {
107 : : ShumateKineticScrolling *kinetic_scrolling;
108 : : ShumateMap *map;
109 : : double start_lat;
110 : : double start_lon;
111 : : int64_t last_deceleration_time_us;
112 : : graphene_vec2_t direction;
113 : : } KineticScrollData;
114 : :
115 : : struct _ShumateMap
116 : : {
117 : : GtkWidget parent_instance;
118 : :
119 : : ShumateViewport *viewport;
120 : :
121 : : gboolean zoom_on_double_click;
122 : : gboolean animate_zoom;
123 : :
124 : : ShumateState state; /* View's global state */
125 : :
126 : : GoToContext *goto_context;
127 : :
128 : : guint deceleration_tick_id;
129 : :
130 : : guint zoom_timeout;
131 : :
132 : : guint go_to_duration;
133 : :
134 : : double current_x;
135 : : double current_y;
136 : :
137 : : double zoom_level_begin;
138 : : double rotate_begin;
139 : :
140 : : double gesture_begin_lat;
141 : : double gesture_begin_lon;
142 : : double drag_begin_x;
143 : : double drag_begin_y;
144 : : };
145 : :
146 [ + + + - ]: 32 : G_DEFINE_TYPE (ShumateMap, shumate_map, GTK_TYPE_WIDGET);
147 : :
148 : : static double
149 : 0 : positive_mod (double i, double n)
150 : : {
151 : 0 : return fmod (fmod (i, n) + n, n);
152 : : }
153 : :
154 : : static void
155 : 0 : move_location_to_coords_calc (ShumateMap *self,
156 : : double *lat,
157 : : double *lon,
158 : : double x,
159 : : double y,
160 : : ShumateViewport *viewport)
161 : : {
162 : 0 : ShumateMapSource *map_source = shumate_viewport_get_reference_map_source (viewport);
163 : 0 : double zoom_level = shumate_viewport_get_zoom_level (viewport);
164 : 0 : double tile_size, map_width, map_height;
165 : 0 : double map_x, map_y;
166 : 0 : double target_lat, target_lon;
167 : 0 : double target_map_x, target_map_y;
168 : 0 : double current_lat, current_lon;
169 : 0 : double current_map_x, current_map_y;
170 : 0 : double new_map_x, new_map_y;
171 : :
172 [ # # ]: 0 : if (map_source == NULL)
173 : 0 : return;
174 : :
175 : 0 : tile_size = shumate_map_source_get_tile_size_at_zoom (map_source, zoom_level);
176 : 0 : map_width = tile_size * shumate_map_source_get_column_count (map_source, zoom_level);
177 : 0 : map_height = tile_size * shumate_map_source_get_row_count (map_source, zoom_level);
178 : :
179 : 0 : map_x = shumate_map_source_get_x (map_source, zoom_level, *lon);
180 : 0 : map_y = shumate_map_source_get_y (map_source, zoom_level, *lat);
181 : :
182 : 0 : current_lat = shumate_location_get_latitude (SHUMATE_LOCATION (viewport));
183 : 0 : current_lon = shumate_location_get_longitude (SHUMATE_LOCATION (viewport));
184 : 0 : current_map_x = shumate_map_source_get_x (map_source, zoom_level, current_lon);
185 : 0 : current_map_y = shumate_map_source_get_y (map_source, zoom_level, current_lat);
186 : :
187 : 0 : shumate_viewport_widget_coords_to_location (viewport, GTK_WIDGET (self), x, y, &target_lat, &target_lon);
188 : 0 : target_map_x = shumate_map_source_get_x (map_source, zoom_level, target_lon);
189 : 0 : target_map_y = shumate_map_source_get_y (map_source, zoom_level, target_lat);
190 : :
191 : 0 : new_map_x = positive_mod (current_map_x - (target_map_x - map_x), map_width);
192 : 0 : new_map_y = positive_mod (current_map_y - (target_map_y - map_y), map_height);
193 : :
194 : 0 : *lat = shumate_map_source_get_latitude (map_source, zoom_level, new_map_y);
195 : 0 : *lon = shumate_map_source_get_longitude (map_source, zoom_level, new_map_x);
196 : : }
197 : :
198 : : static void
199 : 0 : move_location_to_coords (ShumateMap *self,
200 : : double lat,
201 : : double lon,
202 : : double x,
203 : : double y)
204 : : {
205 : 0 : move_location_to_coords_calc (self, &lat, &lon, x, y, self->viewport);
206 : 0 : shumate_location_set_location (SHUMATE_LOCATION (self->viewport), lat, lon);
207 : 0 : }
208 : :
209 : : static void
210 : 0 : move_viewport_from_pixel_offset (ShumateMap *self,
211 : : double latitude,
212 : : double longitude,
213 : : double offset_x,
214 : : double offset_y)
215 : : {
216 : 0 : ShumateMapSource *map_source;
217 : 0 : double x, y;
218 : 0 : double lat, lon;
219 : :
220 [ # # ]: 0 : g_assert (SHUMATE_IS_MAP (self));
221 : :
222 : 0 : map_source = shumate_viewport_get_reference_map_source (self->viewport);
223 [ # # ]: 0 : if (!map_source)
224 : 0 : return;
225 : :
226 : 0 : shumate_viewport_location_to_widget_coords (self->viewport, GTK_WIDGET (self), latitude, longitude, &x, &y);
227 : :
228 : 0 : x -= offset_x;
229 : 0 : y -= offset_y;
230 : :
231 : 0 : shumate_viewport_widget_coords_to_location (self->viewport, GTK_WIDGET (self), x, y, &lat, &lon);
232 : :
233 : 0 : lat = fmod (lat + 90, 180) - 90;
234 : 0 : lon = fmod (lon + 180, 360) - 180;
235 : :
236 : 0 : shumate_location_set_location (SHUMATE_LOCATION (self->viewport), lat, lon);
237 : : }
238 : :
239 : : static void
240 : 0 : cancel_deceleration (ShumateMap *self)
241 : : {
242 [ # # ]: 0 : if (self->deceleration_tick_id > 0)
243 : : {
244 : 0 : gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->deceleration_tick_id);
245 : 0 : self->deceleration_tick_id = 0;
246 : : }
247 : 0 : }
248 : :
249 : : static gboolean
250 : 0 : view_deceleration_tick_cb (GtkWidget *widget,
251 : : GdkFrameClock *frame_clock,
252 : : gpointer user_data)
253 : : {
254 : 0 : KineticScrollData *data = user_data;
255 : 0 : ShumateMap *map = data->map;
256 : 0 : int64_t current_time_us;
257 : 0 : double elapsed_us;
258 : 0 : double position;
259 : :
260 [ # # ]: 0 : g_assert (SHUMATE_IS_MAP (map));
261 : :
262 : 0 : current_time_us = gdk_frame_clock_get_frame_time (frame_clock);
263 : 0 : elapsed_us = current_time_us - data->last_deceleration_time_us;
264 : :
265 : : /* The frame clock can sometimes fire immediately after adding a tick callback,
266 : : * in which case no time has passed, making it impossible to calculate the
267 : : * kinetic factor. If this is the case, wait for the next tick.
268 : : */
269 [ # # # # ]: 0 : if (G_APPROX_VALUE (elapsed_us, 0.0, FLT_EPSILON))
270 : : return G_SOURCE_CONTINUE;
271 : :
272 : 0 : data->last_deceleration_time_us = current_time_us;
273 : :
274 [ # # # # ]: 0 : if (data->kinetic_scrolling &&
275 : 0 : shumate_kinetic_scrolling_tick (data->kinetic_scrolling, elapsed_us, &position))
276 : 0 : {
277 : 0 : graphene_vec2_t new_positions;
278 : :
279 : 0 : graphene_vec2_init (&new_positions, position, position);
280 : 0 : graphene_vec2_multiply (&new_positions, &data->direction, &new_positions);
281 : :
282 : 0 : move_viewport_from_pixel_offset (map,
283 : : data->start_lat,
284 : : data->start_lon,
285 : 0 : graphene_vec2_get_x (&new_positions),
286 : 0 : graphene_vec2_get_y (&new_positions));
287 : : }
288 : : else
289 : : {
290 [ # # ]: 0 : g_clear_pointer (&data->kinetic_scrolling, shumate_kinetic_scrolling_free);
291 : : }
292 : :
293 [ # # ]: 0 : if (!data->kinetic_scrolling)
294 : : {
295 : 0 : cancel_deceleration (map);
296 : 0 : return G_SOURCE_REMOVE;
297 : : }
298 : :
299 : : return G_SOURCE_CONTINUE;
300 : : }
301 : :
302 : :
303 : : static void
304 : 0 : kinetic_scroll_data_free (KineticScrollData *data)
305 : : {
306 [ # # ]: 0 : if (data == NULL)
307 : : return;
308 : :
309 [ # # ]: 0 : g_clear_pointer (&data->kinetic_scrolling, shumate_kinetic_scrolling_free);
310 : 0 : g_free (data);
311 : : }
312 : :
313 : : static void
314 : 0 : start_deceleration (ShumateMap *self,
315 : : double h_velocity,
316 : : double v_velocity)
317 : : {
318 : 0 : GdkFrameClock *frame_clock;
319 : 0 : KineticScrollData *data;
320 : 0 : graphene_vec2_t velocity;
321 : :
322 [ # # ]: 0 : g_assert (self->deceleration_tick_id == 0);
323 : :
324 : 0 : frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));
325 : :
326 : 0 : graphene_vec2_init (&velocity, h_velocity, v_velocity);
327 : :
328 : 0 : data = g_new0 (KineticScrollData, 1);
329 : 0 : data->map = self;
330 : 0 : data->last_deceleration_time_us = gdk_frame_clock_get_frame_time (frame_clock);
331 : 0 : data->start_lat = shumate_location_get_latitude (SHUMATE_LOCATION (self->viewport));
332 : 0 : data->start_lon = shumate_location_get_longitude (SHUMATE_LOCATION (self->viewport));
333 : 0 : graphene_vec2_normalize (&velocity, &data->direction);
334 : 0 : data->kinetic_scrolling =
335 : 0 : shumate_kinetic_scrolling_new (DECELERATION_FRICTION,
336 : 0 : graphene_vec2_length (&velocity));
337 : :
338 : 0 : self->deceleration_tick_id =
339 : 0 : gtk_widget_add_tick_callback (GTK_WIDGET (self),
340 : : view_deceleration_tick_cb,
341 : : data,
342 : : (GDestroyNotify) kinetic_scroll_data_free);
343 : 0 : }
344 : :
345 : : static inline double
346 : 0 : ease_in_out_quad (double p)
347 : : {
348 : 0 : p = 2.0 * p;
349 [ # # ]: 0 : if (p < 1.0)
350 : 0 : return 0.5 * p * p;
351 : :
352 : 0 : p -= 1.0;
353 : 0 : return -0.5 * (p * (p - 2) - 1);
354 : : }
355 : :
356 : : static inline double
357 : 0 : ease_out_quad (double p)
358 : : {
359 : 0 : return 1 - (1 - p) * (1 - p);
360 : : }
361 : :
362 : : static inline int64_t
363 : 0 : ms_to_us (int64_t ms)
364 : : {
365 : 0 : return ms * 1000;
366 : : }
367 : :
368 : : static gboolean
369 : 0 : go_to_tick_cb (GtkWidget *widget,
370 : : GdkFrameClock *frame_clock,
371 : : gpointer user_data)
372 : : {
373 : 0 : ShumateMap *self = SHUMATE_MAP (widget);
374 : 0 : GoToContext *ctx;
375 : 0 : int64_t now_us;
376 : 0 : double latitude, longitude;
377 : 0 : double progress;
378 : 0 : double current_zoom;
379 : :
380 [ # # ]: 0 : g_assert (SHUMATE_IS_MAP (widget));
381 [ # # ]: 0 : g_assert (user_data == NULL);
382 : :
383 : 0 : ctx = self->goto_context;
384 : :
385 [ # # ]: 0 : g_assert (ctx != NULL);
386 [ # # ]: 0 : g_assert (ctx->duration_us >= 0);
387 : :
388 : 0 : now_us = g_get_monotonic_time ();
389 : :
390 [ # # ]: 0 : if (now_us >= ctx->start_us + ctx->duration_us)
391 : : {
392 : 0 : shumate_location_set_location (SHUMATE_LOCATION (self->viewport),
393 : : ctx->to_latitude,
394 : : ctx->to_longitude);
395 : 0 : shumate_viewport_set_zoom_level (self->viewport, ctx->to_zoom);
396 : 0 : shumate_map_stop_go_to (SHUMATE_MAP (widget));
397 : 0 : return G_SOURCE_REMOVE;
398 : : }
399 : :
400 : 0 : progress = (now_us - ctx->start_us) / (double) ctx->duration_us;
401 [ # # # # ]: 0 : g_assert (progress >= 0.0 && progress <= 1.0);
402 : :
403 : : /* Apply the ease function to the progress itself */
404 [ # # ]: 0 : if (ctx->zoom_deceleration)
405 : 0 : progress = ease_out_quad (progress);
406 [ # # ]: 0 : else if (!ctx->zoom_animation)
407 : 0 : progress = ease_in_out_quad (progress);
408 : :
409 : : /* Interpolate zoom level */
410 : 0 : current_zoom = ctx->from_zoom + (ctx->to_zoom - ctx->from_zoom) * progress;
411 : 0 : shumate_viewport_set_zoom_level (self->viewport, current_zoom);
412 : :
413 : : /* If we're zooming, we need to adjust for that in the progress, otherwise
414 : : * the animation will speed up at higher zoom levels. */
415 [ # # ]: 0 : if (ctx->to_zoom != ctx->from_zoom)
416 : 0 : progress = (pow (2, -ctx->from_zoom) - pow (2, -current_zoom))
417 : 0 : / (pow (2, -ctx->from_zoom) - pow (2, -ctx->to_zoom));
418 : :
419 : : /* Since progress already follows the easing curve, a simple LERP is guaranteed
420 : : * to follow it too.
421 : : */
422 : 0 : latitude = ctx->from_latitude + (ctx->to_latitude - ctx->from_latitude) * progress;
423 : 0 : longitude = ctx->from_longitude + (ctx->to_longitude - ctx->from_longitude) * progress;
424 : :
425 : 0 : shumate_location_set_location (SHUMATE_LOCATION (self->viewport), latitude, longitude);
426 : :
427 : 0 : return G_SOURCE_CONTINUE;
428 : : }
429 : :
430 : : static void
431 : 0 : on_drag_gesture_drag_begin (ShumateMap *self,
432 : : double start_x,
433 : : double start_y,
434 : : GtkGestureDrag *gesture)
435 : : {
436 [ # # ]: 0 : g_assert (SHUMATE_IS_MAP (self));
437 : :
438 : 0 : cancel_deceleration (self);
439 : :
440 : 0 : self->drag_begin_x = start_x;
441 : 0 : self->drag_begin_y = start_y;
442 : :
443 : 0 : shumate_viewport_widget_coords_to_location (self->viewport, GTK_WIDGET (self),
444 : : start_x, start_y,
445 : : &self->gesture_begin_lat,
446 : : &self->gesture_begin_lon);
447 : :
448 : 0 : gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "grabbing");
449 : 0 : }
450 : :
451 : : static void
452 : 0 : on_drag_gesture_drag_update (ShumateMap *self,
453 : : double offset_x,
454 : : double offset_y,
455 : : GtkGestureDrag *gesture)
456 : : {
457 : 0 : move_location_to_coords (self,
458 : : self->gesture_begin_lat,
459 : : self->gesture_begin_lon,
460 : 0 : self->drag_begin_x + offset_x,
461 : 0 : self->drag_begin_y + offset_y);
462 : 0 : }
463 : :
464 : : static void
465 : 0 : on_drag_gesture_drag_end (ShumateMap *self,
466 : : double offset_x,
467 : : double offset_y,
468 : : GtkGestureDrag *gesture)
469 : : {
470 [ # # ]: 0 : g_assert (SHUMATE_IS_MAP (self));
471 : :
472 : 0 : gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "grab");
473 : :
474 : 0 : self->gesture_begin_lon = 0;
475 : 0 : self->gesture_begin_lat = 0;
476 : 0 : }
477 : :
478 : : static void
479 : 0 : view_swipe_cb (GtkGestureSwipe *swipe_gesture,
480 : : double velocity_x,
481 : : double velocity_y,
482 : : ShumateMap *self)
483 : : {
484 : 0 : start_deceleration (self, velocity_x, velocity_y);
485 : 0 : }
486 : :
487 : : static void
488 : 0 : set_zoom_level (ShumateMap *self,
489 : : double zoom_level,
490 : : double animate)
491 : : {
492 : 0 : ShumateMapSource *map_source;
493 : 0 : double lat, lon;
494 : :
495 : 0 : g_object_freeze_notify (G_OBJECT (self->viewport));
496 : :
497 : 0 : map_source = shumate_viewport_get_reference_map_source (self->viewport);
498 [ # # ]: 0 : if (map_source)
499 : 0 : shumate_viewport_widget_coords_to_location (self->viewport,
500 : : GTK_WIDGET (self),
501 : : self->current_x, self->current_y,
502 : : &lat, &lon);
503 : :
504 : 0 : if (map_source)
505 : : {
506 : 0 : g_autoptr(ShumateViewport) new_viewport = shumate_viewport_copy (self->viewport);
507 : 0 : shumate_viewport_set_zoom_level (new_viewport, zoom_level);
508 : 0 : move_location_to_coords_calc (self, &lat, &lon, self->current_x, self->current_y, new_viewport);
509 : :
510 : 0 : shumate_map_go_to_full_with_duration (self,
511 : : lat,
512 : : lon,
513 : : zoom_level,
514 [ # # # # ]: 0 : self->animate_zoom && animate ? ZOOM_ANIMATION_MS : 0);
515 : :
516 [ # # ]: 0 : if (self->goto_context != NULL)
517 : 0 : self->goto_context->zoom_animation = TRUE;
518 : : }
519 : : else
520 : 0 : shumate_viewport_set_zoom_level (self->viewport, zoom_level);
521 : :
522 : 0 : g_object_thaw_notify (G_OBJECT (self->viewport));
523 : 0 : }
524 : :
525 : : static gboolean
526 : 0 : on_scroll_controller_scroll (ShumateMap *self,
527 : : double dx,
528 : : double dy,
529 : : GtkEventControllerScroll *controller)
530 : : {
531 : 0 : double zoom_level = shumate_viewport_get_zoom_level (self->viewport);
532 : :
533 [ # # # # ]: 0 : if (self->goto_context != NULL && self->goto_context->zoom_animation)
534 : 0 : zoom_level = self->goto_context->to_zoom;
535 : :
536 [ # # ]: 0 : if (gtk_event_controller_scroll_get_unit (controller) == GDK_SCROLL_UNIT_SURFACE)
537 : : /* Smooth scrolling with a touchpad or similar device */
538 : 0 : set_zoom_level (self, zoom_level - dy / SCROLL_PIXELS_PER_LEVEL, FALSE);
539 : : else
540 : : {
541 : 0 : zoom_level -= dy / 5;
542 [ # # ]: 0 : if (fabs(dy) == 1)
543 : : /* Traditional discrete mouse, snap to the nearest 1/5 of a zoom level */
544 : 0 : set_zoom_level (self, roundf (zoom_level * 5) / 5, TRUE);
545 : : else
546 : : /* Smooth scrolling using a mouse.
547 : : *
548 : : * Various smooth-scrolling mice behave like "discrete" mice,
549 : : * while emitting fractions of a scroll at the same time.
550 : : * Do not round their events, or most of the scrolling gets ignored.*/
551 : 0 : set_zoom_level (self, zoom_level, TRUE);
552 : : }
553 : :
554 : 0 : return TRUE;
555 : : }
556 : :
557 : : static gboolean
558 : 0 : on_scroll_controller_decelerate (ShumateMap *self,
559 : : double vel_x,
560 : : double vel_y,
561 : : GtkEventControllerScroll *controller)
562 : : {
563 : 0 : double zoom_level = shumate_viewport_get_zoom_level (self->viewport);
564 : :
565 [ # # # # ]: 0 : if (self->goto_context != NULL && self->goto_context->zoom_animation)
566 : 0 : zoom_level = self->goto_context->to_zoom;
567 : :
568 : 0 : set_zoom_level (self, zoom_level - vel_y / SCROLL_PIXELS_PER_LEVEL / ZOOM_ANIMATION_MS, TRUE);
569 [ # # ]: 0 : if (self->goto_context != NULL)
570 : 0 : self->goto_context->zoom_deceleration = TRUE;
571 : :
572 : 0 : return TRUE;
573 : : }
574 : :
575 : : static void
576 : 0 : on_zoom_gesture_begin (ShumateMap *self,
577 : : GdkEventSequence *seq,
578 : : GtkGestureZoom *zoom)
579 : : {
580 : 0 : double zoom_level = shumate_viewport_get_zoom_level (self->viewport);
581 : 0 : double x, y;
582 : :
583 : 0 : gtk_gesture_set_state (GTK_GESTURE (zoom), GTK_EVENT_SEQUENCE_CLAIMED);
584 : 0 : cancel_deceleration (self);
585 : :
586 : 0 : self->zoom_level_begin = zoom_level;
587 : :
588 : 0 : gtk_gesture_get_bounding_box_center (GTK_GESTURE (zoom), &x, &y);
589 : 0 : shumate_viewport_widget_coords_to_location (self->viewport, GTK_WIDGET (self),
590 : : x, y,
591 : : &self->gesture_begin_lat,
592 : : &self->gesture_begin_lon);
593 : 0 : }
594 : :
595 : : static void
596 : 0 : on_zoom_gesture_update (ShumateMap *self,
597 : : GdkEventSequence *seq,
598 : : GtkGestureZoom *zoom)
599 : : {
600 : 0 : double x, y;
601 : 0 : double scale = gtk_gesture_zoom_get_scale_delta (zoom);
602 : :
603 : 0 : gtk_gesture_get_bounding_box_center (GTK_GESTURE (zoom), &x, &y);
604 : 0 : shumate_viewport_set_zoom_level (self->viewport, self->zoom_level_begin + log (scale) / G_LN2);
605 : 0 : move_location_to_coords (self, self->gesture_begin_lat, self->gesture_begin_lon, x, y);
606 : 0 : }
607 : :
608 : : static void
609 : 0 : on_rotate_gesture_begin (ShumateMap *self,
610 : : GdkEventSequence *seq,
611 : : GtkGestureRotate *rotate)
612 : : {
613 : 0 : double rotation = shumate_viewport_get_rotation (self->viewport);
614 : :
615 : 0 : gtk_gesture_set_state (GTK_GESTURE (rotate), GTK_EVENT_SEQUENCE_CLAIMED);
616 : 0 : cancel_deceleration (self);
617 : :
618 : 0 : self->rotate_begin = rotation;
619 : 0 : }
620 : :
621 : : static void
622 : 0 : on_rotate_gesture_update (ShumateMap *self,
623 : : GdkEventSequence *seq,
624 : : GtkGestureRotate *rotate)
625 : : {
626 : 0 : double rotation;
627 : 0 : double x, y;
628 : :
629 : 0 : rotation = gtk_gesture_rotate_get_angle_delta (rotate) + self->rotate_begin;
630 : :
631 : : /* snap to due north */
632 [ # # ]: 0 : if (fabs (fmod (rotation - 0.25, G_PI * 2)) < 0.5)
633 : 0 : rotation = 0.0;
634 : :
635 : 0 : shumate_viewport_set_rotation (self->viewport, rotation);
636 : 0 : gtk_gesture_get_bounding_box_center (GTK_GESTURE (rotate), &x, &y);
637 : 0 : move_location_to_coords (self, self->gesture_begin_lat, self->gesture_begin_lon, x, y);
638 : 0 : }
639 : :
640 : : static void
641 : 0 : on_click_gesture_pressed (ShumateMap *self,
642 : : int n_press,
643 : : double x,
644 : : double y,
645 : : GtkGestureClick *click)
646 : : {
647 [ # # ]: 0 : if (n_press == 2)
648 : : {
649 : 0 : double zoom_level = shumate_viewport_get_zoom_level (self->viewport);
650 : 0 : self->current_x = x;
651 : 0 : self->current_y = y;
652 : 0 : set_zoom_level (self, zoom_level + 1, TRUE);
653 : : }
654 : 0 : }
655 : :
656 : : static void
657 : 0 : on_motion_controller_motion (ShumateMap *self,
658 : : double x,
659 : : double y,
660 : : GtkEventControllerMotion *controller)
661 : : {
662 : 0 : self->current_x = x;
663 : 0 : self->current_y = y;
664 : 0 : }
665 : :
666 : : static void
667 : 0 : on_arrow_key (GtkWidget *widget,
668 : : const char *action,
669 : : GVariant *args)
670 : : {
671 : 0 : ShumateMap *self = SHUMATE_MAP (widget);
672 : 0 : int dx, dy;
673 : 0 : double lat, lon;
674 : :
675 : 0 : g_variant_get (args, "(ii)", &dx, &dy);
676 : :
677 : 0 : shumate_viewport_widget_coords_to_location (self->viewport, widget, dx * 25, dy * 25, &lat, &lon);
678 : 0 : move_location_to_coords (self, lat, lon, 0, 0);
679 : 0 : }
680 : :
681 : : static void
682 : 0 : shumate_map_get_property (GObject *object,
683 : : guint prop_id,
684 : : GValue *value,
685 : : GParamSpec *pspec)
686 : : {
687 : 0 : ShumateMap *self = SHUMATE_MAP (object);
688 : :
689 [ # # # # : 0 : switch (prop_id)
# # ]
690 : : {
691 : 0 : case PROP_ZOOM_ON_DOUBLE_CLICK:
692 : 0 : g_value_set_boolean (value, self->zoom_on_double_click);
693 : 0 : break;
694 : :
695 : 0 : case PROP_ANIMATE_ZOOM:
696 : 0 : g_value_set_boolean (value, self->animate_zoom);
697 : 0 : break;
698 : :
699 : 0 : case PROP_STATE:
700 : 0 : g_value_set_enum (value, self->state);
701 : 0 : break;
702 : :
703 : 0 : case PROP_GO_TO_DURATION:
704 : 0 : g_value_set_uint (value, self->go_to_duration);
705 : 0 : break;
706 : :
707 : 0 : case PROP_VIEWPORT:
708 : 0 : g_value_set_object (value, self->viewport);
709 : 0 : break;
710 : :
711 : 0 : default:
712 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
713 : : }
714 : 0 : }
715 : :
716 : :
717 : : static void
718 : 0 : shumate_map_set_property (GObject *object,
719 : : guint prop_id,
720 : : const GValue *value,
721 : : GParamSpec *pspec)
722 : : {
723 : 0 : ShumateMap *self = SHUMATE_MAP (object);
724 : :
725 [ # # # # ]: 0 : switch (prop_id)
726 : : {
727 : 0 : case PROP_ZOOM_ON_DOUBLE_CLICK:
728 : 0 : shumate_map_set_zoom_on_double_click (self, g_value_get_boolean (value));
729 : 0 : break;
730 : :
731 : 0 : case PROP_ANIMATE_ZOOM:
732 : 0 : shumate_map_set_animate_zoom (self, g_value_get_boolean (value));
733 : 0 : break;
734 : :
735 : 0 : case PROP_GO_TO_DURATION:
736 : 0 : shumate_map_set_go_to_duration (self, g_value_get_uint (value));
737 : 0 : break;
738 : :
739 : 0 : default:
740 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
741 : : }
742 : 0 : }
743 : :
744 : :
745 : : static void
746 : 0 : shumate_map_dispose (GObject *object)
747 : : {
748 : 0 : ShumateMap *self = SHUMATE_MAP (object);
749 : 0 : GtkWidget *child;
750 : :
751 [ # # ]: 0 : if (self->goto_context != NULL)
752 : 0 : shumate_map_stop_go_to (self);
753 : :
754 [ # # ]: 0 : while ((child = gtk_widget_get_first_child (GTK_WIDGET (object))))
755 : 0 : gtk_widget_unparent (child);
756 : :
757 [ # # ]: 0 : g_clear_object (&self->viewport);
758 : :
759 [ # # ]: 0 : g_clear_handle_id (&self->zoom_timeout, g_source_remove);
760 : :
761 : 0 : G_OBJECT_CLASS (shumate_map_parent_class)->dispose (object);
762 : 0 : }
763 : :
764 : : static void
765 : 0 : shumate_map_snapshot (GtkWidget *widget,
766 : : GtkSnapshot *snapshot)
767 : : {
768 : 0 : ShumateMap *self = SHUMATE_MAP (widget);
769 : 0 : ShumateInspectorSettings *settings = shumate_inspector_settings_get_default ();
770 : 0 : ShumateMapSource *map_source = shumate_viewport_get_reference_map_source (self->viewport);
771 : 0 : int width, height;
772 : :
773 : 0 : GTK_WIDGET_CLASS (shumate_map_parent_class)->snapshot (GTK_WIDGET (self), snapshot);
774 : :
775 [ # # ]: 0 : if (shumate_inspector_settings_get_show_debug_overlay (settings))
776 : : {
777 : 0 : g_autoptr(GString) all_debug_text = g_string_new ("");
778 : 0 : PangoContext *context = gtk_widget_create_pango_context (GTK_WIDGET (self));
779 : 0 : g_autoptr(PangoLayout) layout = NULL;
780 : 0 : double lat = shumate_location_get_latitude (SHUMATE_LOCATION (self->viewport));
781 : 0 : double lon = shumate_location_get_longitude (SHUMATE_LOCATION (self->viewport));
782 : 0 : double zoom = shumate_viewport_get_zoom_level (self->viewport);
783 : 0 : double rot = shumate_viewport_get_rotation (self->viewport);
784 : :
785 [ # # ]: 0 : g_string_append (all_debug_text, "<tt>");
786 : 0 : g_string_append_printf (all_debug_text,
787 : : "lat = %9.5f, lon = %10.5f\nzoom = %5.2f, rot = %5.1f\n",
788 : 0 : lat, lon, zoom, rot * 180 / G_PI);
789 : :
790 [ # # ]: 0 : if (map_source != NULL)
791 : 0 : g_string_append_printf (all_debug_text, "tile size = %4dpx (%7.2f)\n",
792 : : shumate_map_source_get_tile_size (map_source),
793 : : shumate_map_source_get_tile_size_at_zoom (map_source, zoom));
794 : :
795 [ # # ]: 0 : g_string_append (all_debug_text, "\n");
796 : :
797 : 0 : for (GtkWidget *w = gtk_widget_get_first_child (GTK_WIDGET (self));
798 [ # # ]: 0 : w != NULL;
799 : 0 : w = gtk_widget_get_next_sibling (w))
800 : : {
801 : 0 : ShumateLayer *layer = SHUMATE_LAYER (w);
802 : 0 : g_string_append_printf (all_debug_text, "<b>%s</b>\n", G_OBJECT_CLASS_NAME (G_OBJECT_GET_CLASS (layer)));
803 [ # # ]: 0 : if (SHUMATE_LAYER_GET_CLASS (layer)->get_debug_text != NULL)
804 : : {
805 : 0 : g_autofree char *debug_text = SHUMATE_LAYER_GET_CLASS (layer)->get_debug_text (layer);
806 [ # # ]: 0 : if (debug_text != NULL)
807 : : {
808 [ # # ]: 0 : g_string_append (all_debug_text, debug_text);
809 [ # # ]: 0 : g_string_append (all_debug_text, "\n");
810 : : }
811 : : }
812 : : }
813 : :
814 [ # # ]: 0 : g_string_append (all_debug_text, "</tt>");
815 : :
816 : 0 : layout = pango_layout_new (context);
817 : 0 : pango_layout_set_markup (layout, all_debug_text->str, -1);
818 : 0 : pango_layout_set_width (layout, gtk_widget_get_width (GTK_WIDGET (self)) * PANGO_SCALE);
819 : :
820 : 0 : pango_layout_get_pixel_size (layout, &width, &height);
821 : 0 : gtk_snapshot_append_color (snapshot, &(GdkRGBA){1, 1, 1, 0.7}, &GRAPHENE_RECT_INIT (0, 0, width, height));
822 : :
823 : 0 : gtk_snapshot_append_layout (snapshot, layout, &(GdkRGBA){0, 0, 0, 1});
824 : :
825 [ # # ]: 0 : g_object_unref (context);
826 : : }
827 : 0 : }
828 : :
829 : : static void
830 : 3 : shumate_map_class_init (ShumateMapClass *klass)
831 : : {
832 : 3 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
833 : 3 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
834 : :
835 : 3 : object_class->dispose = shumate_map_dispose;
836 : 3 : object_class->get_property = shumate_map_get_property;
837 : 3 : object_class->set_property = shumate_map_set_property;
838 : :
839 : 3 : widget_class->snapshot = shumate_map_snapshot;
840 : :
841 : : /**
842 : : * ShumateMap:zoom-on-double-click:
843 : : *
844 : : * Should the view zoom in and recenter when the user double click on the map.
845 : : */
846 : 6 : obj_properties[PROP_ZOOM_ON_DOUBLE_CLICK] =
847 : 3 : g_param_spec_boolean ("zoom-on-double-click",
848 : : "Zoom in on double click",
849 : : "Zoom in and recenter on double click on the map",
850 : : TRUE,
851 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
852 : :
853 : : /**
854 : : * ShumateMap:animate-zoom:
855 : : *
856 : : * Animate zoom change when zooming in/out.
857 : : */
858 : 6 : obj_properties[PROP_ANIMATE_ZOOM] =
859 : 3 : g_param_spec_boolean ("animate-zoom",
860 : : "Animate zoom level change",
861 : : "Animate zoom change when zooming in/out",
862 : : TRUE,
863 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
864 : :
865 : : /**
866 : : * ShumateMap:state:
867 : : *
868 : : * The view's global state. Useful to inform using if the view is busy loading
869 : : * tiles or not.
870 : : */
871 : 6 : obj_properties[PROP_STATE] =
872 : 3 : g_param_spec_enum ("state",
873 : : "View's state",
874 : : "View's global state",
875 : : SHUMATE_TYPE_STATE,
876 : : SHUMATE_STATE_NONE,
877 : : G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
878 : :
879 : : /**
880 : : * ShumateMap:go-to-duration:
881 : : *
882 : : * The duration of an animation when going to a location, in milliseconds.
883 : : * A value of 0 means that the duration is calculated automatically for you.
884 : : *
885 : : * Please note that animation of #shumate_map_ensure_visible also
886 : : * involves a 'go-to' animation.
887 : : *
888 : : */
889 : 6 : obj_properties[PROP_GO_TO_DURATION] =
890 : 3 : g_param_spec_uint ("go-to-duration",
891 : : "Go to animation duration",
892 : : "The duration of an animation when going to a location",
893 : : 0, G_MAXUINT, 0,
894 : : G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
895 : :
896 : : /**
897 : : * ShumateMap:viewport:
898 : : *
899 : : * The viewport, which contains information about the center, rotation, zoom,
900 : : * etc. of the map.
901 : : */
902 : 6 : obj_properties[PROP_VIEWPORT] =
903 : 3 : g_param_spec_object ("viewport",
904 : : "Viewport",
905 : : "Viewport",
906 : : SHUMATE_TYPE_VIEWPORT,
907 : : G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
908 : :
909 : 3 : g_object_class_install_properties (object_class,
910 : : N_PROPERTIES,
911 : : obj_properties);
912 : :
913 : : /**
914 : : * ShumateMap::animation-completed:
915 : : *
916 : : * The #ShumateMap::animation-completed signal is emitted when any animation in the view
917 : : * ends. This is a detailed signal. For example, if you want to be signaled
918 : : * only for go-to animation, you should connect to
919 : : * "animation-completed::go-to". And for zoom, connect to "animation-completed::zoom".
920 : : */
921 : 6 : signals[ANIMATION_COMPLETED] =
922 : 3 : g_signal_new ("animation-completed",
923 : : G_OBJECT_CLASS_TYPE (object_class),
924 : : G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
925 : : 0, NULL, NULL,
926 : : g_cclosure_marshal_VOID__OBJECT,
927 : : G_TYPE_NONE,
928 : : 0);
929 : :
930 : : /* Arrow keys */
931 : 3 : gtk_widget_class_install_action (widget_class, "pan", "(ii)", on_arrow_key);
932 : 3 : gtk_widget_class_add_binding_action (widget_class,
933 : : GDK_KEY_Left, 0,
934 : : "pan",
935 : : "(ii)", -1, 0);
936 : 3 : gtk_widget_class_add_binding_action (widget_class,
937 : : GDK_KEY_Right, 0,
938 : : "pan",
939 : : "(ii)", 1, 0);
940 : 3 : gtk_widget_class_add_binding_action (widget_class,
941 : : GDK_KEY_Up, 0,
942 : : "pan",
943 : : "(ii)", 0, -1);
944 : 3 : gtk_widget_class_add_binding_action (widget_class,
945 : : GDK_KEY_Down, 0,
946 : : "pan",
947 : : "(ii)", 0, 1);
948 : :
949 : 3 : gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
950 : 3 : gtk_widget_class_set_css_name (widget_class, "map-view");
951 : :
952 : 3 : go_to_quark = g_quark_from_static_string ("go-to");
953 : :
954 : 3 : shumate_inspector_page_ensure_registered ();
955 : 3 : }
956 : :
957 : : static void
958 : 2 : shumate_map_init (ShumateMap *self)
959 : : {
960 : 2 : ShumateInspectorSettings *settings = shumate_inspector_settings_get_default ();
961 : 2 : GtkGesture *drag_gesture;
962 : 2 : GtkEventController *scroll_controller;
963 : 2 : GtkEventController *motion_controller;
964 : 2 : GtkGesture *swipe_gesture;
965 : 2 : GtkGesture *zoom_gesture;
966 : 2 : GtkGesture *rotate_gesture;
967 : 2 : GtkGesture *click_gesture;
968 : :
969 : 2 : self->viewport = shumate_viewport_new ();
970 : 2 : self->zoom_on_double_click = TRUE;
971 : 2 : self->animate_zoom = TRUE;
972 : 2 : self->state = SHUMATE_STATE_NONE;
973 : 2 : self->goto_context = NULL;
974 : 2 : self->go_to_duration = 0;
975 : :
976 : 2 : g_signal_connect_object (settings, "notify::show-debug-overlay", G_CALLBACK (gtk_widget_queue_draw), self, G_CONNECT_SWAPPED);
977 : :
978 : 2 : gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "grab");
979 : :
980 : 2 : drag_gesture = gtk_gesture_drag_new ();
981 : 2 : g_signal_connect_swapped (drag_gesture, "drag-begin", G_CALLBACK (on_drag_gesture_drag_begin), self);
982 : 2 : g_signal_connect_swapped (drag_gesture, "drag-update", G_CALLBACK (on_drag_gesture_drag_update), self);
983 : 2 : g_signal_connect_swapped (drag_gesture, "drag-end", G_CALLBACK (on_drag_gesture_drag_end), self);
984 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drag_gesture));
985 : :
986 : 2 : swipe_gesture = gtk_gesture_swipe_new ();
987 : 2 : g_signal_connect (swipe_gesture, "swipe", G_CALLBACK (view_swipe_cb), self);
988 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (swipe_gesture));
989 : :
990 : 2 : scroll_controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL|GTK_EVENT_CONTROLLER_SCROLL_KINETIC);
991 : 2 : g_signal_connect_swapped (scroll_controller, "scroll", G_CALLBACK (on_scroll_controller_scroll), self);
992 : 2 : g_signal_connect_swapped (scroll_controller, "decelerate", G_CALLBACK (on_scroll_controller_decelerate), self);
993 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), scroll_controller);
994 : :
995 : 2 : zoom_gesture = gtk_gesture_zoom_new ();
996 : 2 : g_signal_connect_swapped (zoom_gesture, "begin", G_CALLBACK (on_zoom_gesture_begin), self);
997 : 2 : g_signal_connect_swapped (zoom_gesture, "update", G_CALLBACK (on_zoom_gesture_update), self);
998 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (zoom_gesture));
999 : :
1000 : 2 : motion_controller = gtk_event_controller_motion_new ();
1001 : 2 : g_signal_connect_swapped (motion_controller, "motion", G_CALLBACK (on_motion_controller_motion), self);
1002 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), motion_controller);
1003 : :
1004 : 2 : rotate_gesture = gtk_gesture_rotate_new ();
1005 : 2 : g_signal_connect_swapped (rotate_gesture, "begin", G_CALLBACK (on_rotate_gesture_begin), self);
1006 : 2 : g_signal_connect_swapped (rotate_gesture, "update", G_CALLBACK (on_rotate_gesture_update), self);
1007 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (rotate_gesture));
1008 : :
1009 : 2 : gtk_gesture_group (zoom_gesture, rotate_gesture);
1010 : :
1011 : 2 : click_gesture = gtk_gesture_click_new ();
1012 : 2 : gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (click_gesture), GDK_BUTTON_PRIMARY);
1013 : 2 : g_signal_connect_swapped (click_gesture, "pressed", G_CALLBACK (on_click_gesture_pressed), self);
1014 : 2 : gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (click_gesture));
1015 : :
1016 : 2 : gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
1017 : 2 : gtk_widget_set_focusable (GTK_WIDGET (self), TRUE);
1018 : 2 : }
1019 : :
1020 : : /**
1021 : : * shumate_map_new:
1022 : : *
1023 : : * Creates an instance of #ShumateMap.
1024 : : *
1025 : : * Returns: a new #ShumateMap ready to be used as a #GtkWidget.
1026 : : */
1027 : : ShumateMap *
1028 : 2 : shumate_map_new (void)
1029 : : {
1030 : 2 : return g_object_new (SHUMATE_TYPE_MAP, NULL);
1031 : : }
1032 : :
1033 : :
1034 : : ShumateMap *
1035 : 0 : shumate_map_new_simple (void)
1036 : : {
1037 : 0 : ShumateMap *view = g_object_new (SHUMATE_TYPE_MAP, NULL);
1038 : 0 : g_autoptr(ShumateMapSourceRegistry) registry = NULL;
1039 : 0 : ShumateMapSource *source;
1040 : 0 : ShumateMapLayer *map_layer;
1041 : 0 : ShumateViewport *viewport;
1042 : :
1043 : 0 : viewport = shumate_map_get_viewport (view);
1044 : 0 : registry = shumate_map_source_registry_new_with_defaults ();
1045 : 0 : source = shumate_map_source_registry_get_by_id (registry, SHUMATE_MAP_SOURCE_OSM_MAPNIK);
1046 : 0 : shumate_viewport_set_reference_map_source (viewport, source);
1047 : 0 : map_layer = shumate_map_layer_new (source, viewport);
1048 : 0 : shumate_map_add_layer (view, SHUMATE_LAYER (map_layer));
1049 : :
1050 [ # # ]: 0 : return view;
1051 : : }
1052 : :
1053 : : /**
1054 : : * shumate_map_get_viewport:
1055 : : * @self: a #ShumateMap
1056 : : *
1057 : : * Get the #ShumateViewport used by this view.
1058 : : *
1059 : : * Returns: (transfer none): the #ShumateViewport
1060 : : */
1061 : : ShumateViewport *
1062 : 2 : shumate_map_get_viewport (ShumateMap *self)
1063 : : {
1064 [ + - ]: 2 : g_return_val_if_fail (SHUMATE_IS_MAP (self), NULL);
1065 : :
1066 : 2 : return self->viewport;
1067 : : }
1068 : :
1069 : : /**
1070 : : * shumate_map_center_on:
1071 : : * @self: a #ShumateMap
1072 : : * @latitude: the longitude to center the map at
1073 : : * @longitude: the longitude to center the map at
1074 : : *
1075 : : * Centers the map on these coordinates.
1076 : : */
1077 : : void
1078 : 0 : shumate_map_center_on (ShumateMap *self,
1079 : : double latitude,
1080 : : double longitude)
1081 : : {
1082 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1083 : :
1084 : 0 : shumate_location_set_location (SHUMATE_LOCATION (self->viewport), latitude, longitude);
1085 : : }
1086 : :
1087 : : /**
1088 : : * shumate_map_stop_go_to:
1089 : : * @self: a #ShumateMap
1090 : : *
1091 : : * Stop the go to animation. The view will stay where it was when the
1092 : : * animation was stopped.
1093 : : */
1094 : : void
1095 : 0 : shumate_map_stop_go_to (ShumateMap *self)
1096 : : {
1097 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1098 : :
1099 [ # # ]: 0 : if (self->goto_context == NULL)
1100 : : return;
1101 : :
1102 : 0 : gtk_widget_remove_tick_callback (GTK_WIDGET (self), self->goto_context->tick_id);
1103 [ # # ]: 0 : g_clear_pointer (&self->goto_context, g_free);
1104 : :
1105 : 0 : g_signal_emit (self, signals[ANIMATION_COMPLETED], go_to_quark, NULL);
1106 : : }
1107 : :
1108 : :
1109 : : /**
1110 : : * shumate_map_go_to:
1111 : : * @self: a #ShumateMap
1112 : : * @latitude: the longitude to center the map at
1113 : : * @longitude: the longitude to center the map at
1114 : : *
1115 : : * Move from the current position to these coordinates. All tiles in the
1116 : : * intermediate view WILL be loaded!
1117 : : */
1118 : : void
1119 : 0 : shumate_map_go_to (ShumateMap *self,
1120 : : double latitude,
1121 : : double longitude)
1122 : : {
1123 : 0 : double zoom_level;
1124 : :
1125 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1126 [ # # # # ]: 0 : g_return_if_fail (latitude >= SHUMATE_MIN_LATITUDE && latitude <= SHUMATE_MAX_LATITUDE);
1127 [ # # # # ]: 0 : g_return_if_fail (longitude >= SHUMATE_MIN_LONGITUDE && longitude <= SHUMATE_MAX_LONGITUDE);
1128 : :
1129 : 0 : zoom_level = shumate_viewport_get_zoom_level (self->viewport);
1130 : :
1131 : 0 : shumate_map_go_to_full (self, latitude, longitude, zoom_level);
1132 : : }
1133 : :
1134 : :
1135 : : /**
1136 : : * shumate_map_go_to_full:
1137 : : * @self: a #ShumateMap
1138 : : * @latitude: the longitude to center the map at
1139 : : * @longitude: the longitude to center the map at
1140 : : * @zoom_level: the zoom level to end at
1141 : : *
1142 : : * Move from the current position to these coordinates and zoom to the given
1143 : : * zoom level. All tiles in the intermediate view WILL be loaded!
1144 : : */
1145 : : void
1146 : 0 : shumate_map_go_to_full (ShumateMap *self,
1147 : : double latitude,
1148 : : double longitude,
1149 : : double zoom_level)
1150 : : {
1151 : 0 : guint duration;
1152 : :
1153 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1154 [ # # # # ]: 0 : g_return_if_fail (latitude >= SHUMATE_MIN_LATITUDE && latitude <= SHUMATE_MAX_LATITUDE);
1155 [ # # # # ]: 0 : g_return_if_fail (longitude >= SHUMATE_MIN_LONGITUDE && longitude <= SHUMATE_MAX_LONGITUDE);
1156 : :
1157 : 0 : duration = self->go_to_duration;
1158 [ # # ]: 0 : if (duration == 0) /* calculate duration from zoom level */
1159 : 0 : duration = 500 * zoom_level / 2.0;
1160 : :
1161 : 0 : shumate_map_go_to_full_with_duration (self, latitude, longitude, zoom_level, duration);
1162 : : }
1163 : :
1164 : :
1165 : : /**
1166 : : * shumate_map_go_to_full_with_duration:
1167 : : * @self: a #ShumateMap
1168 : : * @latitude: the longitude to center the map at
1169 : : * @longitude: the longitude to center the map at
1170 : : * @zoom_level: the zoom level to end at
1171 : : * @duration_ms: animation duration in milliseconds
1172 : : *
1173 : : * Move from the current position to these coordinates and zoom to the given
1174 : : * zoom level. The given duration is used instead of the map's default [property@Map:go-to-duration].
1175 : : * All tiles in the intermediate view WILL be loaded!
1176 : : */
1177 : : void
1178 : 0 : shumate_map_go_to_full_with_duration (ShumateMap *self,
1179 : : double latitude,
1180 : : double longitude,
1181 : : double zoom_level,
1182 : : guint duration_ms)
1183 : : {
1184 : 0 : double min_zoom, max_zoom;
1185 : 0 : GoToContext *ctx;
1186 : 0 : gboolean enable_animations;
1187 : :
1188 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1189 [ # # # # ]: 0 : g_return_if_fail (latitude >= SHUMATE_MIN_LATITUDE && latitude <= SHUMATE_MAX_LATITUDE);
1190 [ # # # # ]: 0 : g_return_if_fail (longitude >= SHUMATE_MIN_LONGITUDE && longitude <= SHUMATE_MAX_LONGITUDE);
1191 : :
1192 : 0 : g_object_get (gtk_widget_get_settings (GTK_WIDGET (self)),
1193 : : "gtk-enable-animations", &enable_animations,
1194 : : NULL);
1195 : :
1196 [ # # # # ]: 0 : if (!enable_animations || duration_ms == 0)
1197 : : {
1198 : 0 : shumate_map_center_on (self, latitude, longitude);
1199 : 0 : shumate_viewport_set_zoom_level (self->viewport, zoom_level);
1200 : 0 : return;
1201 : : }
1202 : :
1203 : 0 : shumate_map_stop_go_to (self);
1204 : :
1205 : 0 : min_zoom = shumate_viewport_get_min_zoom_level (self->viewport);
1206 : 0 : max_zoom = shumate_viewport_get_max_zoom_level (self->viewport);
1207 : :
1208 : 0 : ctx = g_new (GoToContext, 1);
1209 : 0 : ctx->start_us = g_get_monotonic_time ();
1210 : 0 : ctx->duration_us = ms_to_us (duration_ms);
1211 : 0 : ctx->from_latitude = shumate_location_get_latitude (SHUMATE_LOCATION (self->viewport));
1212 : 0 : ctx->from_longitude = shumate_location_get_longitude (SHUMATE_LOCATION (self->viewport));
1213 [ # # # # ]: 0 : ctx->from_zoom = CLAMP (shumate_viewport_get_zoom_level (self->viewport), min_zoom, max_zoom);
1214 : 0 : ctx->to_latitude = latitude;
1215 : 0 : ctx->to_longitude = longitude;
1216 [ # # # # ]: 0 : ctx->to_zoom = CLAMP (zoom_level, min_zoom, max_zoom);
1217 : 0 : ctx->zoom_animation = FALSE;
1218 : :
1219 : 0 : self->goto_context = ctx;
1220 : :
1221 : 0 : ctx->tick_id = gtk_widget_add_tick_callback (GTK_WIDGET (self), go_to_tick_cb, NULL, NULL);
1222 : : }
1223 : :
1224 : :
1225 : : /**
1226 : : * shumate_map_get_go_to_duration:
1227 : : * @self: a #ShumateMap
1228 : : *
1229 : : * Get the 'go-to-duration' property.
1230 : : *
1231 : : * Returns: the animation duration when calling [method@Map.go_to],
1232 : : * in milliseconds.
1233 : : */
1234 : : guint
1235 : 0 : shumate_map_get_go_to_duration (ShumateMap *self)
1236 : : {
1237 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_MAP (self), 0);
1238 : :
1239 : 0 : return self->go_to_duration;
1240 : : }
1241 : :
1242 : : /**
1243 : : * shumate_map_set_go_to_duration:
1244 : : * @self: a #ShumateMap
1245 : : * @duration: the animation duration, in milliseconds
1246 : : *
1247 : : * Set the duration of the transition of [method@Map.go_to].
1248 : : */
1249 : : void
1250 : 0 : shumate_map_set_go_to_duration (ShumateMap *self,
1251 : : guint duration)
1252 : : {
1253 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1254 : :
1255 [ # # ]: 0 : if (self->go_to_duration == duration)
1256 : : return;
1257 : :
1258 : 0 : self->go_to_duration = duration;
1259 : 0 : g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_GO_TO_DURATION]);
1260 : : }
1261 : :
1262 : : /**
1263 : : * shumate_map_add_layer:
1264 : : * @self: a #ShumateMap
1265 : : * @layer: a #ShumateLayer
1266 : : *
1267 : : * Adds a new layer to the view
1268 : : */
1269 : : void
1270 : 4 : shumate_map_add_layer (ShumateMap *self,
1271 : : ShumateLayer *layer)
1272 : : {
1273 [ + - ]: 4 : g_return_if_fail (SHUMATE_IS_MAP (self));
1274 [ - + ]: 4 : g_return_if_fail (SHUMATE_IS_LAYER (layer));
1275 : :
1276 : 4 : gtk_widget_insert_before (GTK_WIDGET (layer), GTK_WIDGET (self), NULL);
1277 : : }
1278 : :
1279 : :
1280 : : /**
1281 : : * shumate_map_insert_layer_behind:
1282 : : * @self: a #ShumateMap
1283 : : * @layer: a #ShumateLayer
1284 : : * @next_sibling: (nullable): a #ShumateLayer that is a child of @self, or %NULL
1285 : : *
1286 : : * Adds @layer to @self behind @next_sibling or, if @next_sibling is %NULL, at
1287 : : * the top of the layer list.
1288 : : */
1289 : : void
1290 : 4 : shumate_map_insert_layer_behind (ShumateMap *self,
1291 : : ShumateLayer *layer,
1292 : : ShumateLayer *next_sibling)
1293 : : {
1294 [ + - ]: 4 : g_return_if_fail (SHUMATE_IS_MAP (self));
1295 [ - + ]: 4 : g_return_if_fail (SHUMATE_IS_LAYER (layer));
1296 [ + + - + ]: 4 : g_return_if_fail (next_sibling == NULL || SHUMATE_IS_LAYER (next_sibling));
1297 [ + + - + ]: 4 : g_return_if_fail (next_sibling == NULL || gtk_widget_get_parent (GTK_WIDGET (next_sibling)) == GTK_WIDGET (self));
1298 : :
1299 : 4 : gtk_widget_insert_before (GTK_WIDGET (layer), GTK_WIDGET (self), GTK_WIDGET (next_sibling));
1300 : : }
1301 : :
1302 : :
1303 : : /**
1304 : : * shumate_map_insert_layer_above:
1305 : : * @self: a #ShumateMap
1306 : : * @layer: a #ShumateLayer
1307 : : * @next_sibling: (nullable): a #ShumateLayer that is a child of @self, or %NULL
1308 : : *
1309 : : * Adds @layer to @self above @next_sibling or, if @next_sibling is %NULL, at
1310 : : * the bottom of the layer list.
1311 : : */
1312 : : void
1313 : 4 : shumate_map_insert_layer_above (ShumateMap *self,
1314 : : ShumateLayer *layer,
1315 : : ShumateLayer *next_sibling)
1316 : : {
1317 [ + - ]: 4 : g_return_if_fail (SHUMATE_IS_MAP (self));
1318 [ - + ]: 4 : g_return_if_fail (SHUMATE_IS_LAYER (layer));
1319 [ + + - + ]: 4 : g_return_if_fail (next_sibling == NULL || SHUMATE_IS_LAYER (next_sibling));
1320 [ + + - + ]: 4 : g_return_if_fail (next_sibling == NULL || gtk_widget_get_parent (GTK_WIDGET (next_sibling)) == GTK_WIDGET (self));
1321 : :
1322 : 4 : gtk_widget_insert_after (GTK_WIDGET (layer), GTK_WIDGET (self), GTK_WIDGET (next_sibling));
1323 : : }
1324 : :
1325 : :
1326 : : /**
1327 : : * shumate_map_remove_layer:
1328 : : * @self: a #ShumateMap
1329 : : * @layer: a #ShumateLayer
1330 : : *
1331 : : * Removes the given layer from the view
1332 : : */
1333 : : void
1334 : 8 : shumate_map_remove_layer (ShumateMap *self,
1335 : : ShumateLayer *layer)
1336 : : {
1337 [ + - ]: 8 : g_return_if_fail (SHUMATE_IS_MAP (self));
1338 [ - + ]: 8 : g_return_if_fail (SHUMATE_IS_LAYER (layer));
1339 : :
1340 [ - + ]: 8 : if (gtk_widget_get_parent (GTK_WIDGET (layer)) != GTK_WIDGET (self))
1341 : : {
1342 : 0 : g_critical ("The given ShumateLayer isn't a child of the view");
1343 : 0 : return;
1344 : : }
1345 : :
1346 : 8 : gtk_widget_unparent (GTK_WIDGET (layer));
1347 : : }
1348 : :
1349 : : /**
1350 : : * shumate_map_set_map_source:
1351 : : * @self: a #ShumateMap
1352 : : * @map_source: a #ShumateMapSource
1353 : : *
1354 : : * Changes the currently used map source. #g_object_unref() will be called on
1355 : : * the previous one.
1356 : : *
1357 : : * As a side effect, changing the primary map source will also clear all
1358 : : * secondary map sources.
1359 : : */
1360 : : void
1361 : 0 : shumate_map_set_map_source (ShumateMap *self,
1362 : : ShumateMapSource *source)
1363 : : {
1364 : 0 : ShumateMapSource *ref_map_source;
1365 : :
1366 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1367 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP_SOURCE (source));
1368 : :
1369 : 0 : ref_map_source = shumate_viewport_get_reference_map_source (self->viewport);
1370 [ # # ]: 0 : if (ref_map_source == source)
1371 : : return;
1372 : :
1373 : 0 : shumate_viewport_set_reference_map_source (self->viewport, source);
1374 : : }
1375 : :
1376 : : /**
1377 : : * shumate_map_set_zoom_on_double_click:
1378 : : * @self: a #ShumateMap
1379 : : * @value: a #gboolean
1380 : : *
1381 : : * Should the view zoom in and recenter when the user double click on the map.
1382 : : */
1383 : : void
1384 : 0 : shumate_map_set_zoom_on_double_click (ShumateMap *self,
1385 : : gboolean value)
1386 : : {
1387 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1388 : :
1389 : 0 : self->zoom_on_double_click = value;
1390 : 0 : g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_ZOOM_ON_DOUBLE_CLICK]);
1391 : : }
1392 : :
1393 : :
1394 : : /**
1395 : : * shumate_map_set_animate_zoom:
1396 : : * @self: a #ShumateMap
1397 : : * @value: a #gboolean
1398 : : *
1399 : : * Should the view animate zoom level changes.
1400 : : */
1401 : : void
1402 : 0 : shumate_map_set_animate_zoom (ShumateMap *self,
1403 : : gboolean value)
1404 : : {
1405 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1406 : :
1407 : 0 : self->animate_zoom = value;
1408 : 0 : g_object_notify_by_pspec (G_OBJECT (self), obj_properties[PROP_ANIMATE_ZOOM]);
1409 : : }
1410 : :
1411 : : /**
1412 : : * shumate_map_get_zoom_on_double_click:
1413 : : * @self: a #ShumateMap
1414 : : *
1415 : : * Checks whether the view zooms on double click.
1416 : : *
1417 : : * Returns: TRUE if the view zooms on double click, FALSE otherwise.
1418 : : */
1419 : : gboolean
1420 : 0 : shumate_map_get_zoom_on_double_click (ShumateMap *self)
1421 : : {
1422 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_MAP (self), FALSE);
1423 : :
1424 : 0 : return self->zoom_on_double_click;
1425 : : }
1426 : :
1427 : :
1428 : : /**
1429 : : * shumate_map_get_animate_zoom:
1430 : : * @self: a #ShumateMap
1431 : : *
1432 : : * Checks whether the view animates zoom level changes.
1433 : : *
1434 : : * Returns: TRUE if the view animates zooms, FALSE otherwise.
1435 : : */
1436 : : gboolean
1437 : 0 : shumate_map_get_animate_zoom (ShumateMap *self)
1438 : : {
1439 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_MAP (self), FALSE);
1440 : :
1441 : 0 : return self->animate_zoom;
1442 : : }
1443 : :
1444 : : /**
1445 : : * shumate_map_get_state:
1446 : : * @self: a #ShumateMap
1447 : : *
1448 : : * Gets the view's state.
1449 : : *
1450 : : * Returns: the state.
1451 : : */
1452 : : ShumateState
1453 : 0 : shumate_map_get_state (ShumateMap *self)
1454 : : {
1455 [ # # ]: 0 : g_return_val_if_fail (SHUMATE_IS_MAP (self), SHUMATE_STATE_NONE);
1456 : :
1457 : 0 : return self->state;
1458 : : }
1459 : :
1460 : : static void
1461 : 0 : zoom (ShumateMap *self,
1462 : : gboolean zoom_out)
1463 : : {
1464 [ # # ]: 0 : double amount = (zoom_out ? -.2 : .2);
1465 : :
1466 : : /* If there is an ongoing animation, add to it rather than starting a new animation from the current position */
1467 [ # # # # ]: 0 : if (self->goto_context != NULL && self->goto_context->zoom_animation)
1468 : : {
1469 : 0 : shumate_map_go_to_full_with_duration (self,
1470 : : self->goto_context->to_latitude,
1471 : : self->goto_context->to_longitude,
1472 : 0 : self->goto_context->to_zoom + amount,
1473 : : ZOOM_ANIMATION_MS);
1474 : : }
1475 : : else
1476 : : {
1477 : 0 : double zoom_level = shumate_viewport_get_zoom_level (self->viewport);
1478 : 0 : shumate_map_go_to_full_with_duration (self,
1479 : 0 : shumate_location_get_latitude (SHUMATE_LOCATION (self->viewport)),
1480 : 0 : shumate_location_get_longitude (SHUMATE_LOCATION (self->viewport)),
1481 : 0 : roundf ((zoom_level + amount) * 5) / 5,
1482 [ # # ]: 0 : self->animate_zoom ? ZOOM_ANIMATION_MS : 0);
1483 : : }
1484 : :
1485 [ # # ]: 0 : if (self->goto_context != NULL)
1486 : 0 : self->goto_context->zoom_animation = TRUE;
1487 : 0 : }
1488 : :
1489 : : /**
1490 : : * shumate_map_zoom_in:
1491 : : * @self: a [class@Map]
1492 : : *
1493 : : * Zooms the map in. If [property@Map:animate-zoom] is `TRUE`, the change will be animated.
1494 : : */
1495 : : void
1496 : 0 : shumate_map_zoom_in (ShumateMap *self)
1497 : : {
1498 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1499 : 0 : zoom (self, FALSE);
1500 : : }
1501 : :
1502 : : /**
1503 : : * shumate_map_zoom_out:
1504 : : * @self: a [class@Map]
1505 : : *
1506 : : * Zooms the map out. If [property@Map:animate-zoom] is `TRUE`, the change will be animated.
1507 : : */
1508 : : void
1509 : 0 : shumate_map_zoom_out (ShumateMap *self)
1510 : : {
1511 [ # # ]: 0 : g_return_if_fail (SHUMATE_IS_MAP (self));
1512 : 0 : zoom (self, TRUE);
1513 : : }
|