shumate-map.c 42.8 KB
Newer Older
1
2
3
/*
 * Copyright (C) 2008-2009 Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
 * Copyright (C) 2010-2013 Jiri Techet <techet@gmail.com>
4
 * Copyright (C) 2012,2020 Collabora Ltd. (https://www.collabora.com/)
Marcus Lundblad's avatar
Marcus Lundblad committed
5
 * Copyright (C) 2019 Marcus Lundblad <ml@update.uu.se>
6
 * Copyright (C) 2020 Corentin Noël <corentin.noel@collabora.com>
Marcus Lundblad's avatar
Marcus Lundblad committed
7
 *
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

/**
25
 * ShumateMap:
26
 *
27
28
29
30
31
32
 * The Map widget is a [class@Gtk.Widget] that show and allows interaction with
 * the user.
 *
 * This is the base widget and doesn't have advanced features. You can check the
 * [class@Shumate.SimpleMap] for a ready-to-use widget.
 *
James Westman's avatar
James Westman committed
33
 * By default, a [class@Shumate.Viewport] is created and can be accessed with
34
35
36
37
38
39
 * [method@Shumate.Map.get_viewport].
 *
 * Unless created with [ctor@Shumate.Map.new_simple], the widget doesn't hold any
 * layer and won't show anything. A [class@Shumate.Layer] can be added or removed
 * using the [method@Shumate.Map.add_layer] or [method@Shumate.Map.remove_layer]
 * methods.
40
41
 */

42
#include "shumate-map.h"
43

Marcus Lundblad's avatar
Marcus Lundblad committed
44
45
#include "shumate.h"
#include "shumate-enum-types.h"
46
#include "shumate-kinetic-scrolling-private.h"
Marcus Lundblad's avatar
Marcus Lundblad committed
47
#include "shumate-marshal.h"
48
#include "shumate-map-layer.h"
Marcus Lundblad's avatar
Marcus Lundblad committed
49
#include "shumate-map-source.h"
50
#include "shumate-map-source-registry.h"
Marcus Lundblad's avatar
Marcus Lundblad committed
51
52
#include "shumate-tile.h"
#include "shumate-license.h"
53
#include "shumate-location.h"
54
#include "shumate-viewport-private.h"
55
56
57

#include <glib.h>
#include <glib-object.h>
58
#include <gtk/gtk.h>
59
60
#include <math.h>

61
#define DECELERATION_FRICTION 4.0
James Westman's avatar
James Westman committed
62
#define ZOOM_ANIMATION_MS 200
63

64
65
66
67
68
69
70
71
72
enum
{
  /* normal signals */
  ANIMATION_COMPLETED,
  LAST_SIGNAL
};

enum
{
73
  PROP_ZOOM_ON_DOUBLE_CLICK = 1,
74
75
  PROP_ANIMATE_ZOOM,
  PROP_STATE,
76
  PROP_GO_TO_DURATION,
77
  PROP_VIEWPORT,
78
  N_PROPERTIES
79
80
81
82
};

static guint signals[LAST_SIGNAL] = { 0, };

83
static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
84
static GQuark go_to_quark;
85

86
87
88
/* Between state values for go_to */
typedef struct
{
89
90
  int64_t duration_us;
  int64_t start_us;
91
92
  double to_latitude;
  double to_longitude;
James Westman's avatar
James Westman committed
93
  double to_zoom;
94
95
  double from_latitude;
  double from_longitude;
James Westman's avatar
James Westman committed
96
  double from_zoom;
97
  guint tick_id;
James Westman's avatar
James Westman committed
98
  gboolean zoom_animation : 1;
99
100
} GoToContext;

101
102
103
typedef struct
{
  ShumateKineticScrolling *kinetic_scrolling;
104
  ShumateMap *map;
105
106
107
108
109
  double start_lat;
  double start_lon;
  int64_t last_deceleration_time_us;
  graphene_vec2_t direction;
} KineticScrollData;
110

111
typedef struct
112
{
113
114
  ShumateViewport *viewport;

115
116
117
  gboolean zoom_on_double_click;
  gboolean animate_zoom;

Marcus Lundblad's avatar
Marcus Lundblad committed
118
  ShumateState state; /* View's global state */
119
120
121

  GoToContext *goto_context;

122
123
  guint deceleration_tick_id;

124
  guint zoom_timeout;
Marcus Lundblad's avatar
Marcus Lundblad committed
125

126
  guint go_to_duration;
127

128
129
  double current_x;
  double current_y;
130

131
  double zoom_level_begin;
James Westman's avatar
James Westman committed
132
  double rotate_begin;
133

James Westman's avatar
James Westman committed
134
135
136
137
  double gesture_begin_lat;
  double gesture_begin_lon;
  double drag_begin_x;
  double drag_begin_y;
138
} ShumateMapPrivate;
139

