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