LCOV - code coverage report
Current view: top level - shumate - shumate-map.c (source / functions) Hit Total Coverage
Test: Code coverage Lines: 105 564 18.6 %
Date: 2024-05-11 21:41:31 Functions: 11 56 19.6 %
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 25 250 10.0 %

           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                 :            : }

Generated by: LCOV version 1.14