140
G_DEFINE_TYPE_WITH_PRIVATE (ShumateMap, shumate_map, GTK_TYPE_WIDGET);
141

James Westman's avatar
James Westman committed
142
143
144
145
146
147
148
static double
positive_mod (double i, double n)
{
  return fmod (fmod (i, n) + n, n);
}

static void
James Westman's avatar
James Westman committed
149
150
151
152
153
154
move_location_to_coords_calc (ShumateMap      *self,
                              double          *lat,
                              double          *lon,
                              double           x,
                              double           y,
                              ShumateViewport *viewport)
James Westman's avatar
James Westman committed
155
{
James Westman's avatar
James Westman committed
156
157
  ShumateMapSource *map_source = shumate_viewport_get_reference_map_source (viewport);
  double zoom_level = shumate_viewport_get_zoom_level (viewport);
James Westman's avatar
James Westman committed
158
159
160
161
162
163
164
165
166
167
168
  double tile_size, map_width, map_height;
  double map_x, map_y;
  double target_lat, target_lon;
  double target_map_x, target_map_y;
  double current_lat, current_lon;
  double current_map_x, current_map_y;
  double new_map_x, new_map_y;

  if (map_source == NULL)
    return;

James Westman's avatar
James Westman committed
169
  tile_size = shumate_map_source_get_tile_size_at_zoom (map_source, zoom_level);
James Westman's avatar
James Westman committed
170
171
172
  map_width = tile_size * shumate_map_source_get_column_count (map_source, zoom_level);
  map_height = tile_size * shumate_map_source_get_row_count (map_source, zoom_level);

James Westman's avatar
James Westman committed
173
174
  map_x = shumate_map_source_get_x (map_source, zoom_level, *lon);
  map_y = shumate_map_source_get_y (map_source, zoom_level, *lat);
James Westman's avatar
James Westman committed
175

James Westman's avatar
James Westman committed
176
177
  current_lat = shumate_location_get_latitude (SHUMATE_LOCATION (viewport));
  current_lon = shumate_location_get_longitude (SHUMATE_LOCATION (viewport));
James Westman's avatar
James Westman committed
178
179
180
  current_map_x = shumate_map_source_get_x (map_source, zoom_level, current_lon);
  current_map_y = shumate_map_source_get_y (map_source, zoom_level, current_lat);

James Westman's avatar
James Westman committed
181
  shumate_viewport_widget_coords_to_location (viewport, GTK_WIDGET (self), x, y, &target_lat, &target_lon);
James Westman's avatar
James Westman committed
182
183
184
185
186
187
  target_map_x = shumate_map_source_get_x (map_source, zoom_level, target_lon);
  target_map_y = shumate_map_source_get_y (map_source, zoom_level, target_lat);

  new_map_x = positive_mod (current_map_x - (target_map_x - map_x), map_width);
  new_map_y = positive_mod (current_map_y - (target_map_y - map_y), map_height);

James Westman's avatar
James Westman committed
188
189
190
191
192
193
194
195
196
197
198
199
200
201
  *lat = shumate_map_source_get_latitude (map_source, zoom_level, new_map_y);
  *lon = shumate_map_source_get_longitude (map_source, zoom_level, new_map_x);
}

static void
move_location_to_coords (ShumateMap *self,
                         double lat,
                         double lon,
                         double x,
                         double y)
{
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
  move_location_to_coords_calc (self, &lat, &lon, x, y, priv->viewport);
  shumate_location_set_location (SHUMATE_LOCATION (priv->viewport), lat, lon);
James Westman's avatar
James Westman committed
202
203
}

204
static void
205
206
207
208
209
move_viewport_from_pixel_offset (ShumateMap *self,
                                 double      latitude,
                                 double      longitude,
                                 double      offset_x,
                                 double      offset_y)
210
{
211
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
212
213
214
215
  ShumateMapSource *map_source;
  double x, y;
  double lat, lon;

216
  g_assert (SHUMATE_IS_MAP (self));
217
218
219
220
221

  map_source = shumate_viewport_get_reference_map_source (priv->viewport);
  if (!map_source)
    return;

James Westman's avatar
James Westman committed
222
  shumate_viewport_location_to_widget_coords (priv->viewport, GTK_WIDGET (self), latitude, longitude, &x, &y);
223

James Westman's avatar
James Westman committed
224
225
  x -= offset_x;
  y -= offset_y;
226

James Westman's avatar
James Westman committed
227
  shumate_viewport_widget_coords_to_location (priv->viewport, GTK_WIDGET (self), x, y, &lat, &lon);
228

James Westman's avatar
James Westman committed
229
230
  lat = fmod (lat + 90, 180) - 90;
  lon = fmod (lon + 180, 360) - 180;
231
232
233
234

  shumate_location_set_location (SHUMATE_LOCATION (priv->viewport), lat, lon);
}

