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 Collabora Ltd. <http://www.collabora.co.uk/>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20 
21 /**
22  * SECTION:champlain-view
23  * @short_description: A #ClutterActor to display maps
24  *
25  * The #ChamplainView is a ClutterActor to display maps.  It supports two modes
26  * of scrolling:
27  * <itemizedlist>
28  *   <listitem><para>Push: the normal behavior where the maps don't move
29  *   after the user stopped scrolling;</para></listitem>
30  *   <listitem><para>Kinetic: the iPhone-like behavior where the maps
31  *   decelerate after the user stopped scrolling.</para></listitem>
32  * </itemizedlist>
33  *
34  * You can use the same #ChamplainView to display many types of maps.  In
35  * Champlain they are called map sources.  You can change the #map-source
36  * property at anytime to replace the current displayed map.
37  *
38  * The maps are downloaded from Internet from open maps sources (like
39  * <ulink role="online-location"
40  * url="https://www.openstreetmap.org">OpenStreetMap</ulink>).  Maps are divided
41  * in tiles for each zoom level.  When a tile is requested, #ChamplainView will
42  * first check if it is in cache (in the user's cache dir under champlain). If
43  * an error occurs during download, an error tile will be displayed.
44  *
45  * The button-press-event and button-release-event signals are emitted each
46  * time a mouse button is pressed and released on the @view.
47  */
48 
49 #include "config.h"
50 
51 #include "champlain-view.h"
52 
53 #define DEBUG_FLAG CHAMPLAIN_DEBUG_VIEW
54 #include "champlain-debug.h"
55 
56 #include "champlain.h"
57 #include "champlain-defines.h"
58 #include "champlain-enum-types.h"
59 #include "champlain-map-source.h"
60 #include "champlain-map-source-factory.h"
61 #include "champlain-private.h"
62 #include "champlain-tile.h"
63 #include "champlain-license.h"
64 
65 #include <clutter/clutter.h>
66 #include <glib.h>
67 #include <glib-object.h>
68 #include <math.h>
69 #include <champlain-kinetic-scroll-view.h>
70 #include <champlain-viewport.h>
71 #include <champlain-adjustment.h>
72 
73 /* #define VIEW_LOG */
74 #ifdef VIEW_LOG
75 #define DEBUG_LOG() g_print ("%s\n", __FUNCTION__);
76 #else
77 #define DEBUG_LOG()
78 #endif
79 
80 enum
81 {
82   /* normal signals */
83   ANIMATION_COMPLETED,
84   LAYER_RELOCATED,
85   LAST_SIGNAL
86 };
87 
88 enum
89 {
90   PROP_0,
91   PROP_LONGITUDE,
92   PROP_LATITUDE,
93   PROP_ZOOM_LEVEL,
94   PROP_MIN_ZOOM_LEVEL,
95   PROP_MAX_ZOOM_LEVEL,
96   PROP_MAP_SOURCE,
97   PROP_DECELERATION,
98   PROP_KINETIC_MODE,
99   PROP_KEEP_CENTER_ON_RESIZE,
100   PROP_ZOOM_ON_DOUBLE_CLICK,
101   PROP_ANIMATE_ZOOM,
102   PROP_STATE,
103   PROP_BACKGROUND_PATTERN,
104   PROP_GOTO_ANIMATION_MODE,
105   PROP_GOTO_ANIMATION_DURATION,
106   PROP_WORLD,
107   PROP_HORIZONTAL_WRAP
108 };
109 
110 #define PADDING 10
111 static guint signals[LAST_SIGNAL] = { 0, };
112 
113 #define ZOOM_LEVEL_OUT_OF_RANGE(priv, level) \
114   (level < priv->min_zoom_level || \
115            level > priv->max_zoom_level || \
116    level < champlain_map_source_get_min_zoom_level (priv->map_source) || \
117            level > champlain_map_source_get_max_zoom_level (priv->map_source))
118 
119 /* Between state values for go_to */
120 typedef struct
121 {
122   ChamplainView *view;
123   ClutterTimeline *timeline;
124   gdouble to_latitude;
125   gdouble to_longitude;
126   gdouble from_latitude;
127   gdouble from_longitude;
128 } GoToContext;
129 
130 
131 typedef struct
132 {
133   ChamplainView *view;
134   ChamplainMapSource *map_source;
135   gint x;
136   gint y;
137   gint zoom_level;
138   gint size;
139 } FillTileCallbackData;
140 
141 
142 struct _ChamplainViewPrivate
143 {
144                                 /* ChamplainView */
145   ClutterActor *kinetic_scroll;     /* kinetic_scroll */
146   ClutterActor *viewport;               /* viewport */
147   ClutterActor *viewport_container;         /* viewport_container */
148   ClutterActor *background_layer;               /* background_layer */
149   ClutterActor *zoom_layer;                     /* zoom_layer */
150   ClutterActor *map_layer;                      /* map_layer */
151                                                 /* map_layer clones */
152   ClutterActor *user_layers;                    /* user_layers and clones */
153   ClutterActor *zoom_overlay_actor; /* zoom_overlay_actor */
154   ClutterActor *license_actor;      /* license_actor */
155 
156   ClutterContent *background_content;
157 
158   gboolean hwrap;
159   /* There are num_right_clones clones on the right, and one extra on the left */
160   gint num_right_clones;
161   GList *map_clones;
162   /* There are num_right_clones + 2 user layer slots, overlayed on the map clones.
163    * Initially, the first slot contains the left clone, the second slot
164    * contains the real user layer, and the rest contain the right clones.
165    * Whenever the cursor enters a clone slot, its content
166    * is swapped with the real one so as to ensure reactiveness to events.
167    */
168   GList *user_layer_slots;
169 
170   gdouble viewport_x;
171   gdouble viewport_y;
172   gint viewport_width;
173   gint viewport_height;
174 
175   ChamplainMapSource *map_source; /* Current map tile source */
176   GList *overlay_sources;
177 
178   guint zoom_level; /* Holds the current zoom level number */
179   guint min_zoom_level; /* Lowest allowed zoom level */
180   guint max_zoom_level; /* Highest allowed zoom level */
181 
182   /* Represents the (lat, lon) at the center of the viewport */
183   gdouble longitude;
184   gdouble latitude;
185   gboolean location_updated;
186 
187   gint bg_offset_x;
188   gint bg_offset_y;
189 
190   gboolean keep_center_on_resize;
191   gboolean zoom_on_double_click;
192   gboolean animate_zoom;
193 
194   gboolean kinetic_mode;
195 
196   ChamplainState state; /* View's global state */
197 
198   /* champlain_view_go_to's context, kept for stop_go_to */
199   GoToContext *goto_context;
200 
201   gint tiles_loading;
202 
203   guint redraw_timeout;
204   guint zoom_timeout;
205 
206   ClutterAnimationMode goto_mode;
207   guint goto_duration;
208 
209   gboolean animating_zoom;
210   guint anim_start_zoom_level;
211   gdouble zoom_actor_viewport_x;
212   gdouble zoom_actor_viewport_y;
213   guint zoom_actor_timeout;
214 
215   GHashTable *tile_map;
216 
217   gint tile_x_first;
218   gint tile_y_first;
219   gint tile_x_last;
220   gint tile_y_last;
221 
222   /* Zoom gesture */
223   ClutterGestureAction *zoom_gesture;
224   guint initial_gesture_zoom;
225   gdouble focus_lat;
226   gdouble focus_lon;
227   gboolean zoom_started;
228   gdouble accumulated_scroll_dy;
229 
230   ChamplainBoundingBox *world_bbox;
231 
232   GHashTable *visible_tiles;
233 };
234 
235 G_DEFINE_TYPE_WITH_PRIVATE (ChamplainView, champlain_view, CLUTTER_TYPE_ACTOR)
236 
237 static void exclusive_destroy_clone (ClutterActor *clone);
238 static void update_clones (ChamplainView *view);
239 static gboolean scroll_event (ClutterActor *actor,
240     ClutterScrollEvent *event,
241     ChamplainView *view);
242 static void resize_viewport (ChamplainView *view);
243 static void champlain_view_get_property (GObject *object,
244     guint prop_id,
245     GValue *value,
246     GParamSpec *pspec);
247 static void champlain_view_set_property (GObject *object,
248     guint prop_id,
249     const GValue *value,
250     GParamSpec *pspec);
251 static void champlain_view_dispose (GObject *object);
252 static void champlain_view_class_init (ChamplainViewClass *champlainViewClass);
253 static void champlain_view_init (ChamplainView *view);
254 static void viewport_pos_changed_cb (GObject *gobject,
255     GParamSpec *arg1,
256     ChamplainView *view);
257 static gboolean kinetic_scroll_button_press_cb (ClutterActor *actor,
258     ClutterButtonEvent *event,
259     ChamplainView *view);
260 static ClutterActor *sample_user_layer_at_pos (ChamplainView *view,
261     gfloat x,
262     gfloat y);
263 static void swap_user_layer_slots (ChamplainView *view,
264     gint original_index,
265     gint clone_index);
266 static gboolean viewport_motion_cb (ClutterActor *actor,
267     ClutterMotionEvent *event,
268     ChamplainView *view);
269 static gboolean viewport_press_cb (ClutterActor *actor,
270     ClutterButtonEvent *event,
271     ChamplainView *view);
272 static void load_visible_tiles (ChamplainView *view,
273     gboolean relocate);
274 static gboolean view_set_zoom_level_at (ChamplainView *view,
275     guint zoom_level,
276     gboolean use_event_coord,
277     gint x,
278     gint y);
279 static void tile_state_notify (ChamplainTile *tile,
280     G_GNUC_UNUSED GParamSpec *pspec,
281     ChamplainView *view);
282 static gboolean kinetic_scroll_key_press_cb (ChamplainView *view,
283     ClutterKeyEvent *event);
284 static void champlain_view_go_to_with_duration (ChamplainView *view,
285     gdouble latitude,
286     gdouble longitude,
287     guint duration);
288 static gboolean redraw_timeout_cb(gpointer view);
289 static void remove_all_tiles (ChamplainView *view);
290 static void get_x_y_for_zoom_level (ChamplainView *view,
291     guint zoom_level,
292     gint offset_x,
293     gint offset_y,
294     gdouble *new_x,
295     gdouble *new_y);
296 static ChamplainBoundingBox *get_bounding_box (ChamplainView *view,
297     guint zoom_level,
298     gdouble x,
299     gdouble y);
300 static void get_tile_bounds (ChamplainView *view,
301     guint *min_x,
302     guint *min_y,
303     guint *max_x,
304     guint *max_y);
305 static gboolean tile_in_tile_table (ChamplainView *view,
306     GHashTable *table,
307     gint tile_x,
308     gint tile_y);
309 
310 static gdouble
x_to_wrap_x(gdouble x,gdouble width)311 x_to_wrap_x (gdouble x, gdouble width)
312 {
313   if (x < 0)
314     x += ((gint)-x / (gint)width + 1) * width;
315 
316   return fmod (x, width);
317 }
318 
319 
320 static gint
get_map_width(ChamplainView * view)321 get_map_width (ChamplainView *view)
322 {
323   gint size, cols;
324   ChamplainViewPrivate *priv = view->priv;
325 
326   size = champlain_map_source_get_tile_size (priv->map_source);
327   cols = champlain_map_source_get_column_count (priv->map_source,
328                                                 priv->zoom_level);
329   return size * cols;
330 }
331 
332 
333 static gdouble
get_longitude(ChamplainView * view,guint zoom_level,gdouble x)334 get_longitude (ChamplainView *view,
335     guint zoom_level,
336     gdouble x)
337 {
338   ChamplainViewPrivate *priv = view->priv;
339 
340   DEBUG_LOG ()
341 
342   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
343 
344   if (priv->hwrap)
345     x = x_to_wrap_x (x, get_map_width (view));
346 
347   return champlain_map_source_get_longitude (priv->map_source,
348         zoom_level,
349         x);
350 }
351 
352 
353 static void
update_coords(ChamplainView * view,gdouble x,gdouble y,gboolean notify)354 update_coords (ChamplainView *view,
355     gdouble x,
356     gdouble y,
357     gboolean notify)
358 {
359   DEBUG_LOG ()
360 
361   ChamplainViewPrivate *priv = view->priv;
362 
363   priv->viewport_x = x;
364   priv->viewport_y = y;
365   priv->longitude = get_longitude (view,
366         priv->zoom_level,
367         x + priv->viewport_width / 2.0);
368   priv->latitude = champlain_map_source_get_latitude (priv->map_source,
369         priv->zoom_level,
370         y + priv->viewport_height / 2.0);
371 
372   if (notify)
373     {
374       g_object_notify (G_OBJECT (view), "longitude");
375       g_object_notify (G_OBJECT (view), "latitude");
376     }
377 }
378 
379 
380 static void
position_viewport(ChamplainView * view,gdouble x,gdouble y)381 position_viewport (ChamplainView *view,
382     gdouble x,
383     gdouble y)
384 {
385   DEBUG_LOG ()
386 
387   ChamplainViewPrivate *priv = view->priv;
388   gint old_bg_offset_x = 0, old_bg_offset_y = 0;
389   gfloat bg_width, bg_height;
390 
391   /* remember the relative offset of the background tile */
392   if (priv->background_content)
393     {
394       clutter_content_get_preferred_size (priv->background_content, &bg_width, &bg_height);
395       old_bg_offset_x = ((gint)priv->viewport_x + priv->bg_offset_x) % (gint)bg_width;
396       old_bg_offset_y = ((gint)priv->viewport_y + priv->bg_offset_y) % (gint)bg_height;
397     }
398 
399   /* notify about latitude and longitude change only after the viewport position is set */
400   g_object_freeze_notify (G_OBJECT (view));
401 
402   update_coords (view, x, y, TRUE);
403 
404   /* compute the new relative offset of the background tile */
405   if (priv->background_content)
406     {
407       gint new_bg_offset_x = (gint)priv->viewport_x % (gint)bg_width;
408       gint new_bg_offset_y = (gint)priv->viewport_y % (gint)bg_height;
409       priv->bg_offset_x = (old_bg_offset_x - new_bg_offset_x) % (gint)bg_width;
410       priv->bg_offset_y = (old_bg_offset_y - new_bg_offset_y) % (gint)bg_height;
411       if (priv->bg_offset_x < 0)
412         priv->bg_offset_x += bg_width;
413       if (priv->bg_offset_y < 0)
414         priv->bg_offset_y += bg_height;
415     }
416 
417   /* we know about the change already - don't send the notifications again */
418   g_signal_handlers_block_by_func (priv->viewport, G_CALLBACK (viewport_pos_changed_cb), view);
419   champlain_viewport_set_origin (CHAMPLAIN_VIEWPORT (priv->viewport),
420       (gint)priv->viewport_x,
421       (gint)priv->viewport_y);
422   g_signal_handlers_unblock_by_func (priv->viewport, G_CALLBACK (viewport_pos_changed_cb), view);
423 
424   g_object_thaw_notify (G_OBJECT (view));
425 }
426 
427 
428 static void
view_relocated_cb(G_GNUC_UNUSED ChamplainViewport * viewport,ChamplainView * view)429 view_relocated_cb (G_GNUC_UNUSED ChamplainViewport *viewport,
430     ChamplainView *view)
431 {
432   ChamplainViewPrivate *priv = view->priv;
433   gint anchor_x, anchor_y, new_width, new_height;
434   gint tile_size, column_count, row_count;
435 
436   clutter_actor_destroy_all_children (priv->zoom_layer);
437   load_visible_tiles (view, TRUE);
438   g_signal_emit_by_name (view, "layer-relocated", NULL);
439 
440   /* Clutter clones need their source actor to have an explicitly set size to display properly */
441   tile_size = champlain_map_source_get_tile_size (priv->map_source);
442   column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
443   row_count = champlain_map_source_get_row_count (priv->map_source, priv->zoom_level);
444   champlain_viewport_get_anchor (CHAMPLAIN_VIEWPORT (priv->viewport), &anchor_x, &anchor_y);
445 
446   /* The area containing tiles in the map layer is actually column_count * tile_size wide (same
447    * for height), but the viewport anchor acts as an offset for the tile actors, causing the map
448    * layer to contain some empty space as well.
449    */
450   new_width = column_count * tile_size + anchor_x;
451   new_height = row_count * tile_size + anchor_y;
452 
453   clutter_actor_set_size (priv->map_layer, new_width, new_height);
454 }
455 
456 
457 static void
panning_completed(G_GNUC_UNUSED ChamplainKineticScrollView * scroll,ChamplainView * view)458 panning_completed (G_GNUC_UNUSED ChamplainKineticScrollView *scroll,
459     ChamplainView *view)
460 {
461   DEBUG_LOG ()
462 
463   ChamplainViewPrivate *priv = view->priv;
464   gdouble x, y;
465 
466   if (priv->redraw_timeout != 0)
467     {
468       g_source_remove (priv->redraw_timeout);
469       priv->redraw_timeout = 0;
470     }
471 
472   champlain_viewport_get_origin (CHAMPLAIN_VIEWPORT (priv->viewport), &x, &y);
473 
474   update_coords (view, x, y, TRUE);
475   load_visible_tiles (view, FALSE);
476 }
477 
478 
479 static gboolean
zoom_timeout_cb(gpointer data)480 zoom_timeout_cb (gpointer data)
481 {
482   DEBUG_LOG ()
483 
484   ChamplainView *view = data;
485   ChamplainViewPrivate *priv = view->priv;
486 
487   priv->accumulated_scroll_dy = 0;
488   priv->zoom_timeout = 0;
489 
490   return FALSE;
491 }
492 
493 
494 static gboolean
scroll_event(G_GNUC_UNUSED ClutterActor * actor,ClutterScrollEvent * event,ChamplainView * view)495 scroll_event (G_GNUC_UNUSED ClutterActor *actor,
496     ClutterScrollEvent *event,
497     ChamplainView *view)
498 {
499   DEBUG_LOG ()
500 
501   ChamplainViewPrivate *priv = view->priv;
502 
503   guint zoom_level = priv->zoom_level;
504 
505   if (event->direction == CLUTTER_SCROLL_UP)
506     zoom_level = priv->zoom_level + 1;
507   else if (event->direction == CLUTTER_SCROLL_DOWN)
508     zoom_level = priv->zoom_level - 1;
509   else if (event->direction == CLUTTER_SCROLL_SMOOTH)
510     {
511       gdouble dx, dy;
512       gint steps;
513 
514       clutter_event_get_scroll_delta ((ClutterEvent *)event, &dx, &dy);
515 
516       priv->accumulated_scroll_dy += dy;
517       /* add some small value to avoid missing step for values like 0.999999 */
518       if (dy > 0)
519         steps = (int) (priv->accumulated_scroll_dy + 0.01);
520       else
521         steps = (int) (priv->accumulated_scroll_dy - 0.01);
522       zoom_level = priv->zoom_level - steps;
523       priv->accumulated_scroll_dy -= steps;
524 
525       if (priv->zoom_timeout != 0)
526         g_source_remove (priv->zoom_timeout);
527       priv->zoom_timeout = g_timeout_add (1000, zoom_timeout_cb, view);
528     }
529 
530   return view_set_zoom_level_at (view, zoom_level, TRUE, event->x, event->y);
531 }
532 
533 
534 static void
resize_viewport(ChamplainView * view)535 resize_viewport (ChamplainView *view)
536 {
537   DEBUG_LOG ()
538 
539   gdouble lower_x = 0;
540   gdouble lower_y = 0;
541   gdouble upper_x = G_MAXINT16;
542   gdouble upper_y = G_MAXINT16;
543   ChamplainAdjustment *hadjust, *vadjust;
544   guint min_x, min_y, max_x, max_y;
545 
546   ChamplainViewPrivate *priv = view->priv;
547 
548   champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadjust,
549       &vadjust);
550 
551   get_tile_bounds (view, &min_x, &min_y, &max_x, &max_y);
552   gint x_last = max_x * champlain_map_source_get_tile_size (priv->map_source);
553   gint y_last = max_y * champlain_map_source_get_tile_size (priv->map_source);
554   gint x_first = min_x * champlain_map_source_get_tile_size (priv->map_source);
555   gint y_first = min_y * champlain_map_source_get_tile_size (priv->map_source);
556 
557   /* Location of viewport with respect to the first tile:
558    *
559    * - for large maps (higher zoom levels) we allow the map to end in the middle
560    *   of the viewport; that is, one half of the viewport is positioned before
561    *   the first tile
562    * - for small maps (e.g. zoom level 0) we allow half of the map to go outside
563    *   the viewport; that is, whole viewport except one half of the map is
564    *   positioned before the first tile
565    *
566    * The first and the second element of the MIN() below corresponds to the
567    * first and the second case above. */
568   lower_x = MIN (x_first - priv->viewport_width / 2,
569                  (x_first - priv->viewport_width) + (x_last - x_first) / 2);
570 
571   lower_y = MIN (y_first - priv->viewport_height / 2,
572                  (y_first - priv->viewport_height) + (y_last - y_first) / 2);
573 
574   if (priv->hwrap)
575     upper_x = MAX (x_last - x_first + priv->viewport_width / 2, priv->viewport_width + (x_last - x_first) / 2);
576   else
577     upper_x = MAX (x_last - priv->viewport_width / 2, (x_last - x_first) / 2);
578   upper_y = MAX (y_last - priv->viewport_height / 2, (y_last - y_first)/ 2);
579 
580   /* we don't want to get notified about the position change now */
581   g_signal_handlers_block_by_func (priv->viewport, G_CALLBACK (viewport_pos_changed_cb), view);
582   champlain_adjustment_set_values (hadjust, champlain_adjustment_get_value (hadjust), lower_x, upper_x, 1.0);
583   champlain_adjustment_set_values (vadjust, champlain_adjustment_get_value (vadjust), lower_y, upper_y, 1.0);
584   g_signal_handlers_unblock_by_func (priv->viewport, G_CALLBACK (viewport_pos_changed_cb), view);
585 }
586 
587 static void
champlain_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)588 champlain_view_get_property (GObject *object,
589     guint prop_id,
590     GValue *value,
591     GParamSpec *pspec)
592 {
593   DEBUG_LOG ()
594 
595   ChamplainView *view = CHAMPLAIN_VIEW (object);
596   ChamplainViewPrivate *priv = view->priv;
597 
598   switch (prop_id)
599     {
600     case PROP_LONGITUDE:
601       g_value_set_double (value,
602           CLAMP (priv->longitude, priv->world_bbox->left, priv->world_bbox->right));
603       break;
604 
605     case PROP_LATITUDE:
606       g_value_set_double (value,
607           CLAMP (priv->latitude, priv->world_bbox->bottom, priv->world_bbox->top));
608       break;
609 
610     case PROP_ZOOM_LEVEL:
611       g_value_set_uint (value, priv->zoom_level);
612       break;
613 
614     case PROP_MIN_ZOOM_LEVEL:
615       g_value_set_uint (value, priv->min_zoom_level);
616       break;
617 
618     case PROP_MAX_ZOOM_LEVEL:
619       g_value_set_uint (value, priv->max_zoom_level);
620       break;
621 
622     case PROP_MAP_SOURCE:
623       g_value_set_object (value, priv->map_source);
624       break;
625 
626     case PROP_KINETIC_MODE:
627       g_value_set_boolean (value, priv->kinetic_mode);
628       break;
629 
630     case PROP_DECELERATION:
631       {
632         gdouble decel = 0.0;
633         g_object_get (priv->kinetic_scroll, "deceleration", &decel, NULL);
634         g_value_set_double (value, decel);
635         break;
636       }
637 
638     case PROP_KEEP_CENTER_ON_RESIZE:
639       g_value_set_boolean (value, priv->keep_center_on_resize);
640       break;
641 
642     case PROP_ZOOM_ON_DOUBLE_CLICK:
643       g_value_set_boolean (value, priv->zoom_on_double_click);
644       break;
645 
646     case PROP_ANIMATE_ZOOM:
647       g_value_set_boolean (value, priv->animate_zoom);
648       break;
649 
650     case PROP_STATE:
651       g_value_set_enum (value, priv->state);
652       break;
653 
654     case PROP_BACKGROUND_PATTERN:
655       g_value_set_object (value, priv->background_content);
656       break;
657 
658     case PROP_GOTO_ANIMATION_MODE:
659       g_value_set_enum (value, priv->goto_mode);
660       break;
661 
662     case PROP_GOTO_ANIMATION_DURATION:
663       g_value_set_uint (value, priv->goto_duration);
664       break;
665 
666     case PROP_WORLD:
667       g_value_set_boxed (value, priv->world_bbox);
668       break;
669 
670     case PROP_HORIZONTAL_WRAP:
671       g_value_set_boolean (value, champlain_view_get_horizontal_wrap (view));
672       break;
673 
674     default:
675       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
676     }
677 }
678 
679 
680 static void
champlain_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)681 champlain_view_set_property (GObject *object,
682     guint prop_id,
683     const GValue *value,
684     GParamSpec *pspec)
685 {
686   DEBUG_LOG ()
687 
688   ChamplainView *view = CHAMPLAIN_VIEW (object);
689   ChamplainViewPrivate *priv = view->priv;
690 
691   switch (prop_id)
692     {
693     case PROP_LONGITUDE:
694       champlain_view_center_on (view, priv->latitude,
695           g_value_get_double (value));
696       break;
697 
698     case PROP_LATITUDE:
699       champlain_view_center_on (view, g_value_get_double (value),
700           priv->longitude);
701       break;
702 
703     case PROP_ZOOM_LEVEL:
704       champlain_view_set_zoom_level (view, g_value_get_uint (value));
705       break;
706 
707     case PROP_MIN_ZOOM_LEVEL:
708       champlain_view_set_min_zoom_level (view, g_value_get_uint (value));
709       break;
710 
711     case PROP_MAX_ZOOM_LEVEL:
712       champlain_view_set_max_zoom_level (view, g_value_get_uint (value));
713       break;
714 
715     case PROP_MAP_SOURCE:
716       champlain_view_set_map_source (view, g_value_get_object (value));
717       break;
718 
719     case PROP_KINETIC_MODE:
720       champlain_view_set_kinetic_mode (view, g_value_get_boolean (value));
721       break;
722 
723     case PROP_DECELERATION:
724       champlain_view_set_deceleration (view, g_value_get_double (value));
725       break;
726 
727     case PROP_KEEP_CENTER_ON_RESIZE:
728       champlain_view_set_keep_center_on_resize (view, g_value_get_boolean (value));
729       break;
730 
731     case PROP_ZOOM_ON_DOUBLE_CLICK:
732       champlain_view_set_zoom_on_double_click (view, g_value_get_boolean (value));
733       break;
734 
735     case PROP_ANIMATE_ZOOM:
736       champlain_view_set_animate_zoom (view, g_value_get_boolean (value));
737       break;
738 
739     case PROP_BACKGROUND_PATTERN:
740       champlain_view_set_background_pattern (view, g_value_get_object (value));
741       break;
742 
743     case PROP_GOTO_ANIMATION_MODE:
744       priv->goto_mode = g_value_get_enum (value);
745       break;
746 
747     case PROP_GOTO_ANIMATION_DURATION:
748       priv->goto_duration = g_value_get_uint (value);
749       break;
750 
751     case PROP_WORLD:
752       champlain_view_set_world (view, g_value_get_boxed (value));
753       break;
754 
755     case PROP_HORIZONTAL_WRAP:
756       champlain_view_set_horizontal_wrap (view, g_value_get_boolean (value));
757       break;
758 
759     default:
760       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
761     }
762 }
763 
764 
765 static void
champlain_view_dispose(GObject * object)766 champlain_view_dispose (GObject *object)
767 {
768   DEBUG_LOG ()
769 
770   ChamplainView *view = CHAMPLAIN_VIEW (object);
771   ChamplainViewPrivate *priv = view->priv;
772 
773   if (priv->goto_context != NULL)
774     champlain_view_stop_go_to (view);
775 
776   if (priv->kinetic_scroll != NULL)
777     {
778       champlain_kinetic_scroll_view_stop (CHAMPLAIN_KINETIC_SCROLL_VIEW (priv->kinetic_scroll));
779       priv->kinetic_scroll = NULL;
780     }
781 
782   if (priv->viewport != NULL)
783     {
784       champlain_viewport_stop (CHAMPLAIN_VIEWPORT (priv->viewport));
785       priv->viewport = NULL;
786     }
787 
788   if (priv->map_source != NULL)
789     {
790       g_object_unref (priv->map_source);
791       priv->map_source = NULL;
792     }
793 
794   g_list_free_full (priv->overlay_sources, g_object_unref);
795   priv->overlay_sources = NULL;
796 
797   if (priv->background_content)
798     {
799       g_object_unref (priv->background_content);
800       priv->background_content = NULL;
801     }
802 
803   if (priv->redraw_timeout != 0)
804     {
805       g_source_remove (priv->redraw_timeout);
806       priv->redraw_timeout = 0;
807     }
808 
809   if (priv->zoom_actor_timeout != 0)
810     {
811       g_source_remove (priv->zoom_actor_timeout);
812       priv->zoom_actor_timeout = 0;
813     }
814 
815   if (priv->zoom_timeout != 0)
816     {
817       g_source_remove (priv->zoom_timeout);
818       priv->zoom_timeout = 0;
819     }
820 
821   if (priv->tile_map != NULL)
822     {
823       g_hash_table_destroy (priv->tile_map);
824       priv->tile_map = NULL;
825     }
826 
827   if (priv->zoom_gesture)
828     {
829       clutter_actor_remove_action (CLUTTER_ACTOR (view),
830                                    CLUTTER_ACTION (priv->zoom_gesture));
831       priv->zoom_gesture = NULL;
832     }
833 
834   if (priv->visible_tiles != NULL)
835     {
836       g_hash_table_destroy (priv->visible_tiles);
837       priv->visible_tiles = NULL;
838     }
839 
840   priv->map_layer = NULL;
841   priv->license_actor = NULL;
842 
843   /* This is needed to prevent race condition see bug #760012 */
844   if (priv->user_layers)
845       clutter_actor_remove_all_children (priv->user_layers);
846   priv->user_layers = NULL;
847   priv->zoom_layer = NULL;
848 
849   if (priv->world_bbox)
850     {
851       champlain_bounding_box_free (priv->world_bbox);
852       priv->world_bbox = NULL;
853     }
854 
855   G_OBJECT_CLASS (champlain_view_parent_class)->dispose (object);
856 }
857 
858 
859 static void
champlain_view_finalize(GObject * object)860 champlain_view_finalize (GObject *object)
861 {
862   DEBUG_LOG ()
863 
864   G_OBJECT_CLASS (champlain_view_parent_class)->finalize (object);
865 }
866 
867 
868 /* These return fixed sizes because either a.) We expect the user to size
869  * explicitly with clutter_actor_get_size or b.) place it in a container that
870  * allocates it whatever it wants.
871  */
872 static void
champlain_view_get_preferred_width(ClutterActor * actor,G_GNUC_UNUSED gfloat for_height,gfloat * min_width,gfloat * nat_width)873 champlain_view_get_preferred_width (ClutterActor *actor,
874     G_GNUC_UNUSED gfloat for_height,
875     gfloat *min_width,
876     gfloat *nat_width)
877 {
878   DEBUG_LOG ()
879 
880   ChamplainView *view = CHAMPLAIN_VIEW (actor);
881   gint width = champlain_map_source_get_tile_size (view->priv->map_source);
882 
883   if (min_width)
884     *min_width = 1;
885 
886   if (nat_width)
887     *nat_width = width;
888 }
889 
890 
891 static void
champlain_view_get_preferred_height(ClutterActor * actor,G_GNUC_UNUSED gfloat for_width,gfloat * min_height,gfloat * nat_height)892 champlain_view_get_preferred_height (ClutterActor *actor,
893     G_GNUC_UNUSED gfloat for_width,
894     gfloat *min_height,
895     gfloat *nat_height)
896 {
897   DEBUG_LOG ()
898 
899   ChamplainView *view = CHAMPLAIN_VIEW (actor);
900   gint height = champlain_map_source_get_tile_size (view->priv->map_source);
901 
902   if (min_height)
903     *min_height = 1;
904 
905   if (nat_height)
906     *nat_height = height;
907 }
908 
909 
910 static void
champlain_view_class_init(ChamplainViewClass * champlainViewClass)911 champlain_view_class_init (ChamplainViewClass *champlainViewClass)
912 {
913   DEBUG_LOG ()
914 
915   GObjectClass *object_class = G_OBJECT_CLASS (champlainViewClass);
916   object_class->dispose = champlain_view_dispose;
917   object_class->finalize = champlain_view_finalize;
918   object_class->get_property = champlain_view_get_property;
919   object_class->set_property = champlain_view_set_property;
920 
921   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (champlainViewClass);
922   actor_class->get_preferred_width = champlain_view_get_preferred_width;
923   actor_class->get_preferred_height = champlain_view_get_preferred_height;
924 
925   /**
926    * ChamplainView:longitude:
927    *
928    * The longitude coordonate of the map
929    *
930    * Since: 0.1
931    */
932   g_object_class_install_property (object_class,
933       PROP_LONGITUDE,
934       g_param_spec_double ("longitude",
935           "Longitude",
936           "The longitude coordonate of the map",
937           -180.0f,
938           180.0f,
939           0.0f,
940           CHAMPLAIN_PARAM_READWRITE));
941 
942   /**
943    * ChamplainView:latitude:
944    *
945    * The latitude coordonate of the map
946    *
947    * Since: 0.1
948    */
949   g_object_class_install_property (object_class,
950       PROP_LATITUDE,
951       g_param_spec_double ("latitude",
952           "Latitude",
953           "The latitude coordonate of the map",
954           -90.0f,
955           90.0f,
956           0.0f,
957           CHAMPLAIN_PARAM_READWRITE));
958 
959   /**
960    * ChamplainView:zoom-level:
961    *
962    * The level of zoom of the content.
963    *
964    * Since: 0.1
965    */
966   g_object_class_install_property (object_class,
967       PROP_ZOOM_LEVEL,
968       g_param_spec_uint ("zoom-level",
969           "Zoom level",
970           "The level of zoom of the map",
971           0,
972           20,
973           3,
974           CHAMPLAIN_PARAM_READWRITE));
975 
976   /**
977    * ChamplainView:min-zoom-level:
978    *
979    * The lowest allowed level of zoom of the content.
980    *
981    * Since: 0.4
982    */
983   g_object_class_install_property (object_class,
984       PROP_MIN_ZOOM_LEVEL,
985       g_param_spec_uint ("min-zoom-level",
986           "Min zoom level",
987           "The lowest allowed level of zoom",
988           0,
989           20,
990           0,
991           CHAMPLAIN_PARAM_READWRITE));
992 
993   /**
994    * ChamplainView:max-zoom-level:
995    *
996    * The highest allowed level of zoom of the content.
997    *
998    * Since: 0.4
999    */
1000   g_object_class_install_property (object_class,
1001       PROP_MAX_ZOOM_LEVEL,
1002       g_param_spec_uint ("max-zoom-level",
1003           "Max zoom level",
1004           "The highest allowed level of zoom",
1005           0,
1006           20,
1007           20,
1008           CHAMPLAIN_PARAM_READWRITE));
1009 
1010   /**
1011    * ChamplainView:map-source:
1012    *
1013    * The #ChamplainMapSource being displayed
1014    *
1015    * Since: 0.2
1016    */
1017   g_object_class_install_property (object_class,
1018       PROP_MAP_SOURCE,
1019       g_param_spec_object ("map-source",
1020           "Map source",
1021           "The map source being displayed",
1022           CHAMPLAIN_TYPE_MAP_SOURCE,
1023           CHAMPLAIN_PARAM_READWRITE));
1024 
1025   /**
1026    * ChamplainView:kinetic-mode:
1027    *
1028    * Determines whether the view should use kinetic mode.
1029    *
1030    * Since: 0.10
1031    */
1032   g_object_class_install_property (object_class,
1033       PROP_KINETIC_MODE,
1034       g_param_spec_boolean ("kinetic-mode",
1035           "Kinetic Mode",
1036           "Determines whether the view should use kinetic mode.",
1037           FALSE,
1038           CHAMPLAIN_PARAM_READWRITE));
1039 
1040   /**
1041    * ChamplainView:deceleration:
1042    *
1043    * The deceleration rate for the kinetic mode. The default value is 1.1.
1044    *
1045    * Since: 0.10
1046    */
1047   g_object_class_install_property (object_class,
1048       PROP_DECELERATION,
1049       g_param_spec_double ("deceleration",
1050           "Deceleration rate",
1051           "Rate at which the view will decelerate in kinetic mode.",
1052           1.0001,
1053           2.0,
1054           1.1,
1055           CHAMPLAIN_PARAM_READWRITE));
1056 
1057   /**
1058    * ChamplainView:keep-center-on-resize:
1059    *
1060    * Keep the current centered position when resizing the view.
1061    *
1062    * Since: 0.2.7
1063    */
1064   g_object_class_install_property (object_class,
1065       PROP_KEEP_CENTER_ON_RESIZE,
1066       g_param_spec_boolean ("keep-center-on-resize",
1067           "Keep center on resize",
1068           "Keep the current centered position upon resizing",
1069           TRUE,
1070           CHAMPLAIN_PARAM_READWRITE));
1071 
1072   /**
1073    * ChamplainView:zoom-on-double-click:
1074    *
1075    * Should the view zoom in and recenter when the user double click on the map.
1076    *
1077    * Since: 0.4
1078    */
1079   g_object_class_install_property (object_class,
1080       PROP_ZOOM_ON_DOUBLE_CLICK,
1081       g_param_spec_boolean ("zoom-on-double-click",
1082           "Zoom in on double click",
1083           "Zoom in and recenter on double click on the map",
1084           TRUE,
1085           CHAMPLAIN_PARAM_READWRITE));
1086 
1087   /**
1088    * ChamplainView:animate-zoom:
1089    *
1090    * Animate zoom change when zooming in/out.
1091    *
1092    * Since: 0.12
1093    */
1094   g_object_class_install_property (object_class,
1095       PROP_ANIMATE_ZOOM,
1096       g_param_spec_boolean ("animate-zoom",
1097           "Animate zoom level change",
1098           "Animate zoom change when zooming in/out",
1099           TRUE,
1100           CHAMPLAIN_PARAM_READWRITE));
1101 
1102   /**
1103    * ChamplainView:state:
1104    *
1105    * The view's global state. Useful to inform using if the view is busy loading
1106    * tiles or not.
1107    *
1108    * Since: 0.4
1109    */
1110   g_object_class_install_property (object_class,
1111       PROP_STATE,
1112       g_param_spec_enum ("state",
1113           "View's state",
1114           "View's global state",
1115           CHAMPLAIN_TYPE_STATE,
1116           CHAMPLAIN_STATE_NONE,
1117           G_PARAM_READABLE));
1118 
1119   /**
1120    * ChamplainView:background-pattern:
1121    *
1122    * The pattern displayed in the background of the map.
1123    *
1124    * Since: 0.12.4
1125    */
1126   g_object_class_install_property (object_class,
1127       PROP_BACKGROUND_PATTERN,
1128       g_param_spec_object ("background-pattern",
1129           "Background pattern",
1130           "The tile's background pattern",
1131           CLUTTER_TYPE_ACTOR,
1132           G_PARAM_READWRITE));
1133 
1134   /**
1135    * ChamplainView:goto-animation-mode:
1136    *
1137    * The mode of animation when going to a location.
1138    *
1139    * Please note that animation of #champlain_view_ensure_visible also
1140    * involves a 'goto' animation.
1141    *
1142    */
1143   g_object_class_install_property (object_class,
1144       PROP_GOTO_ANIMATION_MODE,
1145       g_param_spec_enum ("goto-animation-mode",
1146           "Go to animation mode",
1147           "The mode of animation when going to a location",
1148           CLUTTER_TYPE_ANIMATION_MODE,
1149           CLUTTER_EASE_IN_OUT_CIRC,
1150           G_PARAM_READWRITE));
1151 
1152   /**
1153    * ChamplainView:goto-animation-duration:
1154    *
1155    * The duration of an animation when going to a location.
1156    * A value of 0 means that the duration is calculated automatically for you.
1157    *
1158    * Please note that animation of #champlain_view_ensure_visible also
1159    * involves a 'goto' animation.
1160    *
1161    */
1162   g_object_class_install_property (object_class,
1163       PROP_GOTO_ANIMATION_DURATION,
1164       g_param_spec_uint ("goto-animation-duration",
1165           "Go to animation duration",
1166           "The duration of an animation when going to a location",
1167           0,
1168           G_MAXINT,
1169           0,
1170           G_PARAM_READWRITE));
1171 
1172   /**
1173    * ChamplainView:world:
1174    *
1175    * Set a bounding box to limit the world to. No tiles will be loaded
1176    * outside of this bounding box. It will not be possible to scroll outside
1177    * of this bounding box.
1178    *
1179    * Default world is the actual world.
1180    *
1181    * Since: 0.12.11
1182    */
1183   g_object_class_install_property (object_class,
1184       PROP_WORLD,
1185       g_param_spec_boxed ("world",
1186           "The world",
1187           "The bounding box to limit the #ChamplainView to",
1188           CHAMPLAIN_TYPE_BOUNDING_BOX,
1189           G_PARAM_READWRITE));
1190 
1191   /**
1192    * ChamplainView:horizontal-wrap:
1193    *
1194    * Determines whether the view should wrap horizontally.
1195    *
1196    */
1197   g_object_class_install_property (object_class,
1198       PROP_HORIZONTAL_WRAP,
1199       g_param_spec_boolean ("horizontal-wrap",
1200           "Horizontal wrap",
1201           "Determines whether the view should wrap horizontally.",
1202           FALSE,
1203           CHAMPLAIN_PARAM_READWRITE));
1204 
1205   /**
1206    * ChamplainView::animation-completed:
1207    *
1208    * The #ChamplainView::animation-completed signal is emitted when any animation in the view
1209    * ends.  This is a detailed signal.  For example, if you want to be signaled
1210    * only for go-to animation, you should connect to
1211    * "animation-completed::go-to". And for zoom, connect to "animation-completed::zoom".
1212    *
1213    * Since: 0.4
1214    */
1215   signals[ANIMATION_COMPLETED] =
1216     g_signal_new ("animation-completed",
1217         G_OBJECT_CLASS_TYPE (object_class),
1218         G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
1219         0, NULL, NULL,
1220         NULL,
1221         G_TYPE_NONE,
1222         0);
1223 
1224   /**
1225    * ChamplainView::layer-relocated:
1226    *
1227    * Indicates that the layers have been "relocated". In practice this means that
1228    * every layer should connect to this signal and redraw itself when the signal is
1229    * emitted. Layer relocation happens when zooming in/out and when panning for more
1230    * than MAX_INT pixels.
1231    *
1232    * Since: 0.10
1233    */
1234   signals[LAYER_RELOCATED] =
1235     g_signal_new ("layer-relocated",
1236         G_OBJECT_CLASS_TYPE (object_class),
1237         G_SIGNAL_RUN_LAST,
1238         0, NULL, NULL,
1239         NULL,
1240         G_TYPE_NONE,
1241         0);
1242 }
1243 
1244 
1245 static void
champlain_view_realized_cb(ChamplainView * view,G_GNUC_UNUSED GParamSpec * pspec)1246 champlain_view_realized_cb (ChamplainView *view,
1247     G_GNUC_UNUSED GParamSpec *pspec)
1248 {
1249   DEBUG_LOG ()
1250 
1251   ChamplainViewPrivate *priv = view->priv;
1252 
1253   if (!clutter_actor_is_realized (CLUTTER_ACTOR (view)))
1254     return;
1255 
1256   clutter_actor_grab_key_focus (priv->kinetic_scroll);
1257 
1258   resize_viewport (view);
1259   champlain_view_center_on (view, priv->latitude, priv->longitude);
1260 
1261   g_object_notify (G_OBJECT (view), "zoom-level");
1262   g_object_notify (G_OBJECT (view), "map-source");
1263   g_signal_emit_by_name (view, "layer-relocated", NULL);
1264 }
1265 
1266 
1267 static gboolean
_update_idle_cb(ChamplainView * view)1268 _update_idle_cb (ChamplainView *view)
1269 {
1270   DEBUG_LOG ()
1271 
1272   ChamplainViewPrivate *priv = view->priv;
1273 
1274   if (!priv->kinetic_scroll)
1275     return FALSE;
1276 
1277   clutter_actor_set_size (priv->kinetic_scroll,
1278       priv->viewport_width,
1279       priv->viewport_height);
1280 
1281   resize_viewport (view);
1282 
1283   if (priv->keep_center_on_resize)
1284     champlain_view_center_on (view, priv->latitude, priv->longitude);
1285   else
1286     load_visible_tiles (view, FALSE);
1287 
1288   if (priv->hwrap)
1289     {
1290       update_clones (view);
1291       position_viewport (view, x_to_wrap_x (priv->viewport_x, get_map_width (view)), priv->viewport_y);
1292     }
1293 
1294   return FALSE;
1295 }
1296 
1297 
1298 static void
view_size_changed_cb(ChamplainView * view,G_GNUC_UNUSED GParamSpec * pspec)1299 view_size_changed_cb (ChamplainView *view,
1300     G_GNUC_UNUSED GParamSpec *pspec)
1301 {
1302   ChamplainViewPrivate *priv = view->priv;
1303   gint width, height;
1304 
1305   width = clutter_actor_get_width (CLUTTER_ACTOR (view));
1306   height = clutter_actor_get_height (CLUTTER_ACTOR (view));
1307 
1308   if (priv->viewport_width != width || priv->viewport_height != height)
1309     {
1310       g_idle_add_full (CLUTTER_PRIORITY_REDRAW,
1311           (GSourceFunc) _update_idle_cb,
1312           g_object_ref (view),
1313           (GDestroyNotify) g_object_unref);
1314     }
1315 
1316   priv->viewport_width = width;
1317   priv->viewport_height = height;
1318 }
1319 
1320 static void
exclusive_destroy_clone(ClutterActor * clone)1321 exclusive_destroy_clone (ClutterActor *clone)
1322 {
1323   if (!CLUTTER_IS_CLONE (clone))
1324     return;
1325 
1326   clutter_actor_destroy (clone);
1327 }
1328 
1329 static void
add_clone(ChamplainView * view,gint x)1330 add_clone (ChamplainView *view,
1331     gint x)
1332 {
1333   ChamplainViewPrivate *priv = view->priv;
1334   ClutterActor *map_clone, *user_clone;
1335 
1336   /* Map layer clones */
1337   map_clone = clutter_clone_new (priv->map_layer);
1338   clutter_actor_set_x (map_clone, x);
1339   clutter_actor_insert_child_below (priv->viewport_container, map_clone,
1340                                     NULL);
1341 
1342   priv->map_clones = g_list_prepend (priv->map_clones, map_clone);
1343 
1344   /* User layer clones */
1345   user_clone = clutter_clone_new (priv->user_layers);
1346   clutter_actor_set_x (user_clone, x);
1347   clutter_actor_insert_child_below (priv->viewport_container, user_clone,
1348                                     priv->user_layers);
1349 
1350   /* Inserting clones in the slots following the real user layer (order must be kept)*/
1351   priv->user_layer_slots = g_list_append (priv->user_layer_slots, user_clone);
1352 }
1353 
1354 
1355 static void
update_clones(ChamplainView * view)1356 update_clones (ChamplainView *view)
1357 {
1358   DEBUG_LOG ()
1359 
1360   ChamplainViewPrivate *priv = view->priv;
1361   gint map_size;
1362   gfloat view_width;
1363   gint i;
1364 
1365   map_size = get_map_width (view);
1366   clutter_actor_get_size (CLUTTER_ACTOR (view), &view_width, NULL);
1367 
1368   priv->num_right_clones = ceil (view_width / map_size) + 1;
1369 
1370   if (priv->map_clones != NULL)
1371     {
1372       /* Only destroy clones, skip the real user_layers actor */
1373       g_list_free_full (priv->user_layer_slots, (GDestroyNotify) exclusive_destroy_clone);
1374       g_list_free_full (priv->map_clones, (GDestroyNotify) clutter_actor_destroy);
1375 
1376       priv->map_clones = NULL;
1377       priv->user_layer_slots = NULL;
1378     }
1379 
1380   /* An extra clone is added to the left for smoother panning */
1381   add_clone (view, -map_size);
1382 
1383   /* Inserting the real user layer in the second slot (after the left clone) */
1384   priv->user_layer_slots = g_list_append (priv->user_layer_slots, priv->user_layers);
1385   clutter_actor_set_x (priv->user_layers, 0);
1386 
1387   for (i = 0; i < priv->num_right_clones; i++)
1388     add_clone (view, (i + 1) * map_size);
1389 }
1390 
1391 
1392 static void
slice_free_gint64(gpointer data)1393 slice_free_gint64 (gpointer data)
1394 {
1395   g_slice_free (gint64, data);
1396 }
1397 
1398 
1399 static guint
view_find_suitable_zoom(ChamplainView * view,gdouble factor)1400 view_find_suitable_zoom (ChamplainView *view,
1401     gdouble factor)
1402 {
1403   ChamplainViewPrivate *priv = view->priv;
1404   guint zoom_level = priv->initial_gesture_zoom;
1405 
1406   while (factor > 2 && zoom_level <= priv->max_zoom_level)
1407     {
1408       factor /= 2;
1409       zoom_level++;
1410     }
1411 
1412   while (factor < 0.5 && zoom_level >= priv->min_zoom_level)
1413     {
1414       factor *= 2;
1415       zoom_level--;
1416     }
1417 
1418   return zoom_level;
1419 }
1420 
1421 
1422 static gboolean
zoom_gesture_zoom_cb(ClutterZoomAction * gesture,G_GNUC_UNUSED ClutterActor * actor,ClutterPoint * focal_point,gdouble factor,gpointer user_data)1423 zoom_gesture_zoom_cb (ClutterZoomAction *gesture,
1424     G_GNUC_UNUSED ClutterActor *actor,
1425     ClutterPoint *focal_point,
1426     gdouble factor,
1427     gpointer user_data)
1428 {
1429   ChamplainView *view = user_data;
1430   ChamplainViewPrivate *priv = view->priv;
1431   gdouble dx, dy, lat, lon;
1432   ClutterPoint focus;
1433 
1434   if (!priv->zoom_started)
1435     {
1436       priv->zoom_started = TRUE;
1437       priv->focus_lat = champlain_view_y_to_latitude (user_data, focal_point->y);
1438       priv->focus_lon = champlain_view_x_to_longitude (user_data, focal_point->x);
1439       priv->initial_gesture_zoom = priv->zoom_level;
1440     }
1441   else
1442     {
1443       guint zoom_level;
1444 
1445       zoom_level = view_find_suitable_zoom (view, factor);
1446 
1447       focus.x = champlain_map_source_get_x (priv->map_source,
1448                                             zoom_level, priv->focus_lon);
1449       focus.y = champlain_map_source_get_y (priv->map_source,
1450                                             zoom_level, priv->focus_lat);
1451 
1452       dx = (priv->viewport_width / 2.0) - focal_point->x;
1453       dy = (priv->viewport_height / 2.0) - focal_point->y;
1454 
1455       lon = get_longitude (view, zoom_level, focus.x + dx);
1456       lat = champlain_map_source_get_latitude (priv->map_source, zoom_level, focus.y + dy);
1457 
1458       champlain_view_center_on (view, lat, lon);
1459       champlain_view_set_zoom_level (view, zoom_level);
1460     }
1461 
1462   return FALSE;
1463 }
1464 
1465 static gboolean
zoom_gesture_begin_cb(ClutterGestureAction * gesture,G_GNUC_UNUSED ClutterActor * actor,G_GNUC_UNUSED gpointer user_data)1466 zoom_gesture_begin_cb (ClutterGestureAction *gesture,
1467     G_GNUC_UNUSED ClutterActor *actor,
1468     G_GNUC_UNUSED gpointer user_data)
1469 {
1470   const ClutterEvent *event = clutter_gesture_action_get_last_event (gesture, 0);
1471   ClutterInputDevice *device = clutter_event_get_source_device (event);
1472 
1473   /* Give up on >2 finger input and when using mouse */
1474   return clutter_gesture_action_get_n_current_points (gesture) == 2 &&
1475     clutter_input_device_get_device_type (device) != CLUTTER_POINTER_DEVICE;
1476 }
1477 
1478 
1479 static void
zoom_gesture_finish_cb(ClutterGestureAction * gesture,G_GNUC_UNUSED ClutterActor * actor,gpointer user_data)1480 zoom_gesture_finish_cb (ClutterGestureAction *gesture,
1481     G_GNUC_UNUSED ClutterActor *actor,
1482     gpointer user_data)
1483 {
1484   ChamplainViewPrivate *priv = CHAMPLAIN_VIEW (user_data)->priv;
1485 
1486   priv->zoom_started = FALSE;
1487 }
1488 
1489 
1490 static void
zoom_gesture_cancel_cb(ClutterGestureAction * gesture,G_GNUC_UNUSED ClutterActor * actor,gpointer user_data)1491 zoom_gesture_cancel_cb (ClutterGestureAction *gesture,
1492     G_GNUC_UNUSED ClutterActor *actor,
1493     gpointer user_data)
1494 {
1495   ChamplainViewPrivate *priv = CHAMPLAIN_VIEW (user_data)->priv;
1496 
1497   priv->zoom_started = FALSE;
1498   g_signal_stop_emission_by_name (gesture, "gesture-cancel");
1499 }
1500 
1501 
1502 static void
champlain_view_init(ChamplainView * view)1503 champlain_view_init (ChamplainView *view)
1504 {
1505   DEBUG_LOG ()
1506 
1507   ChamplainViewPrivate *priv = champlain_view_get_instance_private (view);
1508   ChamplainMapSourceFactory *factory;
1509   ChamplainMapSource *source;
1510   ClutterLayoutManager *layout;
1511   ClutterColor color = { 0xf1, 0xee, 0xe8, 0xff };
1512 
1513   champlain_debug_set_flags (g_getenv ("CHAMPLAIN_DEBUG"));
1514 
1515   view->priv = priv;
1516 
1517   factory = champlain_map_source_factory_dup_default ();
1518   source = champlain_map_source_factory_create_cached_source (factory, CHAMPLAIN_MAP_SOURCE_OSM_MAPNIK);
1519 
1520   priv->map_source = CHAMPLAIN_MAP_SOURCE (g_object_ref_sink (source));
1521 
1522   priv->zoom_level = 0;
1523   priv->min_zoom_level = champlain_map_source_get_min_zoom_level (priv->map_source);
1524   priv->max_zoom_level = champlain_map_source_get_max_zoom_level (priv->map_source);
1525   priv->keep_center_on_resize = TRUE;
1526   priv->zoom_on_double_click = TRUE;
1527   priv->animate_zoom = TRUE;
1528   priv->license_actor = NULL;
1529   priv->kinetic_mode = FALSE;
1530   priv->viewport_x = 0;
1531   priv->viewport_y = 0;
1532   priv->viewport_width = 0;
1533   priv->viewport_height = 0;
1534   priv->state = CHAMPLAIN_STATE_NONE;
1535   priv->latitude = 0.0;
1536   priv->longitude = 0.0;
1537   priv->goto_context = NULL;
1538   priv->tiles_loading = 0;
1539   priv->animating_zoom = FALSE;
1540   priv->background_content = NULL;
1541   priv->zoom_overlay_actor = NULL;
1542   priv->bg_offset_x = 0;
1543   priv->bg_offset_y = 0;
1544   priv->location_updated = FALSE;
1545   priv->redraw_timeout = 0;
1546   priv->zoom_actor_timeout = 0;
1547   priv->tile_map = g_hash_table_new_full (g_int64_hash, g_int64_equal, slice_free_gint64, NULL);
1548   priv->visible_tiles = g_hash_table_new_full (g_int64_hash, g_int64_equal, slice_free_gint64, NULL);
1549   priv->goto_duration = 0;
1550   priv->goto_mode = CLUTTER_EASE_IN_OUT_CIRC;
1551   priv->world_bbox = champlain_bounding_box_new ();
1552   priv->world_bbox->left = CHAMPLAIN_MIN_LONGITUDE;
1553   priv->world_bbox->bottom = CHAMPLAIN_MIN_LATITUDE;
1554   priv->world_bbox->right = CHAMPLAIN_MAX_LONGITUDE;
1555   priv->world_bbox->top = CHAMPLAIN_MAX_LATITUDE;
1556   priv->num_right_clones = 0;
1557   priv->map_clones = NULL;
1558   priv->user_layer_slots = NULL;
1559   priv->hwrap = FALSE;
1560 
1561   clutter_actor_set_background_color (CLUTTER_ACTOR (view), &color);
1562 
1563   g_signal_connect (view, "notify::width", G_CALLBACK (view_size_changed_cb), NULL);
1564   g_signal_connect (view, "notify::height", G_CALLBACK (view_size_changed_cb), NULL);
1565 
1566   g_signal_connect (view, "notify::realized", G_CALLBACK (champlain_view_realized_cb), NULL);
1567 
1568   layout = clutter_bin_layout_new (CLUTTER_BIN_ALIGNMENT_FIXED,
1569                                    CLUTTER_BIN_ALIGNMENT_FIXED);
1570   clutter_actor_set_layout_manager (CLUTTER_ACTOR (view), layout);
1571 
1572   /* Setup viewport layers*/
1573   priv->background_layer = clutter_actor_new ();
1574   priv->zoom_layer = clutter_actor_new ();
1575   priv->map_layer = clutter_actor_new ();
1576   priv->user_layers = clutter_actor_new ();
1577 
1578   priv->viewport_container = clutter_actor_new ();
1579   clutter_actor_add_child (priv->viewport_container, priv->background_layer);
1580   clutter_actor_add_child (priv->viewport_container, priv->zoom_layer);
1581   clutter_actor_add_child (priv->viewport_container, priv->map_layer);
1582   clutter_actor_add_child (priv->viewport_container, priv->user_layers);
1583 
1584   /* Setup viewport */
1585   priv->viewport = champlain_viewport_new ();
1586   champlain_viewport_set_child (CHAMPLAIN_VIEWPORT (priv->viewport), priv->viewport_container);
1587   g_signal_connect (priv->viewport, "relocated", G_CALLBACK (view_relocated_cb), view);
1588 
1589   g_signal_connect (priv->viewport, "notify::x-origin",
1590       G_CALLBACK (viewport_pos_changed_cb), view);
1591   g_signal_connect (priv->viewport, "notify::y-origin",
1592       G_CALLBACK (viewport_pos_changed_cb), view);
1593   clutter_actor_set_reactive (priv->viewport, TRUE);
1594 
1595   /* Setup kinetic scroll */
1596   priv->kinetic_scroll = champlain_kinetic_scroll_view_new (FALSE, CHAMPLAIN_VIEWPORT (priv->viewport));
1597 
1598   g_signal_connect (priv->kinetic_scroll, "scroll-event",
1599       G_CALLBACK (scroll_event), view);
1600   g_signal_connect (priv->kinetic_scroll, "panning-completed",
1601       G_CALLBACK (panning_completed), view);
1602   g_signal_connect (priv->kinetic_scroll, "button-press-event",
1603       G_CALLBACK (kinetic_scroll_button_press_cb), view);
1604 
1605   /* Setup zoom gesture */
1606   priv->zoom_gesture = CLUTTER_GESTURE_ACTION (clutter_zoom_action_new ());
1607   g_signal_connect (priv->zoom_gesture, "zoom",
1608                     G_CALLBACK (zoom_gesture_zoom_cb), view);
1609   g_signal_connect (priv->zoom_gesture, "gesture-begin",
1610                     G_CALLBACK (zoom_gesture_begin_cb), view);
1611   g_signal_connect (priv->zoom_gesture, "gesture-end",
1612                     G_CALLBACK (zoom_gesture_finish_cb), view);
1613   g_signal_connect (priv->zoom_gesture, "gesture-cancel",
1614                     G_CALLBACK (zoom_gesture_cancel_cb), view);
1615   clutter_actor_add_action (CLUTTER_ACTOR (view),
1616                             CLUTTER_ACTION (priv->zoom_gesture));
1617 
1618   /* Setup stage */
1619   clutter_actor_add_child (CLUTTER_ACTOR (view), priv->kinetic_scroll);
1620   priv->zoom_overlay_actor = clutter_actor_new ();
1621   clutter_actor_add_child (CLUTTER_ACTOR (view), priv->zoom_overlay_actor);
1622   g_signal_connect (view, "key-press-event",
1623                     G_CALLBACK (kinetic_scroll_key_press_cb), NULL);
1624 
1625   /* Setup license */
1626   priv->license_actor = champlain_license_new ();
1627   champlain_license_connect_view (CHAMPLAIN_LICENSE (priv->license_actor), view);
1628   clutter_actor_set_x_expand (priv->license_actor, TRUE);
1629   clutter_actor_set_y_expand (priv->license_actor, TRUE);
1630   clutter_actor_set_x_align (priv->license_actor, CLUTTER_ACTOR_ALIGN_END);
1631   clutter_actor_set_y_align (priv->license_actor, CLUTTER_ACTOR_ALIGN_END);
1632   clutter_actor_add_child (CLUTTER_ACTOR (view), priv->license_actor);
1633 }
1634 
1635 
1636 static gboolean
redraw_timeout_cb(gpointer data)1637 redraw_timeout_cb (gpointer data)
1638 {
1639   DEBUG_LOG ()
1640 
1641   ChamplainView *view = data;
1642   ChamplainViewPrivate *priv = view->priv;
1643   gdouble x, y;
1644 
1645   champlain_viewport_get_origin (CHAMPLAIN_VIEWPORT (priv->viewport), &x, &y);
1646 
1647   if (priv->location_updated || (gint)ABS (x - priv->viewport_x) > 0 || (gint)ABS (y - priv->viewport_y) > 0)
1648     {
1649       update_coords (view, x, y, TRUE);
1650       load_visible_tiles (view, FALSE);
1651       priv->location_updated = FALSE;
1652     }
1653 
1654   return TRUE;
1655 }
1656 
1657 
1658 static void
viewport_pos_changed_cb(G_GNUC_UNUSED GObject * gobject,G_GNUC_UNUSED GParamSpec * arg1,ChamplainView * view)1659 viewport_pos_changed_cb (G_GNUC_UNUSED GObject *gobject,
1660     G_GNUC_UNUSED GParamSpec *arg1,
1661     ChamplainView *view)
1662 {
1663   DEBUG_LOG ()
1664 
1665   ChamplainViewPrivate *priv = view->priv;
1666   gdouble x, y;
1667 
1668   if (priv->redraw_timeout == 0)
1669     priv->redraw_timeout = g_timeout_add (350, redraw_timeout_cb, view);
1670 
1671   champlain_viewport_get_origin (CHAMPLAIN_VIEWPORT (priv->viewport), &x, &y);
1672 
1673   if (priv->hwrap)
1674     {
1675       gint map_width;
1676       map_width = get_map_width (view);
1677 
1678       /* Faux wrapping, by positioning viewport to correct wrap point
1679        * so the master map view is on the left edge of ChamplainView
1680        * (possibly partially invisible) */
1681       if (x < 0 || x >= map_width)
1682         position_viewport (view, x_to_wrap_x (x, map_width), y);
1683     }
1684 
1685   if (ABS (x - priv->viewport_x) > 100 || ABS (y - priv->viewport_y) > 100)
1686     {
1687       update_coords (view, x, y, FALSE);
1688       load_visible_tiles (view, FALSE);
1689       priv->location_updated = TRUE;
1690     }
1691 }
1692 
1693 
1694 static void
swap_user_layer_slots(ChamplainView * view,gint original_index,gint clone_index)1695 swap_user_layer_slots (ChamplainView *view,
1696     gint original_index,
1697     gint clone_index)
1698 {
1699   ChamplainViewPrivate *priv = view->priv;
1700   gint map_width = get_map_width (view);
1701 
1702   GList *original_slot = g_list_nth (priv->user_layer_slots, original_index);
1703   GList *clone_slot = g_list_nth (priv->user_layer_slots, clone_index);
1704 
1705   ClutterActor *clone = clone_slot->data;
1706 
1707   original_slot->data = clone;
1708   clone_slot->data = priv->user_layers;
1709 
1710   clutter_actor_set_x (clone, (original_index - 1) * map_width);
1711   clutter_actor_set_x (priv->user_layers, (clone_index - 1) * map_width);
1712 }
1713 
1714 
1715 static gboolean
viewport_motion_cb(G_GNUC_UNUSED ClutterActor * actor,ClutterMotionEvent * event,ChamplainView * view)1716 viewport_motion_cb (G_GNUC_UNUSED ClutterActor *actor,
1717     ClutterMotionEvent *event,
1718     ChamplainView *view)
1719 {
1720    ChamplainViewPrivate *priv = view->priv;
1721 
1722    gint map_width = get_map_width (view);
1723 
1724    gint original_index = g_list_index (priv->user_layer_slots, priv->user_layers);
1725    gint clone_index = (event->x + priv->viewport_x) / map_width + 1;
1726 
1727    if (clone_index != original_index && clone_index < priv->num_right_clones + 2)
1728      swap_user_layer_slots (view, original_index, clone_index);
1729 
1730    return TRUE;
1731  }
1732 
1733 
1734 static ClutterActor *
sample_user_layer_at_pos(ChamplainView * view,gfloat x,gfloat y)1735 sample_user_layer_at_pos (ChamplainView *view,
1736     gfloat x,
1737     gfloat y)
1738 {
1739     ChamplainViewPrivate *priv = view->priv;
1740 
1741     ClutterStage *stage = CLUTTER_STAGE (clutter_actor_get_stage (CLUTTER_ACTOR (view)));
1742     ClutterActor *retval = clutter_stage_get_actor_at_pos (stage,
1743         CLUTTER_PICK_REACTIVE, x, y);
1744 
1745     /* If no reactive actor is found on top of the clone, return NULL */
1746     if (!clutter_actor_contains (priv->user_layers, retval))
1747       return NULL;
1748 
1749     return retval;
1750 }
1751 
1752 
1753 static gboolean
viewport_press_cb(G_GNUC_UNUSED ClutterActor * actor,ClutterButtonEvent * event,ChamplainView * view)1754 viewport_press_cb (G_GNUC_UNUSED ClutterActor *actor,
1755     ClutterButtonEvent *event,
1756     ChamplainView *view)
1757 {
1758   DEBUG_LOG ()
1759 
1760   ChamplainViewPrivate *priv = view->priv;
1761 
1762   if (!priv->hwrap)
1763     return FALSE;
1764 
1765   gint original_index = g_list_index (priv->user_layer_slots, priv->user_layers);
1766   gint initial_original_index = original_index;
1767   ClutterActor *sampled_actor = NULL;
1768 
1769   /* Sampling neighbouring slots for children that are split by the slot border.
1770    * (e.g. a marker that has one half in a slot #n and the other half in #n-1)
1771    * Whenever a user clicks on the real user layer, it is swapped succesively with
1772    * the right and left neighbors (if they exist) and the area at the event
1773    * coordinates is inspected for a reactive child actor. If a child is found,
1774    * a button press is synthesized over it.
1775    */
1776   gint right_neighbor_index = original_index + 1;
1777   gint left_neighbor_index = original_index - 1;
1778 
1779   /* Swapping and testing right neighbor */
1780   if (right_neighbor_index < priv->num_right_clones + 2)
1781     {
1782       swap_user_layer_slots (view, original_index, right_neighbor_index);
1783       original_index = right_neighbor_index;
1784       sampled_actor = sample_user_layer_at_pos (view, event->x, event->y);
1785     }
1786 
1787   /* Swapping and testing left neighbor */
1788   if (left_neighbor_index >= 0 && sampled_actor == NULL)
1789     {
1790       swap_user_layer_slots (view, original_index, left_neighbor_index);
1791       original_index = left_neighbor_index;
1792       sampled_actor = sample_user_layer_at_pos (view, event->x, event->y);
1793     }
1794 
1795   /* If found, redirecting event to the sampled actor */
1796   if (sampled_actor != NULL)
1797     {
1798       ClutterEvent *cloned_event = (ClutterEvent *)event;
1799       clutter_event_set_source (cloned_event, sampled_actor);
1800       clutter_event_put (cloned_event);
1801     }
1802   else
1803     {
1804       /* Swapping the real layer back to its initial slot */
1805       if (original_index != initial_original_index)
1806         swap_user_layer_slots (view, original_index, initial_original_index);
1807 
1808       return FALSE;
1809     }
1810 
1811   return TRUE;
1812 }
1813 
1814 static gboolean
kinetic_scroll_button_press_cb(G_GNUC_UNUSED ClutterActor * actor,ClutterButtonEvent * event,ChamplainView * view)1815 kinetic_scroll_button_press_cb (G_GNUC_UNUSED ClutterActor *actor,
1816     ClutterButtonEvent *event,
1817     ChamplainView *view)
1818 {
1819   DEBUG_LOG ()
1820 
1821   ChamplainViewPrivate *priv = view->priv;
1822 
1823   if (priv->zoom_on_double_click && event->button == 1 && event->click_count == 2)
1824     return view_set_zoom_level_at (view, priv->zoom_level + 1, TRUE, event->x, event->y);
1825 
1826   return FALSE; /* Propagate the event */
1827 }
1828 
1829 
1830 static void
champlain_view_scroll(ChamplainView * view,gint deltax,gint deltay)1831 champlain_view_scroll (ChamplainView *view,
1832     gint deltax,
1833     gint deltay)
1834 {
1835   DEBUG_LOG ()
1836 
1837   ChamplainViewPrivate *priv = view->priv;
1838   gdouble lat, lon;
1839   gint x, y;
1840 
1841   x = priv->viewport_x + priv->viewport_width / 2.0 + deltax;
1842   y = priv->viewport_y + priv->viewport_height / 2.0 + deltay;
1843 
1844   lat = champlain_map_source_get_latitude (priv->map_source, priv->zoom_level, y);
1845   lon = get_longitude (view, priv->zoom_level, x);
1846 
1847   if (priv->kinetic_mode)
1848     champlain_view_go_to_with_duration (view, lat, lon, 300);
1849   else
1850     champlain_view_center_on (view, lat, lon);
1851 }
1852 
1853 
1854 static gboolean
kinetic_scroll_key_press_cb(ChamplainView * view,ClutterKeyEvent * event)1855 kinetic_scroll_key_press_cb (ChamplainView *view,
1856     ClutterKeyEvent *event)
1857 {
1858   DEBUG_LOG ()
1859 
1860   ChamplainViewPrivate *priv = view->priv;
1861 
1862   switch (event->keyval)
1863     {
1864     case 65361: /* Left */
1865       champlain_view_scroll (view, -priv->viewport_width / 4.0, 0);
1866       return TRUE;
1867       break;
1868 
1869     case 65362: /* Up */
1870       if (event->modifier_state & CLUTTER_CONTROL_MASK)
1871         champlain_view_zoom_in (view);
1872       else
1873         champlain_view_scroll (view, 0, -priv->viewport_width / 4.0);
1874       return TRUE;
1875       break;
1876 
1877     case 65363: /* Right */
1878       champlain_view_scroll (view, priv->viewport_width / 4.0, 0);
1879       return TRUE;
1880       break;
1881 
1882     case 65364: /* Down */
1883       if (event->modifier_state & CLUTTER_CONTROL_MASK)
1884         champlain_view_zoom_out (view);
1885       else
1886         champlain_view_scroll (view, 0, priv->viewport_width / 4.0);
1887       return TRUE;
1888       break;
1889 
1890     default:
1891       return FALSE; /* Propagate the event */
1892     }
1893   return FALSE; /* Propagate the event */
1894 }
1895 
1896 
1897 /**
1898  * champlain_view_new:
1899  *
1900  * Creates an instance of #ChamplainView.
1901  *
1902  * Returns: a new #ChamplainView ready to be used as a #ClutterActor.
1903  *
1904  * Since: 0.4
1905  */
1906 ClutterActor *
champlain_view_new(void)1907 champlain_view_new (void)
1908 {
1909   DEBUG_LOG ()
1910 
1911   return g_object_new (CHAMPLAIN_TYPE_VIEW, NULL);
1912 }
1913 
1914 
1915 /**
1916  * champlain_view_center_on:
1917  * @view: a #ChamplainView
1918  * @latitude: the longitude to center the map at
1919  * @longitude: the longitude to center the map at
1920  *
1921  * Centers the map on these coordinates.
1922  *
1923  * Since: 0.1
1924  */
1925 void
champlain_view_center_on(ChamplainView * view,gdouble latitude,gdouble longitude)1926 champlain_view_center_on (ChamplainView *view,
1927     gdouble latitude,
1928     gdouble longitude)
1929 {
1930   DEBUG_LOG ()
1931 
1932   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
1933 
1934   gdouble x, y;
1935   ChamplainViewPrivate *priv = view->priv;
1936 
1937   longitude = CLAMP (longitude, priv->world_bbox->left, priv->world_bbox->right);
1938   latitude = CLAMP (latitude, priv->world_bbox->bottom, priv->world_bbox->top);
1939 
1940   x = champlain_map_source_get_x (priv->map_source, priv->zoom_level, longitude) - priv->viewport_width / 2.0;
1941   y = champlain_map_source_get_y (priv->map_source, priv->zoom_level, latitude) - priv->viewport_height / 2.0;
1942 
1943   DEBUG ("Centering on %f, %f (%g, %g)", latitude, longitude, x, y);
1944 
1945   if (priv->hwrap)
1946     position_viewport (view, x_to_wrap_x (x, get_map_width (view)), y);
1947   else
1948     position_viewport (view, x, y);
1949   load_visible_tiles (view, FALSE);
1950 }
1951 
1952 
1953 static void
timeline_new_frame(G_GNUC_UNUSED ClutterTimeline * timeline,G_GNUC_UNUSED gint frame_num,GoToContext * ctx)1954 timeline_new_frame (G_GNUC_UNUSED ClutterTimeline *timeline,
1955     G_GNUC_UNUSED gint frame_num,
1956     GoToContext *ctx)
1957 {
1958   DEBUG_LOG ()
1959 
1960   gdouble alpha;
1961   gdouble lat;
1962   gdouble lon;
1963 
1964   alpha = clutter_timeline_get_progress (timeline);
1965   lat = ctx->to_latitude - ctx->from_latitude;
1966   lon = ctx->to_longitude - ctx->from_longitude;
1967 
1968   champlain_view_center_on (ctx->view,
1969       ctx->from_latitude + alpha * lat,
1970       ctx->from_longitude + alpha * lon);
1971 }
1972 
1973 
1974 static void
timeline_completed(G_GNUC_UNUSED ClutterTimeline * timeline,ChamplainView * view)1975 timeline_completed (G_GNUC_UNUSED ClutterTimeline *timeline,
1976     ChamplainView *view)
1977 {
1978   DEBUG_LOG ()
1979 
1980   champlain_view_stop_go_to (view);
1981 }
1982 
1983 
1984 /**
1985  * champlain_view_stop_go_to:
1986  * @view: a #ChamplainView
1987  *
1988  * Stop the go to animation.  The view will stay where it was when the
1989  * animation was stopped.
1990  *
1991  * Since: 0.4
1992  */
1993 void
champlain_view_stop_go_to(ChamplainView * view)1994 champlain_view_stop_go_to (ChamplainView *view)
1995 {
1996   DEBUG_LOG ()
1997 
1998   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
1999 
2000   ChamplainViewPrivate *priv = view->priv;
2001 
2002   if (priv->goto_context == NULL)
2003     return;
2004 
2005   clutter_timeline_stop (priv->goto_context->timeline);
2006 
2007   g_object_unref (priv->goto_context->timeline);
2008 
2009   g_slice_free (GoToContext, priv->goto_context);
2010   priv->goto_context = NULL;
2011 
2012   g_signal_emit_by_name (view, "animation-completed::go-to", NULL);
2013 }
2014 
2015 
2016 /**
2017  * champlain_view_go_to:
2018  * @view: a #ChamplainView
2019  * @latitude: the longitude to center the map at
2020  * @longitude: the longitude to center the map at
2021  *
2022  * Move from the current position to these coordinates. All tiles in the
2023  * intermediate view WILL be loaded!
2024  *
2025  * Since: 0.4
2026  */
2027 void
champlain_view_go_to(ChamplainView * view,gdouble latitude,gdouble longitude)2028 champlain_view_go_to (ChamplainView *view,
2029     gdouble latitude,
2030     gdouble longitude)
2031 {
2032   DEBUG_LOG ()
2033 
2034   guint duration = view->priv->goto_duration;
2035 
2036   if (duration == 0) /* calculate duration from zoom level */
2037       duration = 500 * view->priv->zoom_level / 2.0;
2038 
2039   champlain_view_go_to_with_duration (view, latitude, longitude, duration);
2040 }
2041 
2042 
2043 static void
champlain_view_go_to_with_duration(ChamplainView * view,gdouble latitude,gdouble longitude,guint duration)2044 champlain_view_go_to_with_duration (ChamplainView *view,
2045     gdouble latitude,
2046     gdouble longitude,
2047     guint duration) /* In ms */
2048 {
2049   DEBUG_LOG ()
2050 
2051   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2052 
2053   if (duration == 0)
2054     {
2055       champlain_view_center_on (view, latitude, longitude);
2056       return;
2057     }
2058 
2059   GoToContext *ctx;
2060 
2061   ChamplainViewPrivate *priv = view->priv;
2062 
2063   champlain_view_stop_go_to (view);
2064 
2065   ctx = g_slice_new (GoToContext);
2066   ctx->from_latitude = priv->latitude;
2067   ctx->from_longitude = priv->longitude;
2068   ctx->to_latitude = CLAMP (latitude, priv->world_bbox->bottom, priv->world_bbox->top);
2069   ctx->to_longitude = CLAMP (longitude, priv->world_bbox->left, priv->world_bbox->right);
2070 
2071   ctx->view = view;
2072 
2073   /* We keep a reference for stop */
2074   priv->goto_context = ctx;
2075 
2076   /* A ClutterTimeline will be responsible for the animation,
2077    * at each frame, the current position will be computer and set
2078    * using champlain_view_center_on.  Timelines skip frames if the
2079    * computer is not fast enough, so we just need to set the duration.
2080    *
2081    * To have a nice animation, the duration should be longer if the zoom level
2082    * is higher and if the points are far away
2083    */
2084   ctx->timeline = clutter_timeline_new (duration);
2085   clutter_timeline_set_progress_mode (ctx->timeline, priv->goto_mode);
2086 
2087   g_signal_connect (ctx->timeline, "new-frame", G_CALLBACK (timeline_new_frame),
2088       ctx);
2089   g_signal_connect (ctx->timeline, "completed", G_CALLBACK (timeline_completed),
2090       view);
2091 
2092   clutter_timeline_start (ctx->timeline);
2093 }
2094 
2095 
2096 /**
2097  * champlain_view_zoom_in:
2098  * @view: a #ChamplainView
2099  *
2100  * Zoom in the map by one level.
2101  *
2102  * Since: 0.1
2103  */
2104 void
champlain_view_zoom_in(ChamplainView * view)2105 champlain_view_zoom_in (ChamplainView *view)
2106 {
2107   DEBUG_LOG ()
2108 
2109   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2110 
2111   champlain_view_set_zoom_level (view, view->priv->zoom_level + 1);
2112 }
2113 
2114 
2115 /**
2116  * champlain_view_zoom_out:
2117  * @view: a #ChamplainView
2118  *
2119  * Zoom out the map by one level.
2120  *
2121  * Since: 0.1
2122  */
2123 void
champlain_view_zoom_out(ChamplainView * view)2124 champlain_view_zoom_out (ChamplainView *view)
2125 {
2126   DEBUG_LOG ()
2127 
2128   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2129 
2130   champlain_view_set_zoom_level (view, view->priv->zoom_level - 1);
2131 }
2132 
2133 static void
paint_surface(ChamplainView * view,cairo_t * cr,cairo_surface_t * surface,double x,double y,double opacity)2134 paint_surface (ChamplainView *view,
2135     cairo_t *cr,
2136     cairo_surface_t *surface,
2137     double x,
2138     double y,
2139     double opacity)
2140 {
2141   ChamplainViewPrivate *priv = view->priv;
2142 
2143   gint map_width = get_map_width (view);
2144 
2145   cairo_set_source_surface (cr,
2146                             surface,
2147                             x, y);
2148   cairo_paint_with_alpha (cr, opacity);
2149 
2150   /* Paint each surface num_right_clones - 1 extra times on the right
2151    * (last clone is not actually visible) and once in the left
2152    * in order to horizontally wrap.
2153    */
2154   if (priv->hwrap)
2155     {
2156       gint i;
2157 
2158       for (i = 0; i < priv->num_right_clones + 1; i++)
2159         {
2160           /* Don't repaint original layer */
2161           if (i == 1)
2162             continue;
2163 
2164           cairo_set_source_surface (cr,
2165                             surface,
2166                             x + (i - 1) * map_width, y);
2167           cairo_paint_with_alpha (cr, opacity);
2168         }
2169     }
2170 }
2171 
2172 static void
layers_to_surface(ChamplainView * view,cairo_t * cr)2173 layers_to_surface (ChamplainView *view,
2174     cairo_t *cr)
2175 {
2176   ClutterActorIter iter;
2177   ClutterActor *child;
2178 
2179   clutter_actor_iter_init (&iter, view->priv->user_layers);
2180   while (clutter_actor_iter_next (&iter, &child))
2181     {
2182       ChamplainLayer *layer = CHAMPLAIN_LAYER (child);
2183       cairo_surface_t *surface;
2184 
2185       if (!CHAMPLAIN_IS_EXPORTABLE (layer))
2186         continue;
2187 
2188       surface = champlain_exportable_get_surface (CHAMPLAIN_EXPORTABLE (layer));
2189       if (!surface)
2190         continue;
2191 
2192       paint_surface (view, cr, surface, 0, 0, 255);
2193     }
2194 }
2195 
2196 
2197 /**
2198  * champlain_view_to_surface:
2199  * @view: a #ChamplainView
2200  * @include_layers: Set to %TRUE if you want to include layers
2201  *
2202  * Will generate a #cairo_surface_t that represents the current view
2203  * of the map. Without any markers or layers. If the current #ChamplainRenderer
2204  * used does not support this, this function will return %NULL.
2205  *
2206  * If @include_layers is set to %TRUE all layers that implement
2207  * #ChamplainExportable will be included in the surface.
2208  *
2209  * The #ChamplainView also need to be in #CHAMPLAIN_STATE_DONE state.
2210  *
2211  * Returns: (transfer full): a #cairo_surface_t or %NULL on failure. Free with
2212  *          cairo_surface_destroy() when done.
2213  */
2214 cairo_surface_t *
champlain_view_to_surface(ChamplainView * view,gboolean include_layers)2215 champlain_view_to_surface (ChamplainView *view,
2216     gboolean include_layers)
2217 {
2218   DEBUG_LOG ()
2219 
2220   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
2221 
2222   ChamplainViewPrivate *priv = view->priv;
2223   cairo_surface_t *surface;
2224   cairo_t *cr;
2225   ClutterActorIter iter;
2226   ClutterActor *child;
2227   gdouble width, height;
2228 
2229   if (priv->state != CHAMPLAIN_STATE_DONE)
2230     return NULL;
2231 
2232   width = clutter_actor_get_width (CLUTTER_ACTOR (view));
2233   height = clutter_actor_get_height (CLUTTER_ACTOR (view));
2234   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
2235   cr = cairo_create (surface);
2236 
2237   clutter_actor_iter_init (&iter, priv->map_layer);
2238   while (clutter_actor_iter_next (&iter, &child))
2239     {
2240       ChamplainTile *tile = CHAMPLAIN_TILE (child);
2241       guint tile_x = champlain_tile_get_x (tile);
2242       guint tile_y = champlain_tile_get_y (tile);
2243       guint tile_size = champlain_tile_get_size (tile);
2244 
2245       if (tile_in_tile_table (view, priv->tile_map, tile_x, tile_y))
2246         {
2247           cairo_surface_t *tile_surface;
2248           double x, y, opacity;
2249 
2250           tile_surface = champlain_exportable_get_surface (CHAMPLAIN_EXPORTABLE (tile));
2251           if (!tile_surface)
2252             {
2253               cairo_destroy (cr);
2254               cairo_surface_destroy (surface);
2255               return NULL;
2256             }
2257           opacity = ((double) clutter_actor_get_opacity (CLUTTER_ACTOR (tile))) / 255.0;
2258           x = ((double) tile_x * tile_size) - priv->viewport_x;
2259           y = ((double) tile_y * tile_size) - priv->viewport_y;
2260 
2261           paint_surface (view, cr, tile_surface, x, y, opacity);
2262         }
2263     }
2264 
2265     if (include_layers)
2266       layers_to_surface (view, cr);
2267 
2268     cairo_destroy (cr);
2269     return surface;
2270 }
2271 
2272 
2273 /**
2274  * champlain_view_set_zoom_level:
2275  * @view: a #ChamplainView
2276  * @zoom_level: the level of zoom, a guint between 1 and 20
2277  *
2278  * Changes the current level of zoom
2279  *
2280  * Since: 0.4
2281  */
2282 void
champlain_view_set_zoom_level(ChamplainView * view,guint zoom_level)2283 champlain_view_set_zoom_level (ChamplainView *view,
2284     guint zoom_level)
2285 {
2286   DEBUG_LOG ()
2287 
2288   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2289 
2290   view_set_zoom_level_at (view, zoom_level, FALSE, 0, 0);
2291 }
2292 
2293 
2294 /**
2295  * champlain_view_set_min_zoom_level:
2296  * @view: a #ChamplainView
2297  * @zoom_level: the level of zoom
2298  *
2299  * Changes the lowest allowed level of zoom
2300  *
2301  * Since: 0.4
2302  */
2303 void
champlain_view_set_min_zoom_level(ChamplainView * view,guint min_zoom_level)2304 champlain_view_set_min_zoom_level (ChamplainView *view,
2305     guint min_zoom_level)
2306 {
2307   DEBUG_LOG ()
2308 
2309   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2310 
2311   ChamplainViewPrivate *priv = view->priv;
2312 
2313   if (priv->min_zoom_level == min_zoom_level ||
2314       min_zoom_level > priv->max_zoom_level ||
2315       min_zoom_level < champlain_map_source_get_min_zoom_level (priv->map_source))
2316     return;
2317 
2318   priv->min_zoom_level = min_zoom_level;
2319   g_object_notify (G_OBJECT (view), "min-zoom-level");
2320 
2321   if (priv->zoom_level < min_zoom_level)
2322     champlain_view_set_zoom_level (view, min_zoom_level);
2323 }
2324 
2325 
2326 /**
2327  * champlain_view_set_max_zoom_level:
2328  * @view: a #ChamplainView
2329  * @zoom_level: the level of zoom
2330  *
2331  * Changes the highest allowed level of zoom
2332  *
2333  * Since: 0.4
2334  */
2335 void
champlain_view_set_max_zoom_level(ChamplainView * view,guint max_zoom_level)2336 champlain_view_set_max_zoom_level (ChamplainView *view,
2337     guint max_zoom_level)
2338 {
2339   DEBUG_LOG ()
2340 
2341   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2342 
2343   ChamplainViewPrivate *priv = view->priv;
2344 
2345   if (priv->max_zoom_level == max_zoom_level ||
2346       max_zoom_level < priv->min_zoom_level ||
2347       max_zoom_level > champlain_map_source_get_max_zoom_level (priv->map_source))
2348     return;
2349 
2350   priv->max_zoom_level = max_zoom_level;
2351   g_object_notify (G_OBJECT (view), "max-zoom-level");
2352 
2353   if (priv->zoom_level > max_zoom_level)
2354     champlain_view_set_zoom_level (view, max_zoom_level);
2355 }
2356 
2357 /**
2358  * champlain_view_get_world:
2359  * @view: a #ChamplainView
2360  *
2361  * Get the bounding box that represents the extent of the world.
2362  *
2363  * Returns: (transfer none): a #ChamplainBoundingBox that represents the current world
2364  *
2365  * Since: 0.12.11
2366  */
2367 ChamplainBoundingBox *
champlain_view_get_world(ChamplainView * view)2368 champlain_view_get_world (ChamplainView *view)
2369 {
2370   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
2371 
2372   ChamplainViewPrivate *priv = view->priv;
2373 
2374   return priv->world_bbox;
2375 }
2376 
2377 
2378 /**
2379  * champlain_view_set_world:
2380  * @view: a #ChamplainView
2381  * @bbox: (transfer none): the #ChamplainBoundingBox of the world
2382  *
2383  * Set a bounding box to limit the world to. No tiles will be loaded
2384  * outside of this bounding box. It will not be possible to scroll outside
2385  * of this bounding box.
2386  *
2387  * Since: 0.12.11
2388  */
2389 void
champlain_view_set_world(ChamplainView * view,ChamplainBoundingBox * bbox)2390 champlain_view_set_world (ChamplainView *view,
2391     ChamplainBoundingBox *bbox)
2392 {
2393   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2394   g_return_if_fail (bbox != NULL);
2395 
2396   if (!champlain_bounding_box_is_valid (bbox))
2397     return;
2398 
2399   ChamplainViewPrivate *priv = view->priv;
2400   gdouble latitude, longitude;
2401 
2402   bbox->left = CLAMP (bbox->left, CHAMPLAIN_MIN_LONGITUDE, CHAMPLAIN_MAX_LONGITUDE);
2403   bbox->bottom = CLAMP (bbox->bottom, CHAMPLAIN_MIN_LATITUDE, CHAMPLAIN_MAX_LATITUDE);
2404   bbox->right = CLAMP (bbox->right, CHAMPLAIN_MIN_LONGITUDE, CHAMPLAIN_MAX_LONGITUDE);
2405   bbox->top = CLAMP (bbox->top, CHAMPLAIN_MIN_LATITUDE, CHAMPLAIN_MAX_LATITUDE);
2406 
2407   if (priv->world_bbox)
2408     champlain_bounding_box_free (priv->world_bbox);
2409 
2410   priv->world_bbox = champlain_bounding_box_copy (bbox);
2411 
2412   if (!champlain_bounding_box_covers (priv->world_bbox, priv->latitude, priv->longitude))
2413     {
2414       champlain_bounding_box_get_center (priv->world_bbox, &latitude, &longitude);
2415       champlain_view_center_on (view, latitude, longitude);
2416     }
2417 }
2418 
2419 /**
2420  * champlain_view_add_layer:
2421  * @view: a #ChamplainView
2422  * @layer: a #ChamplainLayer
2423  *
2424  * Adds a new layer to the view
2425  *
2426  * Since: 0.2
2427  */
2428 void
champlain_view_add_layer(ChamplainView * view,ChamplainLayer * layer)2429 champlain_view_add_layer (ChamplainView *view,
2430     ChamplainLayer *layer)
2431 {
2432   DEBUG_LOG ()
2433 
2434   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2435   g_return_if_fail (CHAMPLAIN_IS_LAYER (layer));
2436 
2437   clutter_actor_add_child (view->priv->user_layers, CLUTTER_ACTOR (layer));
2438   champlain_layer_set_view (layer, view);
2439   clutter_actor_set_child_above_sibling (view->priv->user_layers, CLUTTER_ACTOR (layer), NULL);
2440 }
2441 
2442 
2443 /**
2444  * champlain_view_remove_layer:
2445  * @view: a #ChamplainView
2446  * @layer: a #ChamplainLayer
2447  *
2448  * Removes the given layer from the view
2449  *
2450  * Since: 0.4.1
2451  */
2452 void
champlain_view_remove_layer(ChamplainView * view,ChamplainLayer * layer)2453 champlain_view_remove_layer (ChamplainView *view,
2454     ChamplainLayer *layer)
2455 {
2456   DEBUG_LOG ()
2457 
2458   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2459   g_return_if_fail (CHAMPLAIN_IS_LAYER (layer));
2460 
2461   champlain_layer_set_view (layer, NULL);
2462 
2463   clutter_actor_remove_child (view->priv->user_layers, CLUTTER_ACTOR (layer));
2464 }
2465 
2466 
2467 /**
2468  * champlain_view_x_to_longitude:
2469  * @view: a #ChamplainView
2470  * @x: x coordinate of the view
2471  *
2472  * Converts the view's x coordinate to longitude.
2473  *
2474  * Returns: the longitude
2475  *
2476  * Since: 0.10
2477  */
2478 gdouble
champlain_view_x_to_longitude(ChamplainView * view,gdouble x)2479 champlain_view_x_to_longitude (ChamplainView *view,
2480     gdouble x)
2481 {
2482   ChamplainViewPrivate *priv = view->priv;
2483 
2484   DEBUG_LOG ()
2485 
2486   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
2487 
2488   return get_longitude (view, priv->zoom_level, x + priv->viewport_x);
2489 }
2490 
2491 
2492 /**
2493  * champlain_view_y_to_latitude:
2494  * @view: a #ChamplainView
2495  * @y: y coordinate of the view
2496  *
2497  * Converts the view's y coordinate to latitude.
2498  *
2499  * Returns: the latitude
2500  *
2501  * Since: 0.10
2502  */
2503 gdouble
champlain_view_y_to_latitude(ChamplainView * view,gdouble y)2504 champlain_view_y_to_latitude (ChamplainView *view,
2505     gdouble y)
2506 {
2507   ChamplainViewPrivate *priv = view->priv;
2508   gdouble latitude;
2509 
2510   DEBUG_LOG ()
2511 
2512   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
2513 
2514   latitude = champlain_map_source_get_latitude (priv->map_source,
2515         priv->zoom_level,
2516         y + priv->viewport_y);
2517 
2518   return latitude;
2519 }
2520 
2521 
2522 /**
2523  * champlain_view_longitude_to_x:
2524  * @view: a #ChamplainView
2525  * @longitude: the longitude
2526  *
2527  * Converts the longitude to view's x coordinate.
2528  *
2529  * Returns: the x coordinate
2530  *
2531  * Since: 0.10
2532  */
2533 gdouble
champlain_view_longitude_to_x(ChamplainView * view,gdouble longitude)2534 champlain_view_longitude_to_x (ChamplainView *view,
2535     gdouble longitude)
2536 {
2537   ChamplainViewPrivate *priv = view->priv;
2538   gdouble x;
2539 
2540   DEBUG_LOG ()
2541 
2542   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0);
2543 
2544   x = champlain_map_source_get_x (priv->map_source, priv->zoom_level, longitude);
2545 
2546   return x - priv->viewport_x;
2547 }
2548 
2549 
2550 /**
2551  * champlain_view_latitude_to_y:
2552  * @view: a #ChamplainView
2553  * @latitude: the latitude
2554  *
2555  * Converts the latitude to view's y coordinate.
2556  *
2557  * Returns: the y coordinate
2558  *
2559  * Since: 0.10
2560  */
2561 gdouble
champlain_view_latitude_to_y(ChamplainView * view,gdouble latitude)2562 champlain_view_latitude_to_y (ChamplainView *view,
2563     gdouble latitude)
2564 {
2565   ChamplainViewPrivate *priv = view->priv;
2566   gdouble y;
2567 
2568   DEBUG_LOG ()
2569 
2570   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0);
2571 
2572   y = champlain_map_source_get_y (priv->map_source, priv->zoom_level, latitude);
2573 
2574   return y - priv->viewport_y;
2575 }
2576 
2577 /**
2578  * champlain_view_get_viewport_anchor:
2579  * @view: a #ChamplainView
2580  * @anchor_x: (out): the x coordinate of the viewport anchor
2581  * @anchor_y: (out): the y coordinate of the viewport anchor
2582  *
2583  * Gets the x and y coordinate of the viewport anchor in respect to the layer origin.
2584  *
2585  * Since: 0.12.14
2586  */
2587 void
champlain_view_get_viewport_anchor(ChamplainView * view,gint * anchor_x,gint * anchor_y)2588 champlain_view_get_viewport_anchor (ChamplainView *view,
2589     gint *anchor_x,
2590     gint *anchor_y)
2591 {
2592   DEBUG_LOG ()
2593 
2594   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2595   ChamplainViewPrivate *priv = view->priv;
2596 
2597   champlain_viewport_get_anchor (CHAMPLAIN_VIEWPORT (priv->viewport), anchor_x, anchor_y);
2598 }
2599 
2600 /**
2601  * champlain_view_get_viewport_origin:
2602  * @view: a #ChamplainView
2603  * @x: (out): the x coordinate of the viewport
2604  * @y: (out): the y coordinate of the viewport
2605  *
2606  * Gets the x and y coordinate of the viewport in respect to the layer origin.
2607  *
2608  * Since: 0.10
2609  */
2610 void
champlain_view_get_viewport_origin(ChamplainView * view,gint * x,gint * y)2611 champlain_view_get_viewport_origin (ChamplainView *view,
2612     gint *x,
2613     gint *y)
2614 {
2615   DEBUG_LOG ()
2616 
2617   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
2618   ChamplainViewPrivate *priv = view->priv;
2619   gint anchor_x, anchor_y;
2620 
2621   champlain_viewport_get_anchor (CHAMPLAIN_VIEWPORT (priv->viewport), &anchor_x, &anchor_y);
2622 
2623   if (x)
2624     *x = priv->viewport_x - anchor_x;
2625 
2626   if (y)
2627     *y = priv->viewport_y - anchor_y;
2628 }
2629 
2630 
2631 static void
fill_background_tiles(ChamplainView * view)2632 fill_background_tiles (ChamplainView *view)
2633 {
2634   DEBUG_LOG ()
2635 
2636   ChamplainViewPrivate *priv = view->priv;
2637   ClutterActorIter iter;
2638   ClutterActor *child;
2639   gint x_count, y_count, x_first, y_first;
2640   gint x, y;
2641   gfloat width, height;
2642   gboolean have_children = TRUE;
2643 
2644   clutter_content_get_preferred_size (priv->background_content, &width, &height);
2645 
2646   x_count = ceil ((gfloat) priv->viewport_width / width) + 3;
2647   y_count = ceil ((gfloat) priv->viewport_height / height) + 3;
2648 
2649   x_first = (gint)priv->viewport_x / width - 1;
2650   y_first = (gint)priv->viewport_y / height - 1;
2651 
2652   clutter_actor_iter_init (&iter, priv->background_layer);
2653 
2654   for (x = x_first; x < x_first + x_count; ++x)
2655     {
2656       for (y = y_first; y < y_first + y_count; ++y)
2657         {
2658           if (!have_children || !clutter_actor_iter_next (&iter, &child))
2659             {
2660               have_children = FALSE;
2661               child = clutter_actor_new ();
2662               clutter_actor_set_size (child, width, height);
2663               clutter_actor_set_content (child, priv->background_content);
2664               clutter_actor_add_child (priv->background_layer, child);
2665             }
2666           champlain_viewport_set_actor_position (CHAMPLAIN_VIEWPORT (priv->viewport),
2667               child,
2668               (x * width) - priv->bg_offset_x,
2669               (y * height) - priv->bg_offset_y);
2670           child = clutter_actor_get_next_sibling (child);
2671         }
2672     }
2673 
2674   if (have_children)
2675     {
2676       while (clutter_actor_iter_next (&iter, &child))
2677           clutter_actor_iter_destroy (&iter);
2678     }
2679 }
2680 
2681 static void
tile_table_set(ChamplainView * view,GHashTable * table,gint tile_x,gint tile_y,gboolean value)2682 tile_table_set (ChamplainView *view,
2683     GHashTable *table,
2684     gint tile_x,
2685     gint tile_y,
2686     gboolean value)
2687 {
2688   ChamplainViewPrivate *priv = view->priv;
2689   gint64 count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
2690   gint64 *key = g_slice_alloc (sizeof(gint64));
2691   *key = (gint64)tile_y * count + tile_x;
2692   if (value)
2693     g_hash_table_insert (table, key, GINT_TO_POINTER (TRUE));
2694   else
2695     {
2696       g_hash_table_remove (table, key);
2697       g_slice_free (gint64, key);
2698     }
2699 }
2700 
2701 
2702 static gboolean
tile_in_tile_table(ChamplainView * view,GHashTable * table,gint tile_x,gint tile_y)2703 tile_in_tile_table (ChamplainView *view,
2704     GHashTable *table,
2705     gint tile_x,
2706     gint tile_y)
2707 {
2708   ChamplainViewPrivate *priv = view->priv;
2709   gint64 count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
2710   gint64 key = (gint64)tile_y * count + tile_x;
2711   return GPOINTER_TO_INT (g_hash_table_lookup (table, &key));
2712 }
2713 
2714 
2715 static void
load_tile_for_source(ChamplainView * view,ChamplainMapSource * source,gint opacity,gint size,gint x,gint y)2716 load_tile_for_source (ChamplainView *view,
2717     ChamplainMapSource *source,
2718     gint opacity,
2719     gint size,
2720     gint x,
2721     gint y)
2722 {
2723   ChamplainViewPrivate *priv = view->priv;
2724   ChamplainTile *tile = champlain_tile_new ();
2725 
2726   DEBUG ("Loading tile %d, %d, %d", priv->zoom_level, x, y);
2727 
2728   champlain_tile_set_x (tile, x);
2729   champlain_tile_set_y (tile, y);
2730   champlain_tile_set_zoom_level (tile, priv->zoom_level);
2731   champlain_tile_set_size (tile, size);
2732   clutter_actor_set_opacity (CLUTTER_ACTOR (tile), opacity);
2733 
2734   g_signal_connect (tile, "notify::state", G_CALLBACK (tile_state_notify), view);
2735   clutter_actor_add_child (priv->map_layer, CLUTTER_ACTOR (tile));
2736   champlain_viewport_set_actor_position (CHAMPLAIN_VIEWPORT (priv->viewport), CLUTTER_ACTOR (tile), x * size, y * size);
2737 
2738   /* updates champlain_view state automatically as
2739      notify::state signal is connected  */
2740   champlain_tile_set_state (tile, CHAMPLAIN_STATE_LOADING);
2741 
2742   champlain_map_source_fill_tile (source, tile);
2743 
2744   if (source != priv->map_source)
2745     g_object_set_data (G_OBJECT (tile), "overlay", GINT_TO_POINTER (TRUE));
2746 }
2747 
2748 
2749 static gboolean
fill_tile_cb(FillTileCallbackData * data)2750 fill_tile_cb (FillTileCallbackData *data)
2751 {
2752   DEBUG_LOG ()
2753 
2754   ChamplainView *view = data->view;
2755   ChamplainViewPrivate *priv = view->priv;
2756   gint x = data->x;
2757   gint y = data->y;
2758   gint size = data->size;
2759   gint zoom_level = data->zoom_level;
2760 
2761   if (zoom_level == priv->zoom_level &&
2762       data->map_source == priv->map_source &&
2763       !tile_in_tile_table (view, priv->tile_map, x, y) &&
2764       tile_in_tile_table (view, priv->visible_tiles, x, y))
2765     {
2766       GList *iter;
2767 
2768       load_tile_for_source (view, priv->map_source, 255, size, x, y);
2769       for (iter = priv->overlay_sources; iter; iter = iter->next)
2770         {
2771           gint opacity = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (iter->data), "opacity"));
2772           load_tile_for_source (view, iter->data, opacity, size, x, y);
2773         }
2774 
2775       tile_table_set (view, priv->tile_map, x, y, TRUE);
2776     }
2777 
2778   g_object_unref (view);
2779   g_slice_free (FillTileCallbackData, data);
2780 
2781   return FALSE;
2782 }
2783 
2784 static void
load_visible_tiles(ChamplainView * view,gboolean relocate)2785 load_visible_tiles (ChamplainView *view,
2786     gboolean relocate)
2787 {
2788   DEBUG_LOG ()
2789 
2790   ChamplainViewPrivate *priv = view->priv;
2791   ClutterActorIter iter;
2792   gint size;
2793   ClutterActor *child;
2794   gint x_count, y_count, column_count;
2795   guint min_x, min_y, max_x, max_y;
2796   gint arm_size, arm_max, turn;
2797   gint dirs[5] = { 0, 1, 0, -1, 0 };
2798   gint i, x, y;
2799 
2800   size = champlain_map_source_get_tile_size (priv->map_source);
2801   get_tile_bounds (view, &min_x, &min_y, &max_x, &max_y);
2802 
2803   x_count = ceil ((gfloat) priv->viewport_width / size) + 1;
2804   column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
2805 
2806   if (priv->hwrap)
2807     {
2808       priv->tile_x_first = priv->viewport_x / size;
2809       priv->tile_x_last = priv->tile_x_first + x_count;
2810     }
2811   else
2812     {
2813       priv->tile_x_first = CLAMP (priv->viewport_x / size, min_x, max_x);
2814       priv->tile_x_last = priv->tile_x_first + x_count;
2815       priv->tile_x_last = CLAMP (priv->tile_x_last, priv->tile_x_first, max_x);
2816       x_count = priv->tile_x_last - priv->tile_x_first;
2817     }
2818 
2819   y_count = ceil ((gfloat) priv->viewport_height / size) + 1;
2820   priv->tile_y_first = CLAMP (priv->viewport_y / size, min_y, max_y);
2821   priv->tile_y_last = priv->tile_y_first + y_count;
2822   priv->tile_y_last = CLAMP (priv->tile_y_last, priv->tile_y_first, max_y);
2823   y_count = priv->tile_y_last - priv->tile_y_first;
2824 
2825   DEBUG ("Range %d, %d to %d, %d", priv->tile_x_first, priv->tile_y_first, priv->tile_x_last, priv->tile_y_last);
2826 
2827   g_hash_table_remove_all (priv->visible_tiles);
2828   for (x = priv->tile_x_first; x < priv->tile_x_last; x++)
2829     for (y = priv->tile_y_first; y < priv->tile_y_last; y++)
2830       {
2831         gint tile_x = x;
2832 
2833         if (priv->hwrap)
2834           tile_x = x_to_wrap_x (tile_x, column_count);
2835 
2836         tile_table_set (view, priv->visible_tiles, tile_x, y, TRUE);
2837       }
2838 
2839   /* fill background tiles */
2840   if (priv->background_content != NULL)
2841       fill_background_tiles (view);
2842 
2843   /* Get rid of old tiles first */
2844   clutter_actor_iter_init (&iter, priv->map_layer);
2845   while (clutter_actor_iter_next (&iter, &child))
2846     {
2847       ChamplainTile *tile = CHAMPLAIN_TILE (child);
2848 
2849       gint tile_x = champlain_tile_get_x (tile);
2850       gint tile_y = champlain_tile_get_y (tile);
2851 
2852       if (!tile_in_tile_table (view, priv->visible_tiles, tile_x, tile_y))
2853         {
2854           champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
2855           clutter_actor_iter_destroy (&iter);
2856           tile_table_set (view, priv->tile_map, tile_x, tile_y, FALSE);
2857         }
2858       else if (relocate)
2859         champlain_viewport_set_actor_position (CHAMPLAIN_VIEWPORT (priv->viewport), CLUTTER_ACTOR (tile), tile_x * size, tile_y * size);
2860     }
2861 
2862   /* Load new tiles if needed */
2863   x = priv->tile_x_first + x_count / 2 - 1;
2864   y = priv->tile_y_first + y_count / 2 - 1;
2865   arm_max = MAX (x_count, y_count) + 2;
2866   arm_size = 1;
2867 
2868   for (turn = 0; arm_size < arm_max; turn++)
2869     {
2870       for (i = 0; i < arm_size; i++)
2871         {
2872           gint tile_x = x;
2873 
2874           if (priv->hwrap)
2875             tile_x = x_to_wrap_x (tile_x, column_count);
2876 
2877           if (!tile_in_tile_table (view, priv->tile_map, tile_x, y) &&
2878               tile_in_tile_table (view, priv->visible_tiles, tile_x, y))
2879             {
2880               FillTileCallbackData *data;
2881 
2882               DEBUG ("Loading tile %d, %d, %d", priv->zoom_level, x, y);
2883 
2884               data = g_slice_new (FillTileCallbackData);
2885               data->x = tile_x;
2886               data->y = y;
2887               data->size = size;
2888               data->zoom_level = priv->zoom_level;
2889               /* used only to check that the map source didn't change before the
2890                * idle function is called */
2891               data->map_source = priv->map_source;
2892               data->view = g_object_ref (view);
2893 
2894               g_idle_add_full (CLUTTER_PRIORITY_REDRAW, (GSourceFunc) fill_tile_cb, data, NULL);
2895             }
2896 
2897           x += dirs[turn % 4 + 1];
2898           y += dirs[turn % 4];
2899         }
2900 
2901       if (turn % 2 == 1)
2902         arm_size++;
2903     }
2904 }
2905 
2906 
2907 static void
remove_all_tiles(ChamplainView * view)2908 remove_all_tiles (ChamplainView *view)
2909 {
2910   DEBUG_LOG ()
2911 
2912   ChamplainViewPrivate *priv = view->priv;
2913   ClutterActorIter iter;
2914   ClutterActor *child;
2915 
2916   clutter_actor_destroy_all_children (priv->zoom_layer);
2917 
2918   clutter_actor_iter_init (&iter, priv->map_layer);
2919   while (clutter_actor_iter_next (&iter, &child))
2920     champlain_tile_set_state (CHAMPLAIN_TILE (child), CHAMPLAIN_STATE_DONE);
2921 
2922   g_hash_table_remove_all (priv->tile_map);
2923 
2924   clutter_actor_destroy_all_children (priv->map_layer);
2925 }
2926 
2927 
2928 /**
2929  * champlain_view_reload_tiles:
2930  * @view: a #ChamplainView
2931  *
2932  * Reloads all visible tiles.
2933  *
2934  * Since: 0.8
2935  */
2936 void
champlain_view_reload_tiles(ChamplainView * view)2937 champlain_view_reload_tiles (ChamplainView *view)
2938 {
2939   DEBUG_LOG ()
2940 
2941   remove_all_tiles (view);
2942 
2943   load_visible_tiles (view, FALSE);
2944 }
2945 
2946 
2947 static gboolean
remove_zoom_actor_cb(ChamplainView * view)2948 remove_zoom_actor_cb (ChamplainView *view)
2949 {
2950   ChamplainViewPrivate *priv = view->priv;
2951 
2952   clutter_actor_destroy_all_children (priv->zoom_layer);
2953   priv->zoom_actor_timeout = 0;
2954   return FALSE;
2955 }
2956 
2957 
2958 static void
tile_state_notify(ChamplainTile * tile,G_GNUC_UNUSED GParamSpec * pspec,ChamplainView * view)2959 tile_state_notify (ChamplainTile *tile,
2960     G_GNUC_UNUSED GParamSpec *pspec,
2961     ChamplainView *view)
2962 {
2963   DEBUG_LOG ()
2964 
2965   ChamplainState tile_state = champlain_tile_get_state (tile);
2966   ChamplainViewPrivate *priv = view->priv;
2967 
2968   if (tile_state == CHAMPLAIN_STATE_LOADING)
2969     {
2970       if (priv->tiles_loading == 0)
2971         {
2972           priv->state = CHAMPLAIN_STATE_LOADING;
2973           g_object_notify (G_OBJECT (view), "state");
2974         }
2975       priv->tiles_loading++;
2976     }
2977   else if (tile_state == CHAMPLAIN_STATE_DONE)
2978     {
2979       if (priv->tiles_loading > 0)
2980         priv->tiles_loading--;
2981       if (priv->tiles_loading == 0)
2982         {
2983           priv->state = CHAMPLAIN_STATE_DONE;
2984           g_object_notify (G_OBJECT (view), "state");
2985           if (clutter_actor_get_n_children (priv->zoom_layer) > 0)
2986             priv->zoom_actor_timeout = g_timeout_add_seconds_full (CLUTTER_PRIORITY_REDRAW, 1, (GSourceFunc) remove_zoom_actor_cb, view, NULL);
2987         }
2988     }
2989 }
2990 
2991 
2992 /**
2993  * champlain_view_set_map_source:
2994  * @view: a #ChamplainView
2995  * @map_source: a #ChamplainMapSource
2996  *
2997  * Changes the currently used map source. #g_object_unref() will be called on
2998  * the previous one.
2999  *
3000  * As a side effect, changing the primary map source will also clear all
3001  * secondary map sources.
3002  *
3003  * Since: 0.4
3004  */
3005 void
champlain_view_set_map_source(ChamplainView * view,ChamplainMapSource * source)3006 champlain_view_set_map_source (ChamplainView *view,
3007     ChamplainMapSource *source)
3008 {
3009   DEBUG_LOG ()
3010 
3011   g_return_if_fail (CHAMPLAIN_IS_VIEW (view) &&
3012       CHAMPLAIN_IS_MAP_SOURCE (source));
3013 
3014   ChamplainViewPrivate *priv = view->priv;
3015   guint source_min_zoom;
3016   guint source_max_zoom;
3017 
3018   if (priv->map_source == source)
3019     return;
3020 
3021   g_object_unref (priv->map_source);
3022   priv->map_source = g_object_ref_sink (source);
3023 
3024   g_list_free_full (priv->overlay_sources, g_object_unref);
3025   priv->overlay_sources = NULL;
3026 
3027   source_min_zoom = champlain_map_source_get_min_zoom_level (priv->map_source);
3028   champlain_view_set_min_zoom_level (view, source_min_zoom);
3029   source_max_zoom = champlain_map_source_get_max_zoom_level (priv->map_source);
3030   champlain_view_set_max_zoom_level (view, source_max_zoom);
3031 
3032   /* Keep same zoom level if the new map supports it */
3033   if (priv->zoom_level > priv->max_zoom_level)
3034     {
3035       priv->zoom_level = priv->max_zoom_level;
3036       g_object_notify (G_OBJECT (view), "zoom-level");
3037     }
3038   else if (priv->zoom_level < priv->min_zoom_level)
3039     {
3040       priv->zoom_level = priv->min_zoom_level;
3041       g_object_notify (G_OBJECT (view), "zoom-level");
3042     }
3043 
3044   champlain_view_reload_tiles (view);
3045 
3046   g_object_notify (G_OBJECT (view), "map-source");
3047 }
3048 
3049 
3050 /**
3051  * champlain_view_set_deceleration:
3052  * @view: a #ChamplainView
3053  * @rate: a #gdouble between 1.001 and 2.0
3054  *
3055  * The deceleration rate for the kinetic mode.
3056  *
3057  * Since: 0.4
3058  */
3059 void
champlain_view_set_deceleration(ChamplainView * view,gdouble rate)3060 champlain_view_set_deceleration (ChamplainView *view,
3061     gdouble rate)
3062 {
3063   DEBUG_LOG ()
3064 
3065   g_return_if_fail (CHAMPLAIN_IS_VIEW (view) &&
3066       rate < 2.0 && rate > 1.0001);
3067 
3068   g_object_set (view->priv->kinetic_scroll, "decel-rate", rate, NULL);
3069   g_object_notify (G_OBJECT (view), "deceleration");
3070 }
3071 
3072 
3073 /**
3074  * champlain_view_set_kinetic_mode:
3075  * @view: a #ChamplainView
3076  * @kinetic: TRUE for kinetic mode, FALSE for push mode
3077  *
3078  * Determines the way the view reacts to scroll events.
3079  *
3080  * Since: 0.10
3081  */
3082 void
champlain_view_set_kinetic_mode(ChamplainView * view,gboolean kinetic)3083 champlain_view_set_kinetic_mode (ChamplainView *view,
3084     gboolean kinetic)
3085 {
3086   DEBUG_LOG ()
3087 
3088   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3089 
3090   ChamplainViewPrivate *priv = view->priv;
3091 
3092   priv->kinetic_mode = kinetic;
3093   g_object_set (view->priv->kinetic_scroll, "mode", kinetic, NULL);
3094   g_object_notify (G_OBJECT (view), "kinetic-mode");
3095 }
3096 
3097 
3098 /**
3099  * champlain_view_set_keep_center_on_resize:
3100  * @view: a #ChamplainView
3101  * @value: a #gboolean
3102  *
3103  * Keep the current centered position when resizing the view.
3104  *
3105  * Since: 0.4
3106  */
3107 void
champlain_view_set_keep_center_on_resize(ChamplainView * view,gboolean value)3108 champlain_view_set_keep_center_on_resize (ChamplainView *view,
3109     gboolean value)
3110 {
3111   DEBUG_LOG ()
3112 
3113   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3114 
3115   view->priv->keep_center_on_resize = value;
3116   g_object_notify (G_OBJECT (view), "keep-center-on-resize");
3117 }
3118 
3119 
3120 /**
3121  * champlain_view_set_zoom_on_double_click:
3122  * @view: a #ChamplainView
3123  * @value: a #gboolean
3124  *
3125  * Should the view zoom in and recenter when the user double click on the map.
3126  *
3127  * Since: 0.4
3128  */
3129 void
champlain_view_set_zoom_on_double_click(ChamplainView * view,gboolean value)3130 champlain_view_set_zoom_on_double_click (ChamplainView *view,
3131     gboolean value)
3132 {
3133   DEBUG_LOG ()
3134 
3135   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3136 
3137   view->priv->zoom_on_double_click = value;
3138   g_object_notify (G_OBJECT (view), "zoom-on-double-click");
3139 }
3140 
3141 
3142 /**
3143  * champlain_view_set_animate_zoom:
3144  * @view: a #ChamplainView
3145  * @value: a #gboolean
3146  *
3147  * Should the view animate zoom level changes.
3148  *
3149  * Since: 0.12
3150  */
3151 void
champlain_view_set_animate_zoom(ChamplainView * view,gboolean value)3152 champlain_view_set_animate_zoom (ChamplainView *view,
3153     gboolean value)
3154 {
3155   DEBUG_LOG ()
3156 
3157   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3158 
3159   view->priv->animate_zoom = value;
3160   g_object_notify (G_OBJECT (view), "animate-zoom");
3161 }
3162 
3163 
3164 /**
3165  * champlain_view_ensure_visible:
3166  * @view: a #ChamplainView
3167  * @bbox: bounding box of the area that should be visible
3168  * @animate: TRUE to perform animation, FALSE otherwise
3169  *
3170  * Changes the map's zoom level and center to make sure the given area
3171  * is visible
3172  *
3173  * Since: 0.10
3174  */
3175 void
champlain_view_ensure_visible(ChamplainView * view,ChamplainBoundingBox * bbox,gboolean animate)3176 champlain_view_ensure_visible (ChamplainView *view,
3177     ChamplainBoundingBox *bbox,
3178     gboolean animate)
3179 {
3180   DEBUG_LOG ()
3181 
3182   ChamplainViewPrivate *priv = view->priv;
3183   guint zoom_level = priv->zoom_level;
3184   gboolean good_size = FALSE;
3185   gdouble lat, lon;
3186 
3187   if (!champlain_bounding_box_is_valid (bbox))
3188     return;
3189 
3190   champlain_bounding_box_get_center (bbox, &lat, &lon);
3191 
3192   DEBUG ("Zone to expose (%f, %f) to (%f, %f)", bbox->bottom, bbox->left, bbox->top, bbox->right);
3193   do
3194     {
3195       gint min_x, min_y, max_x, max_y;
3196 
3197       min_x = champlain_map_source_get_x (priv->map_source, zoom_level, bbox->left);
3198       min_y = champlain_map_source_get_y (priv->map_source, zoom_level, bbox->bottom);
3199       max_x = champlain_map_source_get_x (priv->map_source, zoom_level, bbox->right);
3200       max_y = champlain_map_source_get_y (priv->map_source, zoom_level, bbox->top);
3201 
3202       if (min_y - max_y <= priv->viewport_height &&
3203           max_x - min_x <= priv->viewport_width)
3204         good_size = TRUE;
3205       else
3206         zoom_level--;
3207 
3208       if (zoom_level <= priv->min_zoom_level)
3209         {
3210           zoom_level = priv->min_zoom_level;
3211           good_size = TRUE;
3212         }
3213     } while (!good_size);
3214 
3215   DEBUG ("Ideal zoom level is %d", zoom_level);
3216   champlain_view_set_zoom_level (view, zoom_level);
3217   if (animate)
3218     champlain_view_go_to (view, lat, lon);
3219   else
3220     champlain_view_center_on (view, lat, lon);
3221 }
3222 
3223 
3224 /**
3225  * champlain_view_ensure_layers_visible:
3226  * @view: a #ChamplainView
3227  * @animate: TRUE to perform animation, FALSE otherwise
3228  *
3229  * Changes the map's zoom level and center to make sure that the bounding
3230  * boxes of all inserted layers are visible.
3231  *
3232  * Since: 0.10
3233  */
3234 void
champlain_view_ensure_layers_visible(ChamplainView * view,gboolean animate)3235 champlain_view_ensure_layers_visible (ChamplainView *view,
3236     gboolean animate)
3237 {
3238   DEBUG_LOG ()
3239 
3240   ClutterActorIter iter;
3241   ClutterActor *child;
3242   ChamplainBoundingBox *bbox;
3243 
3244   bbox = champlain_bounding_box_new ();
3245 
3246   clutter_actor_iter_init (&iter, view->priv->user_layers);
3247   while (clutter_actor_iter_next (&iter, &child))
3248     {
3249       ChamplainLayer *layer = CHAMPLAIN_LAYER (child);
3250       ChamplainBoundingBox *other;
3251 
3252       other = champlain_layer_get_bounding_box (layer);
3253       champlain_bounding_box_compose (bbox, other);
3254       champlain_bounding_box_free (other);
3255     }
3256 
3257   champlain_view_ensure_visible (view, bbox, animate);
3258 
3259   champlain_bounding_box_free (bbox);
3260 }
3261 
3262 
3263 /**
3264  * champlain_view_set_background_pattern:
3265  * @view: a #ChamplainView
3266  * @background: The background texture
3267  *
3268  * Sets the background texture displayed behind the map. Setting the background
3269  * pattern affects performence slightly - use reasonably large patterns for
3270  * better performance.
3271  *
3272  * Since: 0.12.4
3273  */
3274 void
champlain_view_set_background_pattern(ChamplainView * view,ClutterContent * background)3275 champlain_view_set_background_pattern (ChamplainView *view,
3276     ClutterContent *background)
3277 {
3278   DEBUG_LOG ()
3279 
3280   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3281 
3282   ChamplainViewPrivate *priv = view->priv;
3283 
3284   if (priv->background_content)
3285     g_object_unref (priv->background_content);
3286 
3287   priv->background_content = g_object_ref_sink (background);
3288   clutter_actor_destroy_all_children (priv->background_layer);
3289 }
3290 
3291 
3292 /**
3293  * champlain_view_get_background_pattern:
3294  * @view: a #ChamplainView
3295  *
3296  * Gets the current background texture displayed behind the map.
3297  *
3298  * Returns: (transfer none): The texture.
3299  *
3300  * Since: 0.12.4
3301  */
3302 ClutterContent *
champlain_view_get_background_pattern(ChamplainView * view)3303 champlain_view_get_background_pattern (ChamplainView *view)
3304 {
3305   DEBUG_LOG ()
3306 
3307   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
3308 
3309   ChamplainViewPrivate *priv = view->priv;
3310 
3311   return priv->background_content;
3312 }
3313 
3314 
3315 /**
3316  * champlain_view_set_horizontal_wrap:
3317  * @view: a #ChamplainView
3318  * @wrap: %TRUE to enable horizontal wrapping
3319  *
3320  * Sets the value of the #ChamplainView:horizontal-wrap property.
3321  */
3322 void
champlain_view_set_horizontal_wrap(ChamplainView * view,gboolean wrap)3323 champlain_view_set_horizontal_wrap (ChamplainView *view,
3324     gboolean wrap)
3325 {
3326   DEBUG_LOG ()
3327 
3328   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3329 
3330   ChamplainViewPrivate *priv = view->priv;
3331 
3332   if (priv->hwrap == wrap)
3333     return;
3334 
3335   priv->hwrap = wrap;
3336 
3337   if (priv->hwrap)
3338     {
3339       g_signal_connect (priv->viewport, "motion-event",
3340         G_CALLBACK (viewport_motion_cb), view);
3341       g_signal_connect (priv->viewport, "button-press-event",
3342         G_CALLBACK (viewport_press_cb), view);
3343       update_clones (view);
3344     }
3345   else
3346     {
3347       g_list_free_full (priv->map_clones, (GDestroyNotify) clutter_actor_destroy);
3348       g_list_free_full (priv->user_layer_slots, (GDestroyNotify) exclusive_destroy_clone);
3349       priv->map_clones = NULL;
3350       priv->user_layer_slots = NULL;
3351       g_signal_handlers_disconnect_by_func (priv->viewport, viewport_motion_cb, view);
3352       g_signal_handlers_disconnect_by_func (priv->viewport, viewport_press_cb, view);
3353       clutter_actor_set_x (priv->user_layers, 0);
3354     }
3355   resize_viewport (view);
3356 
3357   gint map_width = get_map_width (view);
3358   if (priv->hwrap)
3359     position_viewport (view, x_to_wrap_x (priv->viewport_x, map_width), priv->viewport_y);
3360   else
3361     position_viewport (view, priv->viewport_x - ((gint)priv->viewport_width / map_width / 2) * map_width, priv->viewport_y);
3362 
3363   load_visible_tiles (view, FALSE);
3364 }
3365 
3366 
3367 /**
3368  * champlain_view_get_horizontal_wrap:
3369  * @view: a #ChamplainView
3370  *
3371  * Returns the value of the #ChamplainView:horizontal-wrap property.
3372  *
3373  * Returns: %TRUE if #ChamplainView:horizontal-wrap is set.
3374  *
3375  */
3376 gboolean
champlain_view_get_horizontal_wrap(ChamplainView * view)3377 champlain_view_get_horizontal_wrap (ChamplainView *view)
3378 {
3379   DEBUG_LOG ()
3380 
3381   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), FALSE);
3382 
3383   ChamplainViewPrivate *priv = view->priv;
3384 
3385   return priv->hwrap;
3386 }
3387 
3388 
3389 static void
position_zoom_actor(ChamplainView * view)3390 position_zoom_actor (ChamplainView *view)
3391 {
3392   ChamplainViewPrivate *priv = view->priv;
3393   gdouble x, y;
3394   gdouble deltazoom;
3395 
3396   clutter_actor_destroy_all_children (priv->zoom_layer);
3397   if (priv->zoom_actor_timeout != 0)
3398     {
3399       g_source_remove (priv->zoom_actor_timeout);
3400       priv->zoom_actor_timeout = 0;
3401     }
3402 
3403   ClutterActor *zoom_actor = clutter_actor_get_first_child (priv->zoom_overlay_actor);
3404   clutter_actor_set_pivot_point (zoom_actor, 0.0, 0.0);
3405 
3406   g_object_ref (zoom_actor);
3407   clutter_actor_remove_child(priv->zoom_overlay_actor, zoom_actor);
3408   clutter_actor_add_child (priv->zoom_layer, zoom_actor);
3409   g_object_unref (zoom_actor);
3410 
3411   deltazoom = pow (2, (gdouble)priv->zoom_level - (gdouble)priv->anim_start_zoom_level);
3412   x = priv->zoom_actor_viewport_x * deltazoom;
3413   y = priv->zoom_actor_viewport_y * deltazoom;
3414 
3415   champlain_viewport_set_actor_position (CHAMPLAIN_VIEWPORT (priv->viewport), zoom_actor, x, y);
3416 }
3417 
3418 
3419 static void
zoom_animation_completed(ClutterActor * actor,const gchar * transition_name,gboolean is_finished,ChamplainView * view)3420 zoom_animation_completed (ClutterActor *actor,
3421     const gchar *transition_name,
3422     gboolean is_finished,
3423     ChamplainView *view)
3424 {
3425   ChamplainViewPrivate *priv = view->priv;
3426 
3427   priv->animating_zoom = FALSE;
3428   position_zoom_actor (view);
3429   clutter_actor_show (priv->user_layers);
3430   if (priv->hwrap)
3431     update_clones (view);
3432 
3433   if (priv->tiles_loading == 0)
3434     clutter_actor_destroy_all_children (priv->zoom_layer);
3435 
3436   g_signal_handlers_disconnect_by_func (actor, zoom_animation_completed, view);
3437   g_signal_emit_by_name (view, "animation-completed::zoom", NULL);
3438 }
3439 
3440 
3441 static void
show_zoom_actor(ChamplainView * view,guint zoom_level,gdouble x,gdouble y)3442 show_zoom_actor (ChamplainView *view,
3443     guint zoom_level,
3444     gdouble x,
3445     gdouble y)
3446 {
3447   DEBUG_LOG ()
3448 
3449   ChamplainViewPrivate *priv = view->priv;
3450   ClutterActor *zoom_actor = NULL;
3451   gdouble deltazoom;
3452 
3453   if (!priv->animating_zoom)
3454     {
3455       ClutterActorIter iter;
3456       ClutterActor *child;
3457       ClutterActor *tile_container;
3458       gint size;
3459       gint x_first, y_first;
3460       gdouble zoom_actor_width, zoom_actor_height;
3461       gint column_count;
3462       gdouble deltax, deltay;
3463       guint min_x, min_y, max_x, max_y;
3464 
3465       get_tile_bounds (view, &min_x, &min_y, &max_x, &max_y);
3466       size = champlain_map_source_get_tile_size (priv->map_source);
3467 
3468       column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
3469 
3470       x_first = CLAMP (priv->viewport_x / size, min_x, max_x);
3471       y_first = CLAMP (priv->viewport_y / size, min_y, max_y);
3472 
3473       clutter_actor_destroy_all_children (priv->zoom_overlay_actor);
3474       zoom_actor = clutter_actor_new ();
3475       clutter_actor_add_child (priv->zoom_overlay_actor, zoom_actor);
3476 
3477       deltax = priv->viewport_x - x_first * size;
3478       deltay = priv->viewport_y - y_first * size;
3479 
3480       priv->anim_start_zoom_level = priv->zoom_level;
3481       priv->zoom_actor_viewport_x = priv->viewport_x - deltax;
3482       priv->zoom_actor_viewport_y = priv->viewport_y - deltay;
3483 
3484       tile_container = clutter_actor_new ();
3485       clutter_actor_iter_init (&iter, priv->map_layer);
3486       while (clutter_actor_iter_next (&iter, &child))
3487         {
3488           ChamplainTile *tile = CHAMPLAIN_TILE (child);
3489           gint tile_x = champlain_tile_get_x (tile);
3490           gint tile_y = champlain_tile_get_y (tile);
3491           gboolean overlay = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (tile), "overlay"));
3492 
3493           champlain_tile_set_state (tile, CHAMPLAIN_STATE_DONE);
3494 
3495           g_object_ref (CLUTTER_ACTOR (tile));
3496           clutter_actor_iter_remove (&iter);
3497           clutter_actor_add_child (tile_container, CLUTTER_ACTOR (tile));
3498           g_object_unref (CLUTTER_ACTOR (tile));
3499 
3500           /* We move overlay tiles to the zoom actor so they get properly reparented
3501              and destroyed as needed, but we hide them for performance reasons */
3502           if (overlay)
3503             clutter_actor_hide (CLUTTER_ACTOR (tile));
3504 
3505           clutter_actor_set_position (CLUTTER_ACTOR (tile), (tile_x - x_first) * size, (tile_y - y_first) * size);
3506         }
3507       clutter_actor_add_child (zoom_actor, tile_container);
3508 
3509       /* The tile_container is cloned and its clones are also added to the zoom_actor
3510        * in order to horizontally wrap. Moreover, the old clones are hidden while the zooming
3511        * animation is runnning.
3512        */
3513       if (priv->hwrap)
3514         {
3515           GList *old_clone = priv->map_clones;
3516           gint i;
3517 
3518           for (i = 0; i < priv->num_right_clones + 2; i++)
3519             {
3520               gfloat tiles_x;
3521               ClutterActor *clone;
3522 
3523               if (i == 1)
3524                 continue;
3525 
3526               clone = clutter_clone_new (tile_container);
3527 
3528               clutter_actor_hide (CLUTTER_ACTOR (old_clone->data));
3529 
3530               clutter_actor_get_position (tile_container, &tiles_x, NULL);
3531               clutter_actor_set_x (clone, tiles_x + ((i - 1) * column_count * size));
3532 
3533               clutter_actor_add_child (zoom_actor, clone);
3534 
3535               old_clone = old_clone->next;
3536             }
3537         }
3538 
3539       zoom_actor_width = clutter_actor_get_width (zoom_actor);
3540       zoom_actor_height = clutter_actor_get_height (zoom_actor);
3541 
3542       clutter_actor_set_pivot_point (zoom_actor, (x + deltax) / zoom_actor_width, (y + deltay) / zoom_actor_height);
3543       clutter_actor_set_position (zoom_actor, -deltax, -deltay);
3544     }
3545   else
3546     zoom_actor = clutter_actor_get_first_child (priv->zoom_overlay_actor);
3547 
3548   deltazoom = pow (2.0, (gdouble)zoom_level - priv->anim_start_zoom_level);
3549 
3550   if (priv->animate_zoom)
3551     {
3552       clutter_actor_set_opacity (priv->map_layer, 0);
3553 
3554       clutter_actor_destroy_all_children (priv->zoom_layer);
3555 
3556       clutter_actor_save_easing_state (zoom_actor);
3557       clutter_actor_set_easing_mode (zoom_actor, CLUTTER_EASE_IN_OUT_QUAD);
3558       clutter_actor_set_easing_duration (zoom_actor, 350);
3559       clutter_actor_set_scale (zoom_actor, deltazoom, deltazoom);
3560       clutter_actor_restore_easing_state (zoom_actor);
3561 
3562       clutter_actor_save_easing_state (priv->map_layer);
3563       clutter_actor_set_easing_mode (priv->map_layer, CLUTTER_EASE_IN_EXPO);
3564       clutter_actor_set_easing_duration (priv->map_layer, 350);
3565       clutter_actor_set_opacity (priv->map_layer, 255);
3566       clutter_actor_restore_easing_state (priv->map_layer);
3567 
3568       if (!priv->animating_zoom)
3569         {
3570           if (priv->hwrap)
3571             {
3572               GList *slot;
3573               for (slot = priv->user_layer_slots; slot != NULL; slot = slot->next)
3574                 clutter_actor_hide (CLUTTER_ACTOR (slot->data));
3575             }
3576           else
3577             clutter_actor_hide (priv->user_layers);
3578 
3579           g_signal_connect (zoom_actor, "transition-stopped::scale-x", G_CALLBACK (zoom_animation_completed), view);
3580         }
3581 
3582       priv->animating_zoom = TRUE;
3583     }
3584   else
3585     {
3586       clutter_actor_set_scale (zoom_actor, deltazoom, deltazoom);
3587       if (priv->hwrap)
3588         update_clones (view);
3589     }
3590 }
3591 
3592 static void
get_x_y_for_zoom_level(ChamplainView * view,guint zoom_level,gint offset_x,gint offset_y,gdouble * new_x,gdouble * new_y)3593 get_x_y_for_zoom_level (ChamplainView *view,
3594                         guint zoom_level,
3595                         gint offset_x,
3596                         gint offset_y,
3597                         gdouble *new_x,
3598                         gdouble *new_y)
3599 {
3600   ChamplainViewPrivate *priv = view->priv;
3601   gdouble deltazoom;
3602 
3603   deltazoom = pow (2, (gdouble) zoom_level - (gdouble) priv->zoom_level);
3604 
3605   *new_x = (priv->viewport_x + offset_x) * deltazoom - offset_x;
3606   *new_y = (priv->viewport_y + offset_y) * deltazoom - offset_y;
3607 }
3608 
3609 /* Sets the zoom level, leaving the (x, y) at the exact same point in the view */
3610 static gboolean
view_set_zoom_level_at(ChamplainView * view,guint zoom_level,gboolean use_event_coord,gint x,gint y)3611 view_set_zoom_level_at (ChamplainView *view,
3612     guint zoom_level,
3613     gboolean use_event_coord,
3614     gint x,
3615     gint y)
3616 {
3617   DEBUG_LOG ()
3618 
3619   ChamplainViewPrivate *priv = view->priv;
3620   gdouble new_x, new_y;
3621   gdouble offset_x = x;
3622   gdouble offset_y = y;
3623 
3624   if (zoom_level == priv->zoom_level || ZOOM_LEVEL_OUT_OF_RANGE (priv, zoom_level))
3625     return FALSE;
3626 
3627   champlain_view_stop_go_to (view);
3628 
3629   if (!use_event_coord)
3630     {
3631       offset_x = priv->viewport_width / 2.0;
3632       offset_y = priv->viewport_height / 2.0;
3633     }
3634 
3635   /* don't do anything when view not yet realized */
3636   if (clutter_actor_is_realized (CLUTTER_ACTOR (view)))
3637     show_zoom_actor (view, zoom_level, offset_x, offset_y);
3638 
3639   get_x_y_for_zoom_level (view, zoom_level, offset_x, offset_y, &new_x, &new_y);
3640 
3641   priv->zoom_level = zoom_level;
3642 
3643   if (clutter_actor_is_realized (CLUTTER_ACTOR (view)))
3644     {
3645       resize_viewport (view);
3646       remove_all_tiles (view);
3647       if (priv->hwrap)
3648         position_viewport (view, x_to_wrap_x (new_x, get_map_width (view)), new_y);
3649       else
3650         position_viewport (view, new_x, new_y);
3651       load_visible_tiles (view, FALSE);
3652 
3653       if (!priv->animate_zoom)
3654         position_zoom_actor (view);
3655     }
3656 
3657   g_object_notify (G_OBJECT (view), "zoom-level");
3658   return TRUE;
3659 }
3660 
3661 
3662 /**
3663  * champlain_view_get_zoom_level:
3664  * @view: a #ChamplainView
3665  *
3666  * Gets the view's current zoom level.
3667  *
3668  * Returns: the view's current zoom level.
3669  *
3670  * Since: 0.4
3671  */
3672 guint
champlain_view_get_zoom_level(ChamplainView * view)3673 champlain_view_get_zoom_level (ChamplainView *view)
3674 {
3675   DEBUG_LOG ()
3676 
3677   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0);
3678 
3679   return view->priv->zoom_level;
3680 }
3681 
3682 
3683 /**
3684  * champlain_view_get_min_zoom_level:
3685  * @view: a #ChamplainView
3686  *
3687  * Gets the view's minimal allowed zoom level.
3688  *
3689  * Returns: the view's minimal allowed zoom level.
3690  *
3691  * Since: 0.4
3692  */
3693 guint
champlain_view_get_min_zoom_level(ChamplainView * view)3694 champlain_view_get_min_zoom_level (ChamplainView *view)
3695 {
3696   DEBUG_LOG ()
3697 
3698   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0);
3699 
3700   return view->priv->min_zoom_level;
3701 }
3702 
3703 
3704 /**
3705  * champlain_view_get_max_zoom_level:
3706  * @view: a #ChamplainView
3707  *
3708  * Gets the view's maximum allowed zoom level.
3709  *
3710  * Returns: the view's maximum allowed zoom level.
3711  *
3712  * Since: 0.4
3713  */
3714 guint
champlain_view_get_max_zoom_level(ChamplainView * view)3715 champlain_view_get_max_zoom_level (ChamplainView *view)
3716 {
3717   DEBUG_LOG ()
3718 
3719   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0);
3720 
3721   return view->priv->max_zoom_level;
3722 }
3723 
3724 
3725 /**
3726  * champlain_view_get_map_source:
3727  * @view: a #ChamplainView
3728  *
3729  * Gets the view's current map source.
3730  *
3731  * Returns: (transfer none): the view's current map source. If you need to keep a reference to the
3732  * map source then you have to call #g_object_ref().
3733  *
3734  * Since: 0.4
3735  */
3736 ChamplainMapSource *
champlain_view_get_map_source(ChamplainView * view)3737 champlain_view_get_map_source (ChamplainView *view)
3738 {
3739   DEBUG_LOG ()
3740 
3741   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
3742 
3743   return view->priv->map_source;
3744 }
3745 
3746 
3747 /**
3748  * champlain_view_get_deceleration:
3749  * @view: a #ChamplainView
3750  *
3751  * Gets the view's deceleration rate.
3752  *
3753  * Returns: the view's deceleration rate.
3754  *
3755  * Since: 0.4
3756  */
3757 gdouble
champlain_view_get_deceleration(ChamplainView * view)3758 champlain_view_get_deceleration (ChamplainView *view)
3759 {
3760   DEBUG_LOG ()
3761 
3762   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
3763 
3764   gdouble decel = 0.0;
3765   g_object_get (view->priv->kinetic_scroll, "decel-rate", &decel, NULL);
3766   return decel;
3767 }
3768 
3769 
3770 /**
3771  * champlain_view_get_kinetic_mode:
3772  * @view: a #ChamplainView
3773  *
3774  * Gets the view's scroll mode behaviour.
3775  *
3776  * Returns: TRUE for kinetic mode, FALSE for push mode.
3777  *
3778  * Since: 0.10
3779  */
3780 gboolean
champlain_view_get_kinetic_mode(ChamplainView * view)3781 champlain_view_get_kinetic_mode (ChamplainView *view)
3782 {
3783   DEBUG_LOG ()
3784 
3785   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), FALSE);
3786 
3787   return view->priv->kinetic_mode;
3788 }
3789 
3790 
3791 /**
3792  * champlain_view_get_keep_center_on_resize:
3793  * @view: a #ChamplainView
3794  *
3795  * Checks whether to keep the center on resize
3796  *
3797  * Returns: TRUE if the view keeps the center on resize, FALSE otherwise.
3798  *
3799  * Since: 0.4
3800  */
3801 gboolean
champlain_view_get_keep_center_on_resize(ChamplainView * view)3802 champlain_view_get_keep_center_on_resize (ChamplainView *view)
3803 {
3804   DEBUG_LOG ()
3805 
3806   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), FALSE);
3807 
3808   return view->priv->keep_center_on_resize;
3809 }
3810 
3811 
3812 /**
3813  * champlain_view_get_zoom_on_double_click:
3814  * @view: a #ChamplainView
3815  *
3816  * Checks whether the view zooms on double click.
3817  *
3818  * Returns: TRUE if the view zooms on double click, FALSE otherwise.
3819  *
3820  * Since: 0.4
3821  */
3822 gboolean
champlain_view_get_zoom_on_double_click(ChamplainView * view)3823 champlain_view_get_zoom_on_double_click (ChamplainView *view)
3824 {
3825   DEBUG_LOG ()
3826 
3827   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), FALSE);
3828 
3829   return view->priv->zoom_on_double_click;
3830 }
3831 
3832 
3833 /**
3834  * champlain_view_get_animate_zoom:
3835  * @view: a #ChamplainView
3836  *
3837  * Checks whether the view animates zoom level changes.
3838  *
3839  * Returns: TRUE if the view animates zooms, FALSE otherwise.
3840  *
3841  * Since: 0.12
3842  */
3843 gboolean
champlain_view_get_animate_zoom(ChamplainView * view)3844 champlain_view_get_animate_zoom (ChamplainView *view)
3845 {
3846   DEBUG_LOG ()
3847 
3848   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), FALSE);
3849 
3850   return view->priv->animate_zoom;
3851 }
3852 
3853 
3854 static ClutterActorAlign
bin_alignment_to_actor_align(ClutterBinAlignment alignment)3855 bin_alignment_to_actor_align (ClutterBinAlignment alignment)
3856 {
3857     switch (alignment)
3858       {
3859         case CLUTTER_BIN_ALIGNMENT_FILL:
3860             return CLUTTER_ACTOR_ALIGN_FILL;
3861         case CLUTTER_BIN_ALIGNMENT_START:
3862             return CLUTTER_ACTOR_ALIGN_START;
3863         case CLUTTER_BIN_ALIGNMENT_END:
3864             return CLUTTER_ACTOR_ALIGN_END;
3865         case CLUTTER_BIN_ALIGNMENT_CENTER:
3866             return CLUTTER_ACTOR_ALIGN_CENTER;
3867         default:
3868             return CLUTTER_ACTOR_ALIGN_START;
3869       }
3870 }
3871 
3872 
3873 /**
3874  * champlain_view_bin_layout_add:
3875  * @view: a #ChamplainView
3876  * @child: The child to be inserted
3877  * @x_align: x alignment
3878  * @y_align: y alignment
3879  *
3880  * This function inserts a custom actor to the undrelying #ClutterBinLayout
3881  * manager. The inserted actors appear on top of the map. See clutter_bin_layout_add()
3882  * for reference.
3883  *
3884  * Since: 0.10
3885  *
3886  * Deprecated: 0.12.4: Use #ClutterActorAlign and the #ClutterActor
3887  * API instead.
3888  */
3889 void
champlain_view_bin_layout_add(ChamplainView * view,ClutterActor * child,ClutterBinAlignment x_align,ClutterBinAlignment y_align)3890 champlain_view_bin_layout_add (ChamplainView *view,
3891     ClutterActor *child,
3892     ClutterBinAlignment x_align,
3893     ClutterBinAlignment y_align)
3894 {
3895   DEBUG_LOG ()
3896 
3897   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
3898 
3899   clutter_actor_set_x_expand (child, TRUE);
3900   clutter_actor_set_y_expand (child, TRUE);
3901   clutter_actor_set_x_align (child, bin_alignment_to_actor_align (x_align));
3902   clutter_actor_set_y_align (child, bin_alignment_to_actor_align (y_align));
3903   clutter_actor_add_child (CLUTTER_ACTOR (view), child);
3904 }
3905 
3906 
3907 /**
3908  * champlain_view_get_license_actor:
3909  * @view: a #ChamplainView
3910  *
3911  * Returns the #ChamplainLicense actor which is inserted by default into the
3912  * layout manager. It can be manipulated using standard #ClutterActor methods
3913  * (hidden and so on).
3914  *
3915  * Returns: (transfer none): the license actor
3916  *
3917  * Since: 0.10
3918  */
3919 ChamplainLicense *
champlain_view_get_license_actor(ChamplainView * view)3920 champlain_view_get_license_actor (ChamplainView *view)
3921 {
3922   DEBUG_LOG ()
3923 
3924   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
3925 
3926   return CHAMPLAIN_LICENSE (view->priv->license_actor);
3927 }
3928 
3929 
3930 /**
3931  * champlain_view_get_center_latitude:
3932  * @view: a #ChamplainView
3933  *
3934  * Gets the latitude of the view's center.
3935  *
3936  * Returns: the latitude.
3937  *
3938  * Since: 0.10
3939  */
3940 gdouble
champlain_view_get_center_latitude(ChamplainView * view)3941 champlain_view_get_center_latitude (ChamplainView *view)
3942 {
3943   DEBUG_LOG ()
3944 
3945   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
3946 
3947   return view->priv->latitude;
3948 }
3949 
3950 
3951 /**
3952  * champlain_view_get_center_longitude:
3953  * @view: a #ChamplainView
3954  *
3955  * Gets the longitude of the view's center.
3956  *
3957  * Returns: the longitude.
3958  *
3959  * Since: 0.10
3960  */
3961 gdouble
champlain_view_get_center_longitude(ChamplainView * view)3962 champlain_view_get_center_longitude (ChamplainView *view)
3963 {
3964   DEBUG_LOG ()
3965 
3966   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);
3967 
3968   return view->priv->longitude;
3969 }
3970 
3971 
3972 /**
3973  * champlain_view_get_state:
3974  * @view: a #ChamplainView
3975  *
3976  * Gets the view's state.
3977  *
3978  * Returns: the state.
3979  *
3980  * Since: 0.10
3981  */
3982 ChamplainState
champlain_view_get_state(ChamplainView * view)3983 champlain_view_get_state (ChamplainView *view)
3984 {
3985   DEBUG_LOG ()
3986 
3987   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), CHAMPLAIN_STATE_NONE);
3988 
3989   return view->priv->state;
3990 }
3991 
3992 static void
get_tile_bounds(ChamplainView * view,guint * min_x,guint * min_y,guint * max_x,guint * max_y)3993 get_tile_bounds (ChamplainView *view,
3994                  guint *min_x,
3995                  guint *min_y,
3996                  guint *max_x,
3997                  guint *max_y)
3998 {
3999   ChamplainViewPrivate *priv = view->priv;
4000   guint size = champlain_map_source_get_tile_size (priv->map_source);
4001   gint coord;
4002 
4003   coord = champlain_map_source_get_x (priv->map_source,
4004                                       priv->zoom_level,
4005                                       priv->world_bbox->left);
4006   *min_x = coord / size;
4007 
4008   coord = champlain_map_source_get_y (priv->map_source,
4009                                       priv->zoom_level,
4010                                       priv->world_bbox->top);
4011   *min_y = coord/size;
4012 
4013   coord = champlain_map_source_get_x (priv->map_source,
4014                                       priv->zoom_level,
4015                                       priv->world_bbox->right);
4016   *max_x = ceil ((double) coord / (double) size);
4017 
4018   coord  = champlain_map_source_get_y (priv->map_source,
4019                                        priv->zoom_level,
4020                                        priv->world_bbox->bottom);
4021   *max_y = ceil ((double) coord / (double) size);
4022 }
4023 
4024 static ChamplainBoundingBox *
get_bounding_box(ChamplainView * view,guint zoom_level,gdouble x,gdouble y)4025 get_bounding_box (ChamplainView *view,
4026                   guint zoom_level,
4027                   gdouble x,
4028                   gdouble y)
4029 {
4030   ChamplainViewPrivate *priv = view->priv;
4031   ChamplainBoundingBox *bbox;
4032 
4033   bbox = champlain_bounding_box_new ();
4034 
4035   bbox->top = champlain_map_source_get_latitude (priv->map_source,
4036                                                  zoom_level,
4037                                                  y);
4038   bbox->bottom = champlain_map_source_get_latitude (priv->map_source,
4039                                                     zoom_level,
4040                                                     y + priv->viewport_height);
4041   bbox->left = get_longitude (view,
4042                               zoom_level,
4043                               x);
4044   bbox->right = get_longitude (view,
4045                                zoom_level,
4046                                x + priv->viewport_width);
4047   return bbox;
4048 }
4049 
4050 /**
4051  * champlain_view_get_bounding_box_for_zoom_level:
4052  * @view: a #ChamplainView
4053  * @zoom_level: the level of zoom, a guint between 1 and 20
4054  *
4055  * Gets the bounding box for view @view at @zoom_level.
4056  *
4057  * Returns: (transfer full): the bounding box for the view at @zoom_level.
4058  *
4059  * Since: 0.12.6
4060  */
4061 ChamplainBoundingBox *
champlain_view_get_bounding_box_for_zoom_level(ChamplainView * view,guint zoom_level)4062 champlain_view_get_bounding_box_for_zoom_level (ChamplainView *view,
4063                                                 guint zoom_level)
4064 {
4065   ChamplainViewPrivate *priv = view->priv;
4066   gdouble x, y;
4067   gdouble offset_x, offset_y;
4068 
4069   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
4070 
4071   offset_x = priv->viewport_width / 2.0;
4072   offset_y = priv->viewport_height / 2.0;
4073 
4074   get_x_y_for_zoom_level (view, zoom_level, offset_x, offset_y, &x, &y);
4075 
4076   return get_bounding_box (view, zoom_level, x, y);
4077 }
4078 
4079 /**
4080  * champlain_view_get_bounding_box:
4081  * @view: a #ChamplainView
4082  *
4083  * Gets the bounding box for view @view at current zoom-level.
4084  *
4085  * Returns: (transfer full): the bounding box
4086  *
4087  * Since: 0.12.4
4088  */
4089 ChamplainBoundingBox *
champlain_view_get_bounding_box(ChamplainView * view)4090 champlain_view_get_bounding_box (ChamplainView *view)
4091 {
4092   DEBUG_LOG ()
4093 
4094   ChamplainViewPrivate *priv = view->priv;
4095 
4096   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
4097 
4098   return get_bounding_box (view, priv->zoom_level, priv->viewport_x, priv->viewport_y);
4099 }
4100 
4101 
4102 /**
4103  * champlain_view_add_overlay_source:
4104  * @view: a #ChamplainView
4105  * @map_source: a #ChamplainMapSource
4106  * @opacity: opacity to use
4107  *
4108  * Adds a new overlay map source to render tiles with the supplied opacity on top
4109  * of the ordinary map source. Multiple overlay sources can be added.
4110  *
4111  * Since: 0.12.5
4112  */
4113 void
champlain_view_add_overlay_source(ChamplainView * view,ChamplainMapSource * map_source,guint8 opacity)4114 champlain_view_add_overlay_source (ChamplainView *view,
4115     ChamplainMapSource *map_source,
4116     guint8 opacity)
4117 {
4118   DEBUG_LOG ()
4119 
4120   ChamplainViewPrivate *priv;
4121 
4122   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
4123   g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));
4124 
4125   priv = view->priv;
4126   g_object_ref (map_source);
4127   priv->overlay_sources = g_list_append (priv->overlay_sources, map_source);
4128   g_object_set_data (G_OBJECT (map_source), "opacity", GINT_TO_POINTER (opacity));
4129   g_object_notify (G_OBJECT (view), "map-source");
4130 
4131   champlain_view_reload_tiles (view);
4132 }
4133 
4134 
4135 /**
4136  * champlain_view_remove_overlay_source:
4137  * @view: a #ChamplainView
4138  * @map_source: a #ChamplainMapSource
4139  *
4140  * Removes an overlay source from #ChamplainView.
4141  *
4142  * Since: 0.12.5
4143  */
4144 void
champlain_view_remove_overlay_source(ChamplainView * view,ChamplainMapSource * map_source)4145 champlain_view_remove_overlay_source (ChamplainView *view,
4146     ChamplainMapSource *map_source)
4147 {
4148   DEBUG_LOG ()
4149 
4150   ChamplainViewPrivate *priv;
4151 
4152   g_return_if_fail (CHAMPLAIN_IS_VIEW (view));
4153   g_return_if_fail (CHAMPLAIN_IS_MAP_SOURCE (map_source));
4154 
4155   priv = view->priv;
4156   priv->overlay_sources = g_list_remove (priv->overlay_sources, map_source);
4157   g_object_unref (map_source);
4158   g_object_notify (G_OBJECT (view), "map-source");
4159 
4160   champlain_view_reload_tiles (view);
4161 }
4162 
4163 
4164 /**
4165  * champlain_view_get_overlay_sources:
4166  * @view: a #ChamplainView
4167  *
4168  * Gets a list of overlay sources.
4169  *
4170  * Returns: (transfer container) (element-type ChamplainMapSource): the list
4171  *
4172  * Since: 0.12.5
4173  */
4174 GList *
champlain_view_get_overlay_sources(ChamplainView * view)4175 champlain_view_get_overlay_sources (ChamplainView *view)
4176 {
4177   DEBUG_LOG ()
4178 
4179   ChamplainViewPrivate *priv;
4180 
4181   g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), NULL);
4182 
4183   priv = view->priv;
4184 
4185   return g_list_copy (priv->overlay_sources);
4186 }
4187