235
static void
236
cancel_deceleration (ShumateMap *self)
237
{
238
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
239
240
241
242
243
244
245
246
247
248
249
250
251
252

  if (priv->deceleration_tick_id > 0)
    {
      gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->deceleration_tick_id);
      priv->deceleration_tick_id = 0;
    }
}

static gboolean
view_deceleration_tick_cb (GtkWidget     *widget,
                           GdkFrameClock *frame_clock,
                           gpointer       user_data)
{
  KineticScrollData *data = user_data;
253
  ShumateMap *map = data->map;
254
255
256
257
  int64_t current_time_us;
  double elapsed_us;
  double position;

258
  g_assert (SHUMATE_IS_MAP (map));
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279

  current_time_us = gdk_frame_clock_get_frame_time (frame_clock);
  elapsed_us = current_time_us - data->last_deceleration_time_us;

  /* The frame clock can sometimes fire immediately after adding a tick callback,
   * in which case no time has passed, making it impossible to calculate the
   * kinetic factor. If this is the case, wait for the next tick.
   */
  if (G_APPROX_VALUE (elapsed_us, 0.0, FLT_EPSILON))
    return G_SOURCE_CONTINUE;

  data->last_deceleration_time_us = current_time_us;

  if (data->kinetic_scrolling &&
      shumate_kinetic_scrolling_tick (data->kinetic_scrolling, elapsed_us, &position))
    {
      graphene_vec2_t new_positions;

      graphene_vec2_init (&new_positions, position, position);
      graphene_vec2_multiply (&new_positions, &data->direction, &new_positions);

280
      move_viewport_from_pixel_offset (map,
281
282
283
284
285
286
287
288
289
290
291
292
                                       data->start_lat,
                                       data->start_lon,
                                       graphene_vec2_get_x (&new_positions),
                                       graphene_vec2_get_y (&new_positions));
    }
  else
    {
      g_clear_pointer (&data->kinetic_scrolling, shumate_kinetic_scrolling_free);
    }

  if (!data->kinetic_scrolling)
    {
293
      cancel_deceleration (map);
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
      return G_SOURCE_REMOVE;
    }

  return G_SOURCE_CONTINUE;
}


static void
kinetic_scroll_data_free (KineticScrollData *data)
{
  if (data == NULL)
    return;

  g_clear_pointer (&data->kinetic_scrolling, shumate_kinetic_scrolling_free);
  g_free (data);
}

static void
312
313
314
start_deceleration (ShumateMap *self,
                    double      h_velocity,
                    double      v_velocity)
315
{
316
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
317
318
319
320
321
322
323
324
325
326
327
  GdkFrameClock *frame_clock;
  KineticScrollData *data;
  graphene_vec2_t velocity;

  g_assert (priv->deceleration_tick_id == 0);

  frame_clock = gtk_widget_get_frame_clock (GTK_WIDGET (self));

  graphene_vec2_init (&velocity, h_velocity, v_velocity);

  data = g_new0 (KineticScrollData, 1);
328
  data->map = self;
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
  data->last_deceleration_time_us = gdk_frame_clock_get_frame_time (frame_clock);
  data->start_lat = shumate_location_get_latitude (SHUMATE_LOCATION (priv->viewport));
  data->start_lon = shumate_location_get_longitude (SHUMATE_LOCATION (priv->viewport));
  graphene_vec2_normalize (&velocity, &data->direction);
  data->kinetic_scrolling =
    shumate_kinetic_scrolling_new (DECELERATION_FRICTION,
                                   graphene_vec2_length (&velocity));

  priv->deceleration_tick_id =
    gtk_widget_add_tick_callback (GTK_WIDGET (self),
                                  view_deceleration_tick_cb,
                                  data,
                                  (GDestroyNotify) kinetic_scroll_data_free);
}

344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
static inline double
ease_in_out_quad (double p)
{
  p = 2.0 * p;
  if (p < 1.0)
    return 0.5 * p * p;

  p -= 1.0;
  return -0.5 * (p * (p - 2) - 1);
}

static inline int64_t
ms_to_us (int64_t ms)
{
  return ms * 1000;
}

static gboolean
go_to_tick_cb (GtkWidget     *widget,
               GdkFrameClock *frame_clock,
               gpointer       user_data)
{
James Westman's avatar
James Westman committed
366
367
  GoToContext *ctx;
  ShumateMapPrivate *priv;
368
369
370
  int64_t now_us;
  double latitude, longitude;
  double progress;
James Westman's avatar
James Westman committed
371
372
373
374
  double current_zoom;

  g_assert (SHUMATE_IS_MAP (widget));
  g_assert (user_data == NULL);
375

James Westman's avatar
James Westman committed
376
377
378
379
  priv = shumate_map_get_instance_private (SHUMATE_MAP (widget));
  ctx = priv->goto_context;

  g_assert (ctx != NULL);
380
381
382
383
384
385
386
387
388
  g_assert (ctx->duration_us >= 0);

  now_us = g_get_monotonic_time ();

  if (now_us >= ctx->start_us + ctx->duration_us)
    {
      shumate_location_set_location (SHUMATE_LOCATION (priv->viewport),
                                     ctx->to_latitude,
                                     ctx->to_longitude);
James Westman's avatar
James Westman committed
389
390
      shumate_viewport_set_zoom_level (priv->viewport, ctx->to_zoom);
      shumate_map_stop_go_to (SHUMATE_MAP (widget));
391
392
393
394
395
396
397
      return G_SOURCE_REMOVE;
    }

  progress = (now_us - ctx->start_us) / (double) ctx->duration_us;
  g_assert (progress >= 0.0 && progress <= 1.0);

  /* Apply the ease function to the progress itself */
James Westman's avatar
James Westman committed
398
399
  if (!ctx->zoom_animation)
    progress = ease_in_out_quad (progress);
400

James Westman's avatar
James Westman committed
401
402
403
404
405
406
407
408
409
410
  /* Interpolate zoom level */
  current_zoom = ctx->from_zoom + (ctx->to_zoom - ctx->from_zoom) * progress;
  shumate_viewport_set_zoom_level (priv->viewport, current_zoom);

  /* If we're zooming, we need to adjust for that in the progress, otherwise
   * the animation will speed up at higher zoom levels. */
  if (ctx->to_zoom != ctx->from_zoom)
    progress = (pow (2, -ctx->from_zoom) - pow (2, -current_zoom))
                / (pow (2, -ctx->from_zoom) - pow (2, -ctx->to_zoom));

411
412
413
414
415
416
417
418
419
420
421
  /* Since progress already follows the easing curve, a simple LERP is guaranteed
   * to follow it too.
   */
  latitude = ctx->from_latitude + (ctx->to_latitude - ctx->from_latitude) * progress;
  longitude = ctx->from_longitude + (ctx->to_longitude - ctx->from_longitude) * progress;

  shumate_location_set_location (SHUMATE_LOCATION (priv->viewport), latitude, longitude);

  return G_SOURCE_CONTINUE;
}

422
static void
423
424
425
on_drag_gesture_drag_begin (ShumateMap     *self,
                            double          start_x,
                            double          start_y,
426
427
                            GtkGestureDrag *gesture)
{
428
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
429

430
  g_assert (SHUMATE_IS_MAP (self));
431

432
433
  cancel_deceleration (self);

James Westman's avatar
James Westman committed
434
435
436
437
438
439
440
  priv->drag_begin_x = start_x;
  priv->drag_begin_y = start_y;

  shumate_viewport_widget_coords_to_location (priv->viewport, GTK_WIDGET (self),
                                              start_x, start_y,
                                              &priv->gesture_begin_lat,
                                              &priv->gesture_begin_lon);
441
442
443
444
445

  gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "grabbing");
}

static void
446
447
448
on_drag_gesture_drag_update (ShumateMap     *self,
                             double          offset_x,
                             double          offset_y,
449
450
                             GtkGestureDrag *gesture)
{
451
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
452

James Westman's avatar
James Westman committed
453
454
455
456
457
  move_location_to_coords (self,
                           priv->gesture_begin_lat,
                           priv->gesture_begin_lon,
                           priv->drag_begin_x + offset_x,
                           priv->drag_begin_y + offset_y);
458
459
460
}

static void
461
462
463
on_drag_gesture_drag_end (ShumateMap     *self,
                          double          offset_x,
                          double          offset_y,
464
465
                          GtkGestureDrag *gesture)
{
466
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
467

468
  g_assert (SHUMATE_IS_MAP (self));
469

Corentin Noël's avatar
Corentin Noël committed
470
471
  gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "grab");

James Westman's avatar
James Westman committed
472
473
  priv->gesture_begin_lon = 0;
  priv->gesture_begin_lat = 0;
474
475
}

476
477
478
479
static void
view_swipe_cb (GtkGestureSwipe *swipe_gesture,
               double           velocity_x,
               double           velocity_y,
480
               ShumateMap      *self)
481
482
483
484
{
  start_deceleration (self, velocity_x, velocity_y);
}

485
static void
486
487
set_zoom_level (ShumateMap *self,
                double      zoom_level)
488
{
489
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
490
  ShumateMapSource *map_source;
James Westman's avatar
James Westman committed
491
  double lat, lon;
492
493
494
495
496

  g_object_freeze_notify (G_OBJECT (priv->viewport));

  map_source = shumate_viewport_get_reference_map_source (priv->viewport);
  if (map_source)
James Westman's avatar
James Westman committed
497
498
499
500
    shumate_viewport_widget_coords_to_location (priv->viewport,
                                                GTK_WIDGET (self),
                                                priv->current_x, priv->current_y,
                                                &lat, &lon);
501

502
  if (map_source)
James Westman's avatar
James Westman committed
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
    {
      g_autoptr(ShumateViewport) new_viewport = shumate_viewport_copy (priv->viewport);
      shumate_viewport_set_zoom_level (new_viewport, zoom_level);
      move_location_to_coords_calc (self, &lat, &lon, priv->current_x, priv->current_y, new_viewport);

      shumate_map_go_to_full_with_duration (self,
                                            lat,
                                            lon,
                                            zoom_level,
                                            priv->animate_zoom ? ZOOM_ANIMATION_MS : 0);

      if (priv->goto_context != NULL)
        priv->goto_context->zoom_animation = TRUE;
    }
  else
    shumate_viewport_set_zoom_level (priv->viewport, zoom_level);
James Westman's avatar
James Westman committed
519

520
  g_object_thaw_notify (G_OBJECT (priv->viewport));
521
522
523
}

static gboolean
524
525
526
on_scroll_controller_scroll (ShumateMap               *self,
                             double                    dx,
                             double                    dy,
527
528
                             GtkEventControllerScroll *controller)
{
529
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
530
531
  double zoom_level = shumate_viewport_get_zoom_level (priv->viewport);

James Westman's avatar
James Westman committed
532
533
534
  if (priv->goto_context != NULL && priv->goto_context->zoom_animation)
    zoom_level = priv->goto_context->to_zoom;

535
536
537
538
539
540
541
  if (dy < 0)
    zoom_level += 0.2;
  else if (dy > 0)
    zoom_level -= 0.2;

  /* snap to the nearest 1/5 of a zoom level */
  set_zoom_level (self, roundf (zoom_level * 5) / 5);
542

543
544
545
  return TRUE;
}

546
static void
547
on_zoom_gesture_begin (ShumateMap       *self,
548
                       GdkEventSequence *seq,
549
                       GtkGestureZoom   *zoom)
550
{
551
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
552
  double zoom_level = shumate_viewport_get_zoom_level (priv->viewport);
James Westman's avatar
James Westman committed
553
  double x, y;
554
555
556
557
558
559

  gtk_gesture_set_state (GTK_GESTURE (zoom), GTK_EVENT_SEQUENCE_CLAIMED);
  cancel_deceleration (self);

  priv->zoom_level_begin = zoom_level;

James Westman's avatar
James Westman committed
560
561
562
563
564
  gtk_gesture_get_bounding_box_center (GTK_GESTURE (zoom), &x, &y);
  shumate_viewport_widget_coords_to_location (priv->viewport, GTK_WIDGET (self),
                                              x, y,
                                              &priv->gesture_begin_lat,
                                              &priv->gesture_begin_lon);
565
566
567
}

static void
568
on_zoom_gesture_update (ShumateMap       *self,
569
                        GdkEventSequence *seq,
570
                        GtkGestureZoom   *zoom)
571
{
572
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
573
574
575
576
  double x, y;
  double scale = gtk_gesture_zoom_get_scale_delta (zoom);

  gtk_gesture_get_bounding_box_center (GTK_GESTURE (zoom), &x, &y);
James Westman's avatar
James Westman committed
577
578
579
  shumate_viewport_set_zoom_level (priv->viewport, priv->zoom_level_begin + log (scale) / G_LN2);
  move_location_to_coords (self, priv->gesture_begin_lat, priv->gesture_begin_lon, x, y);
}
580

James Westman's avatar
James Westman committed
581
582
583
584
585
586
587
static void
on_rotate_gesture_begin (ShumateMap *self,
                         GdkEventSequence *seq,
                         GtkGestureRotate *rotate)
{
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
  double rotation = shumate_viewport_get_rotation (priv->viewport);
588

James Westman's avatar
James Westman committed
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
  gtk_gesture_set_state (GTK_GESTURE (rotate), GTK_EVENT_SEQUENCE_CLAIMED);
  cancel_deceleration (self);

  priv->rotate_begin = rotation;
}

static void
on_rotate_gesture_update (ShumateMap *self,
                          GdkEventSequence *seq,
                          GtkGestureRotate *rotate)
{
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
  double rotation;
  double x, y;

  rotation = gtk_gesture_rotate_get_angle_delta (rotate) + priv->rotate_begin;
605

James Westman's avatar
James Westman committed
606
607
608
609
610
611
612
  /* snap to due north */
  if (fabs (fmod (rotation - 0.25, G_PI * 2)) < 0.5)
    rotation = 0.0;

  shumate_viewport_set_rotation (priv->viewport, rotation);
  gtk_gesture_get_bounding_box_center (GTK_GESTURE (rotate), &x, &y);
  move_location_to_coords (self, priv->gesture_begin_lat, priv->gesture_begin_lon, x, y);
613
614
}

615
static void
616
617
618
on_motion_controller_motion (ShumateMap               *self,
                             double                    x,
                             double                    y,
619
620
                             GtkEventControllerMotion *controller)
{
621
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
622
623
624
625
626

  priv->current_x = x;
  priv->current_y = y;
}

627
static void
628
629
630
631
shumate_map_get_property (GObject    *object,
                          guint       prop_id,
                          GValue     *value,
                          GParamSpec *pspec)
632
{
633
634
  ShumateMap *view = SHUMATE_MAP (object);
  ShumateMapPrivate *priv = shumate_map_get_instance_private (view);
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649

  switch (prop_id)
    {
    case PROP_ZOOM_ON_DOUBLE_CLICK:
      g_value_set_boolean (value, priv->zoom_on_double_click);
      break;

    case PROP_ANIMATE_ZOOM:
      g_value_set_boolean (value, priv->animate_zoom);
      break;

    case PROP_STATE:
      g_value_set_enum (value, priv->state);
      break;

650
651
    case PROP_GO_TO_DURATION:
      g_value_set_uint (value, priv->go_to_duration);
652
653
      break;

654
655
656
657
    case PROP_VIEWPORT:
      g_value_set_object (value, priv->viewport);
      break;

658
659
660
661
662
663
664
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}


static void
665
666
667
668
shumate_map_set_property (GObject      *object,
                          guint         prop_id,
                          const GValue *value,
                          GParamSpec   *pspec)
669
{
670
  ShumateMap *view = SHUMATE_MAP (object);
671
672
673
674

  switch (prop_id)
    {
    case PROP_ZOOM_ON_DOUBLE_CLICK:
675
      shumate_map_set_zoom_on_double_click (view, g_value_get_boolean (value));
676
677
678
      break;

    case PROP_ANIMATE_ZOOM:
679
      shumate_map_set_animate_zoom (view, g_value_get_boolean (value));
680
      break;
Marcus Lundblad's avatar
Marcus Lundblad committed
681

682
    case PROP_GO_TO_DURATION:
683
      shumate_map_set_go_to_duration (view, g_value_get_uint (value));
684
685
686
687
688
689
690
691
692
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    }
}


static void
693
shumate_map_dispose (GObject *object)
694
{
695
696
  ShumateMap *view = SHUMATE_MAP (object);
  ShumateMapPrivate *priv = shumate_map_get_instance_private (view);
697
  GtkWidget *child;
698
699

  if (priv->goto_context != NULL)
700
    shumate_map_stop_go_to (view);
701

702
703
704
  while ((child = gtk_widget_get_first_child (GTK_WIDGET (object))))
    gtk_widget_unparent (child);

705
  g_clear_object (&priv->viewport);
706

707
708
  g_clear_handle_id (&priv->zoom_timeout, g_source_remove);

709
  G_OBJECT_CLASS (shumate_map_parent_class)->dispose (object);
710
711
712
}

static void
713
shumate_map_class_init (ShumateMapClass *klass)
714
{
715
716
717
718
719
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
  object_class->dispose = shumate_map_dispose;
  object_class->get_property = shumate_map_get_property;
  object_class->set_property = shumate_map_set_property;
720
721

  /**
722
   * ShumateMap:zoom-on-double-click:
723
724
725
   *
   * Should the view zoom in and recenter when the user double click on the map.
   */
726
727
728
729
730
731
  obj_properties[PROP_ZOOM_ON_DOUBLE_CLICK] =
    g_param_spec_boolean ("zoom-on-double-click",
                          "Zoom in on double click",
                          "Zoom in and recenter on double click on the map",
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
732
733

  /**
734
   * ShumateMap:animate-zoom:
735
736
737
   *
   * Animate zoom change when zooming in/out.
   */
738
739
740
741
742
743
  obj_properties[PROP_ANIMATE_ZOOM] =
    g_param_spec_boolean ("animate-zoom",
                          "Animate zoom level change",
                          "Animate zoom change when zooming in/out",
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
744
745

  /**
746
   * ShumateMap:state:
747
748
749
750
   *
   * The view's global state. Useful to inform using if the view is busy loading
   * tiles or not.
   */
751
752
753
754
755
756
757
  obj_properties[PROP_STATE] =
    g_param_spec_enum ("state",
                       "View's state",
                       "View's global state",
                       SHUMATE_TYPE_STATE,
                       SHUMATE_STATE_NONE,
                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
758
759

  /**
760
   * ShumateMap:go-to-duration:
761
   *
762
   * The duration of an animation when going to a location, in milliseconds.
763
764
   * A value of 0 means that the duration is calculated automatically for you.
   *
765
   * Please note that animation of #shumate_map_ensure_visible also
766
   * involves a 'go-to' animation.
767
768
   *
   */
769
770
  obj_properties[PROP_GO_TO_DURATION] =
    g_param_spec_uint ("go-to-duration",
771
772
773
774
                       "Go to animation duration",
                       "The duration of an animation when going to a location",
                       0, G_MAXUINT, 0,
                       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
775

776
777
778
779
780
781
782
783
784
785
786
787
788
  /**
   * ShumateMap:viewport:
   *
   * The viewport, which contains information about the center, rotation, zoom,
   * etc. of the map.
   */
  obj_properties[PROP_VIEWPORT] =
    g_param_spec_object ("viewport",
                         "Viewport",
                         "Viewport",
                         SHUMATE_TYPE_VIEWPORT,
                         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

789
790
791
  g_object_class_install_properties (object_class,
                                     N_PROPERTIES,
                                     obj_properties);
792
793

  /**
794
   * ShumateMap::animation-completed:
795
   *
796
   * The #ShumateMap::animation-completed signal is emitted when any animation in the view
797
798
799
800
801
   * ends.  This is a detailed signal.  For example, if you want to be signaled
   * only for go-to animation, you should connect to
   * "animation-completed::go-to". And for zoom, connect to "animation-completed::zoom".
   */
  signals[ANIMATION_COMPLETED] =
Marcus Lundblad's avatar
Marcus Lundblad committed
802
    g_signal_new ("animation-completed",
803
804
805
806
807
808
                  G_OBJECT_CLASS_TYPE (object_class),
                  G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
                  0, NULL, NULL,
                  g_cclosure_marshal_VOID__OBJECT,
                  G_TYPE_NONE,
                  0);
809

810
  gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BIN_LAYOUT);
811
  gtk_widget_class_set_css_name (widget_class, "map-view");
812
813

  go_to_quark = g_quark_from_static_string ("go-to");
814
815
}

816
static void
817
shumate_map_init (ShumateMap *self)
818
{
819
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
820
821
  GtkGesture *drag_gesture;
  GtkEventController *scroll_controller;
822
  GtkEventController *motion_controller;
823
  GtkEventController *key_controller;
824
  GtkGesture *swipe_gesture;
825
  GtkGesture *zoom_gesture;
James Westman's avatar
James Westman committed
826
  GtkGesture *rotate_gesture;
Marcus Lundblad's avatar
Marcus Lundblad committed
827

828
829
830
831
832
  priv->viewport = shumate_viewport_new ();
  priv->zoom_on_double_click = TRUE;
  priv->animate_zoom = TRUE;
  priv->state = SHUMATE_STATE_NONE;
  priv->goto_context = NULL;
833
  priv->go_to_duration = 0;
834

835
  gtk_widget_set_cursor_from_name (GTK_WIDGET (self), "grab");
836

837
  drag_gesture = gtk_gesture_drag_new ();
838
839
840
841
  g_signal_connect_swapped (drag_gesture, "drag-begin", G_CALLBACK (on_drag_gesture_drag_begin), self);
  g_signal_connect_swapped (drag_gesture, "drag-update", G_CALLBACK (on_drag_gesture_drag_update), self);
  g_signal_connect_swapped (drag_gesture, "drag-end", G_CALLBACK (on_drag_gesture_drag_end), self);
  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drag_gesture));
842

843
  swipe_gesture = gtk_gesture_swipe_new ();
844
845
  g_signal_connect (swipe_gesture, "swipe", G_CALLBACK (view_swipe_cb), self);
  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (swipe_gesture));
846

847
  scroll_controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_VERTICAL|GTK_EVENT_CONTROLLER_SCROLL_DISCRETE);
848
849
  g_signal_connect_swapped (scroll_controller, "scroll", G_CALLBACK (on_scroll_controller_scroll), self);
  gtk_widget_add_controller (GTK_WIDGET (self), scroll_controller);
850

851
  zoom_gesture = gtk_gesture_zoom_new ();
852
853
854
  g_signal_connect_swapped (zoom_gesture, "begin", G_CALLBACK (on_zoom_gesture_begin), self);
  g_signal_connect_swapped (zoom_gesture, "update", G_CALLBACK (on_zoom_gesture_update), self);
  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (zoom_gesture));
855

856
  motion_controller = gtk_event_controller_motion_new ();
857
858
  g_signal_connect_swapped (motion_controller, "motion", G_CALLBACK (on_motion_controller_motion), self);
  gtk_widget_add_controller (GTK_WIDGET (self), motion_controller);
859

James Westman's avatar
James Westman committed
860
861
862
863
864
865
866
  rotate_gesture = gtk_gesture_rotate_new ();
  g_signal_connect_swapped (rotate_gesture, "begin", G_CALLBACK (on_rotate_gesture_begin), self);
  g_signal_connect_swapped (rotate_gesture, "update", G_CALLBACK (on_rotate_gesture_update), self);
  gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (rotate_gesture));

  gtk_gesture_group (zoom_gesture, rotate_gesture);

867
868
869
  key_controller = gtk_event_controller_key_new ();
  gtk_widget_add_controller (GTK_WIDGET (self), key_controller);

870
  gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
871
872
873
}

/**
874
 * shumate_map_new:
875
 *
876
 * Creates an instance of #ShumateMap.
877
 *
878
 * Returns: a new #ShumateMap ready to be used as a #GtkWidget.
879
 */
880
881
ShumateMap *
shumate_map_new (void)
882
{
883
  return g_object_new (SHUMATE_TYPE_MAP, NULL);
884
885
886
}


887
888
ShumateMap *
shumate_map_new_simple (void)
889
{
890
  ShumateMap *view = g_object_new (SHUMATE_TYPE_MAP, NULL);
891
  g_autoptr(ShumateMapSourceRegistry) registry = NULL;
892
893
894
  ShumateMapSource *source;
  ShumateMapLayer *map_layer;
  ShumateViewport *viewport;
895
896

  viewport = shumate_map_get_viewport (view);
897
898
  registry = shumate_map_source_registry_new_with_defaults ();
  source = shumate_map_source_registry_get_by_id (registry, SHUMATE_MAP_SOURCE_OSM_MAPNIK);
899
900
  shumate_viewport_set_reference_map_source (viewport, source);
  map_layer = shumate_map_layer_new (source, viewport);
901
  shumate_map_add_layer (view, SHUMATE_LAYER (map_layer));
902

903
  return view;
904
905
906
}

/**
907
908
 * shumate_map_get_viewport:
 * @self: a #ShumateMap
909
 *
910
 * Get the #ShumateViewport used by this view.
911
 *
912
 * Returns: (transfer none): the #ShumateViewport
913
 */
914
ShumateViewport *
915
shumate_map_get_viewport (ShumateMap *self)
916
{
917
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
918

919
  g_return_val_if_fail (SHUMATE_IS_MAP (self), NULL);
920

921
  return priv->viewport;
922
923
924
}

/**
925
926
 * shumate_map_center_on:
 * @self: a #ShumateMap
927
928
 * @latitude: the longitude to center the map at
 * @longitude: the longitude to center the map at
929
 *
930
 * Centers the map on these coordinates.
931
932
 */
void
933
934
935
shumate_map_center_on (ShumateMap *self,
                       double      latitude,
                       double      longitude)
936
{
937
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
938

939
  g_return_if_fail (SHUMATE_IS_MAP (self));
Marcus Lundblad's avatar
Marcus Lundblad committed
940

941
  shumate_location_set_location (SHUMATE_LOCATION (priv->viewport), latitude, longitude);
942
943
944
}

/**
945
946
 * shumate_map_stop_go_to:
 * @self: a #ShumateMap
947
 *
948
949
 * Stop the go to animation.  The view will stay where it was when the
 * animation was stopped.
950
951
 */
void
952
shumate_map_stop_go_to (ShumateMap *self)
953
{
954
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
955

956
  g_return_if_fail (SHUMATE_IS_MAP (self));
957

958
  if (priv->goto_context == NULL)
959
960
    return;

961
  gtk_widget_remove_tick_callback (GTK_WIDGET (self), priv->goto_context->tick_id);
962
  g_clear_pointer (&priv->goto_context, g_free);
963

964
  g_signal_emit (self, signals[ANIMATION_COMPLETED], go_to_quark, NULL);
965
966
967
968
}


/**
969
970
 * shumate_map_go_to:
 * @self: a #ShumateMap
971
972
 * @latitude: the longitude to center the map at
 * @longitude: the longitude to center the map at
973
 *
974
975
 * Move from the current position to these coordinates. All tiles in the
 * intermediate view WILL be loaded!
976
977
 */
void
978
979
980
shumate_map_go_to (ShumateMap *self,
                   double      latitude,
                   double      longitude)
James Westman's avatar
James Westman committed
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
  ShumateMapPrivate *priv = shumate_map_get_instance_private (self);
  double zoom_level;

  g_return_if_fail (SHUMATE_IS_MAP (self));
  g_return_if_fail (latitude >= SHUMATE_MIN_LATITUDE && latitude <= SHUMATE_MAX_LATITUDE);
  g_return_if_fail (longitude >= SHUMATE_MIN_LONGITUDE && longitude <= SHUMATE_MAX_LONGITUDE);

  zoom_level = shumate_viewport_get_zoom_level (priv->viewport);

  shumate_map_go_to_full (self, latitude, longitude, zoom_level);
}


/**
 * shumate_map_go_to_full:
 * @self: a #ShumateMap
 * @latitude: the longitude to center the map at
 * @longitude: the longitude to center the map at
 * @zoom_level: the zoom level to end at
For faster browsing, not all history is shown. View entire blame