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

21
/**
22
 * SECTION:champlain-view
23 24 25 26 27
 * @short_description: A #ClutterActor to display maps
 *
 * The #ChamplainView is a ClutterActor to display maps.  It supports two modes
 * of scrolling:
 * <itemizedlist>
28
 *   <listitem><para>Push: the normal behavior where the maps don't move
29 30 31 32 33 34 35 36 37 38 39
 *   after the user stopped scrolling;</para></listitem>
 *   <listitem><para>Kinetic: the iPhone-like behavior where the maps
 *   decelerate after the user stopped scrolling.</para></listitem>
 * </itemizedlist>
 *
 * You can use the same #ChamplainView to display many types of maps.  In
 * Champlain they are called map sources.  You can change the #map-source
 * property at anytime to replace the current displayed map.
 *
 * The maps are downloaded from Internet from open maps sources (like
 * <ulink role="online-location"
Jiří Techet's avatar
Jiří Techet committed
40
 * url="https://www.openstreetmap.org">OpenStreetMap</ulink>).  Maps are divided
41 42
 * in tiles for each zoom level.  When a tile is requested, #ChamplainView will
 * 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
 */

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
49 50
#include "config.h"

51 52
#include "champlain-view.h"

53 54 55
#define DEBUG_FLAG CHAMPLAIN_DEBUG_VIEW
#include "champlain-debug.h"

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
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 62
#include "champlain-private.h"
#include "champlain-tile.h"
63
#include "champlain-license.h"
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
64

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
65
#include <clutter/clutter.h>
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
66 67
#include <glib.h>
#include <glib-object.h>
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
68
#include <math.h>
69
#include <champlain-kinetic-scroll-view.h>
70 71
#include <champlain-viewport.h>
#include <champlain-adjustment.h>
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
72

Jiří Techet's avatar
Jiří Techet committed
73
/* #define VIEW_LOG */
74
#ifdef VIEW_LOG
75
#define DEBUG_LOG() g_print ("%s\n", __FUNCTION__);
76 77 78 79
#else
#define DEBUG_LOG()
#endif

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
80 81 82
enum
{
  /* normal signals */
83
  ANIMATION_COMPLETED,
84
  LAYER_RELOCATED,
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
85
  LAST_SIGNAL
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
86 87
};

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
88 89 90
enum
{
  PROP_0,
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
91 92 93
  PROP_LONGITUDE,
  PROP_LATITUDE,
  PROP_ZOOM_LEVEL,
94 95
  PROP_MIN_ZOOM_LEVEL,
  PROP_MAX_ZOOM_LEVEL,
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
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
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
108 109
};

110
#define PADDING 10
111
static guint signals[LAST_SIGNAL] = { 0, };
112
  
113 114
#define ZOOM_LEVEL_OUT_OF_RANGE(priv, level) \
  (level < priv->min_zoom_level || \
Jiří Techet's avatar
Jiří Techet committed
115
           level > priv->max_zoom_level || \
116
   level < champlain_map_source_get_min_zoom_level (priv->map_source) || \
Jiří Techet's avatar
Jiří Techet committed
117
           level > champlain_map_source_get_max_zoom_level (priv->map_source))
118

119
/* Between state values for go_to */
120 121
typedef struct
{
122 123 124 125 126 127 128 129
  ChamplainView *view;
  ClutterTimeline *timeline;
  gdouble to_latitude;
  gdouble to_longitude;
  gdouble from_latitude;
  gdouble from_longitude;
} GoToContext;

130

131 132
typedef struct
{
133
  ChamplainView *view;
134
  ChamplainMapSource *map_source;
135 136 137 138
  gint x;
  gint y;
  gint zoom_level;
  gint size;
139 140
} FillTileCallbackData;

141

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
142
struct _ChamplainViewPrivate
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
143
{
144 145 146
                                /* ChamplainView */
  ClutterActor *kinetic_scroll;     /* kinetic_scroll */
  ClutterActor *viewport;               /* viewport */
147
  ClutterActor *viewport_container;         /* viewport_container */
148 149 150
  ClutterActor *background_layer;               /* background_layer */
  ClutterActor *zoom_layer;                     /* zoom_layer */
  ClutterActor *map_layer;                      /* map_layer */
151
                                                /* map_layer clones */
152
  ClutterActor *user_layers;                    /* user_layers and clones */
153 154
  ClutterActor *zoom_overlay_actor; /* zoom_overlay_actor */
  ClutterActor *license_actor;      /* license_actor */
155

156
  ClutterContent *background_content; 
157

158
  gboolean hwrap;
159 160
  /* There are num_right_clones clones on the right, and one extra on the left */
  gint num_right_clones;
161
  GList *map_clones;
162 163 164 165
  /* There are num_right_clones + 2 user layer slots, overlayed on the map clones.
   * Initially, the first slot contains the left clone, the second slot
   * contains the real user layer, and the rest contain the right clones.
   * Whenever the cursor enters a clone slot, its content
166 167 168
   * is swapped with the real one so as to ensure reactiveness to events.
   */
  GList *user_layer_slots;
169

170 171
  gdouble viewport_x;
  gdouble viewport_y;
172 173 174
  gint viewport_width;
  gint viewport_height;

175
  ChamplainMapSource *map_source; /* Current map tile source */
176
  GList *overlay_sources;
177

Jiří Techet's avatar
Jiří Techet committed
178 179 180
  guint zoom_level; /* Holds the current zoom level number */
  guint min_zoom_level; /* Lowest allowed zoom level */
  guint max_zoom_level; /* Highest allowed zoom level */
181

182
  /* Represents the (lat, lon) at the center of the viewport */
183 184
  gdouble longitude;
  gdouble latitude;
185
  gboolean location_updated;
186

187 188
  gint bg_offset_x;
  gint bg_offset_y;
Jiří Techet's avatar
Jiří Techet committed
189

190
  gboolean keep_center_on_resize;
191
  gboolean zoom_on_double_click;
192
  gboolean animate_zoom;
193

194 195
  gboolean kinetic_mode;

196
  ChamplainState state; /* View's global state */
197 198 199

  /* champlain_view_go_to's context, kept for stop_go_to */
  GoToContext *goto_context;
200

201
  gint tiles_loading;
202
  
203
  guint redraw_timeout;
204
  guint zoom_timeout;
205
  
206 207 208
  ClutterAnimationMode goto_mode;
  guint goto_duration;

209
  gboolean animating_zoom;
210
  guint anim_start_zoom_level;
211 212
  gdouble zoom_actor_viewport_x;
  gdouble zoom_actor_viewport_y;
213
  guint zoom_actor_timeout;
214 215 216 217 218 219 220
  
  GHashTable *tile_map;

  gint tile_x_first;
  gint tile_y_first;
  gint tile_x_last;
  gint tile_y_last;
221 222 223 224 225 226 227

  /* Zoom gesture */
  ClutterGestureAction *zoom_gesture;
  guint initial_gesture_zoom;
  gdouble focus_lat;
  gdouble focus_lon;
  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

Marius Stanciu's avatar
Marius Stanciu committed
237
static void exclusive_destroy_clone (ClutterActor *clone);
238
static void update_clones (ChamplainView *view);
Jiří Techet's avatar
Jiří Techet committed
239 240 241
static gboolean scroll_event (ClutterActor *actor,
    ClutterScrollEvent *event,
    ChamplainView *view);
242
static void resize_viewport (ChamplainView *view);
Jiří Techet's avatar
Jiří Techet committed
243 244 245 246 247 248 249 250
static void champlain_view_get_property (GObject *object,
    guint prop_id,
    GValue *value,
    GParamSpec *pspec);
static void champlain_view_set_property (GObject *object,
    guint prop_id,
    const GValue *value,
    GParamSpec *pspec);
251
static void champlain_view_dispose (GObject *object);
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
252 253
static void champlain_view_class_init (ChamplainViewClass *champlainViewClass);
static void champlain_view_init (ChamplainView *view);
Jiří Techet's avatar
Jiří Techet committed
254 255
static void viewport_pos_changed_cb (GObject *gobject,
    GParamSpec *arg1,
256
    ChamplainView *view);
257
static gboolean kinetic_scroll_button_press_cb (ClutterActor *actor,
Jiří Techet's avatar
Jiří Techet committed
258 259
    ClutterButtonEvent *event,
    ChamplainView *view);
260 261 262
static ClutterActor *sample_user_layer_at_pos (ChamplainView *view,
    gfloat x,
    gfloat y);
263 264 265
static void swap_user_layer_slots (ChamplainView *view,
    gint original_index,
    gint clone_index);
266
static gboolean viewport_motion_cb (ClutterActor *actor,
267
    ClutterMotionEvent *event,
268
    ChamplainView *view);
269 270 271
static gboolean viewport_press_cb (ClutterActor *actor,
    ClutterButtonEvent *event,
    ChamplainView *view);
272 273
static void load_visible_tiles (ChamplainView *view,
    gboolean relocate);
274
static gboolean view_set_zoom_level_at (ChamplainView *view,
Jiří Techet's avatar
Jiří Techet committed
275
    guint zoom_level,
276
    gboolean use_event_coord,
277 278
    gint x,
    gint y);
279 280 281
static void tile_state_notify (ChamplainTile *tile,
    G_GNUC_UNUSED GParamSpec *pspec,
    ChamplainView *view);
282 283
static gboolean kinetic_scroll_key_press_cb (ChamplainView *view,
    ClutterKeyEvent *event);
284 285 286 287
static void champlain_view_go_to_with_duration (ChamplainView *view,
    gdouble latitude,
    gdouble longitude,
    guint duration);
288
static gboolean redraw_timeout_cb(gpointer view);
289
static void remove_all_tiles (ChamplainView *view);
290 291 292 293 294 295 296 297 298 299
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);
static ChamplainBoundingBox *get_bounding_box (ChamplainView *view,
    guint zoom_level,
    gdouble x,
    gdouble y);
300 301 302 303 304
static void get_tile_bounds (ChamplainView *view,
    guint *min_x,
    guint *min_y,
    guint *max_x,
    guint *max_y);
305 306
static gboolean tile_in_tile_table (ChamplainView *view,
    GHashTable *table,
307 308
    gint tile_x,
    gint tile_y);
309

310 311
static gdouble
x_to_wrap_x (gdouble x, gdouble width)
312
{
Jiří Techet's avatar
Jiří Techet committed
313
  if (x < 0)
314
    x += ((gint)-x / (gint)width + 1) * width;
Jiří Techet's avatar
Jiří Techet committed
315
  
316
  return fmod (x, width);
317 318
}

319

320 321 322 323 324 325 326 327 328 329 330 331 332
static gint 
get_map_width (ChamplainView *view) 
{
  gint size, cols;
  ChamplainViewPrivate *priv = view->priv;
  
  size = champlain_map_source_get_tile_size (priv->map_source);
  cols = champlain_map_source_get_column_count (priv->map_source,
                                                priv->zoom_level);
  return size * cols;
}


333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
static gdouble
get_longitude (ChamplainView *view,
    guint zoom_level,
    gdouble x)
{
  ChamplainViewPrivate *priv = view->priv;

  DEBUG_LOG ()

  g_return_val_if_fail (CHAMPLAIN_IS_VIEW (view), 0.0);

  if (priv->hwrap)
    x = x_to_wrap_x (x, get_map_width (view));

  return champlain_map_source_get_longitude (priv->map_source,
        zoom_level,
        x);
}


353
static void
354
update_coords (ChamplainView *view,
355 356
    gdouble x,
    gdouble y,
357
    gboolean notify)
358
{
359
  DEBUG_LOG ()
360

361
  ChamplainViewPrivate *priv = view->priv;
362

363 364
  priv->viewport_x = x;
  priv->viewport_y = y;
365
  priv->longitude = get_longitude (view,
366 367 368 369 370
        priv->zoom_level,
        x + priv->viewport_width / 2.0);
  priv->latitude = champlain_map_source_get_latitude (priv->map_source,
        priv->zoom_level,
        y + priv->viewport_height / 2.0);
371
  
372 373 374 375 376
  if (notify)
    {
      g_object_notify (G_OBJECT (view), "longitude");
      g_object_notify (G_OBJECT (view), "latitude");
    }
377 378 379 380 381
}


static void
position_viewport (ChamplainView *view,
382 383
    gdouble x,
    gdouble y)
384 385 386 387 388 389 390 391 392 393 394
{
  DEBUG_LOG ()

  ChamplainViewPrivate *priv = view->priv;
  gint old_bg_offset_x = 0, old_bg_offset_y = 0;
  gfloat bg_width, bg_height;

  /* remember the relative offset of the background tile */
  if (priv->background_content)
    {
      clutter_content_get_preferred_size (priv->background_content, &bg_width, &bg_height);
395 396
      old_bg_offset_x = ((gint)priv->viewport_x + priv->bg_offset_x) % (gint)bg_width;
      old_bg_offset_y = ((gint)priv->viewport_y + priv->bg_offset_y) % (gint)bg_height;
397 398 399 400 401
    }
    
  /* notify about latitude and longitude change only after the viewport position is set */
  g_object_freeze_notify (G_OBJECT (view));
  
402
  update_coords (view, x, y, TRUE);
403

404 405
  /* compute the new relative offset of the background tile */
  if (priv->background_content)
406
    {
407 408
      gint new_bg_offset_x = (gint)priv->viewport_x % (gint)bg_width;
      gint new_bg_offset_y = (gint)priv->viewport_y % (gint)bg_height;
409 410 411 412 413 414
      priv->bg_offset_x = (old_bg_offset_x - new_bg_offset_x) % (gint)bg_width;
      priv->bg_offset_y = (old_bg_offset_y - new_bg_offset_y) % (gint)bg_height;
      if (priv->bg_offset_x < 0)
        priv->bg_offset_x += bg_width;
      if (priv->bg_offset_y < 0)
        priv->bg_offset_y += bg_height;
415
    }
416 417 418

  /* we know about the change already - don't send the notifications again */
  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 421
      (gint)priv->viewport_x,
      (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 429 430 431
}


static void
view_relocated_cb (G_GNUC_UNUSED ChamplainViewport *viewport,
    ChamplainView *view)
{
432
  ChamplainViewPrivate *priv = view->priv;
433 434 435
  gint anchor_x, anchor_y, new_width, new_height;
  gint tile_size, column_count, row_count;

436 437
  clutter_actor_destroy_all_children (priv->zoom_layer);
  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 442 443
  tile_size = champlain_map_source_get_tile_size (priv->map_source);
  column_count = champlain_map_source_get_column_count (priv->map_source, priv->zoom_level);
  row_count = champlain_map_source_get_row_count (priv->map_source, priv->zoom_level);
444 445
  champlain_viewport_get_anchor (CHAMPLAIN_VIEWPORT (priv->viewport), &anchor_x, &anchor_y);

446 447 448 449
  /* The area containing tiles in the map layer is actually column_count * tile_size wide (same
   * for height), but the viewport anchor acts as an offset for the tile actors, causing the map
   * layer to contain some empty space as well.
   */
450 451 452 453
  new_width = column_count * tile_size + anchor_x;
  new_height = row_count * tile_size + anchor_y;

  clutter_actor_set_size (priv->map_layer, new_width, new_height);
454 455
}

456

457
static void
458
panning_completed (G_GNUC_UNUSED ChamplainKineticScrollView *scroll,
Jiří Techet's avatar
Jiří Techet committed
459
    ChamplainView *view)
460
{
461
  DEBUG_LOG ()
462

463
  ChamplainViewPrivate *priv = view->priv;
464
  gdouble x, y;
465 466 467 468 469 470
  
  if (priv->redraw_timeout != 0)
    {
      g_source_remove (priv->redraw_timeout);
      priv->redraw_timeout = 0;
    }
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 480 481 482 483 484 485 486 487 488 489 490 491 492 493
static gboolean
zoom_timeout_cb (gpointer data)
{
  DEBUG_LOG ()

  ChamplainView *view = data;
  ChamplainViewPrivate *priv = view->priv;

  priv->accumulated_scroll_dy = 0;
  priv->zoom_timeout = 0;

  return FALSE;
}


Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
494
static gboolean
495
scroll_event (G_GNUC_UNUSED ClutterActor *actor,
496 497
    ClutterScrollEvent *event,
    ChamplainView *view)
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
498
{
499
  DEBUG_LOG ()
500

501
  ChamplainViewPrivate *priv = view->priv;
502

Jiří Techet's avatar
Jiří Techet committed
503
  guint zoom_level = priv->zoom_level;
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
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 510 511
  else if (event->direction == CLUTTER_SCROLL_SMOOTH)
    {
      gdouble dx, dy;
512
      gint steps;
513

Jiří Techet's avatar
Jiří Techet committed
514
      clutter_event_get_scroll_delta ((ClutterEvent *)event, &dx, &dy);
515

516 517
      priv->accumulated_scroll_dy += dy;
      /* 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 522 523 524 525 526 527 528
        steps = (int) (priv->accumulated_scroll_dy - 0.01);
      zoom_level = priv->zoom_level - steps;
      priv->accumulated_scroll_dy -= steps;

      if (priv->zoom_timeout != 0)
        g_source_remove (priv->zoom_timeout);
      priv->zoom_timeout = g_timeout_add (1000, zoom_timeout_cb, view);
    }
529

530
  return view_set_zoom_level_at (view, zoom_level, TRUE, event->x, event->y);
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
531 532
}

533

534
static void
535
resize_viewport (ChamplainView *view)
536
{
537
  DEBUG_LOG ()
538

539 540 541 542
  gdouble lower_x = 0;
  gdouble lower_y = 0;
  gdouble upper_x = G_MAXINT16;
  gdouble upper_y = G_MAXINT16;
543
  ChamplainAdjustment *hadjust, *vadjust;
544
  guint min_x, min_y, max_x, max_y;
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
545

546
  ChamplainViewPrivate *priv = view->priv;
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
547

548
  champlain_viewport_get_adjustments (CHAMPLAIN_VIEWPORT (priv->viewport), &hadjust,
549
      &vadjust);
550

551 552 553 554 555 556
  get_tile_bounds (view, &min_x, &min_y, &max_x, &max_y);
  gint x_last = max_x * champlain_map_source_get_tile_size (priv->map_source);
  gint y_last = max_y * champlain_map_source_get_tile_size (priv->map_source);
  gint x_first = min_x * champlain_map_source_get_tile_size (priv->map_source);
  gint y_first = min_y * champlain_map_source_get_tile_size (priv->map_source);

557 558 559 560 561 562 563 564 565 566 567
  /* Location of viewport with respect to the first tile:
   *
   * - for large maps (higher zoom levels) we allow the map to end in the middle
   *   of the viewport; that is, one half of the viewport is positioned before
   *   the first tile
   * - for small maps (e.g. zoom level 0) we allow half of the map to go outside
   *   the viewport; that is, whole viewport except one half of the map is
   *   positioned before the first tile
   *
   * The first and the second element of the MIN() below corresponds to the
   * first and the second case above. */
568 569 570 571 572
  lower_x = MIN (x_first - priv->viewport_width / 2,
                 (x_first - priv->viewport_width) + (x_last - x_first) / 2);

  lower_y = MIN (y_first - priv->viewport_height / 2,
                 (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);
Jiří Techet's avatar
Jiří Techet committed
579

580
  /* we don't want to get notified about the position change now */
Jiří Techet's avatar
Jiří Techet committed
581
  g_signal_handlers_block_by_func (priv->viewport, G_CALLBACK (viewport_pos_changed_cb), view);
582 583
  champlain_adjustment_set_values (hadjust, champlain_adjustment_get_value (hadjust), lower_x, upper_x, 1.0);
  champlain_adjustment_set_values (vadjust, champlain_adjustment_get_value (vadjust), lower_y, upper_y, 1.0);
Jiří Techet's avatar
Jiří Techet committed
584
  g_signal_handlers_unblock_by_func (priv->viewport, G_CALLBACK (viewport_pos_changed_cb), view);
585 586
}

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
587
static void
588
champlain_view_get_property (GObject *object,
589 590 591
    guint prop_id,
    GValue *value,
    GParamSpec *pspec)
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
592
{
593
  DEBUG_LOG ()
594

595
  ChamplainView *view = CHAMPLAIN_VIEW (object);
596
  ChamplainViewPrivate *priv = view->priv;
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
597

598
  switch (prop_id)
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
599
    {
600 601
    case PROP_LONGITUDE:
      g_value_set_double (value,
602
          CLAMP (priv->longitude, priv->world_bbox->left, priv->world_bbox->right));
603 604 605 606
      break;

    case PROP_LATITUDE:
      g_value_set_double (value,
607
          CLAMP (priv->latitude, priv->world_bbox->bottom, priv->world_bbox->top));
608 609 610
      break;

    case PROP_ZOOM_LEVEL:
Jiří Techet's avatar
Jiří Techet committed
611
      g_value_set_uint (value, priv->zoom_level);
612 613 614
      break;

    case PROP_MIN_ZOOM_LEVEL:
Jiří Techet's avatar
Jiří Techet committed
615
      g_value_set_uint (value, priv->min_zoom_level);
616 617 618
      break;

    case PROP_MAX_ZOOM_LEVEL:
Jiří Techet's avatar
Jiří Techet committed
619
      g_value_set_uint (value, priv->max_zoom_level);
620 621 622 623 624 625
      break;

    case PROP_MAP_SOURCE:
      g_value_set_object (value, priv->map_source);
      break;

626 627
    case PROP_KINETIC_MODE:
      g_value_set_boolean (value, priv->kinetic_mode);
628 629
      break;

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 639 640 641 642 643 644 645
      }

    case PROP_KEEP_CENTER_ON_RESIZE:
      g_value_set_boolean (value, priv->keep_center_on_resize);
      break;

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

646 647 648 649
    case PROP_ANIMATE_ZOOM:
      g_value_set_boolean (value, priv->animate_zoom);
      break;

650 651 652 653
    case PROP_STATE:
      g_value_set_enum (value, priv->state);
      break;

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

658 659 660 661 662 663 664 665
    case PROP_GOTO_ANIMATION_MODE:
      g_value_set_enum (value, priv->goto_mode);
      break;

    case PROP_GOTO_ANIMATION_DURATION:
      g_value_set_uint (value, priv->goto_duration);
      break;

666 667 668 669
    case PROP_WORLD:
      g_value_set_boxed (value, priv->world_bbox);
      break;

670
    case PROP_HORIZONTAL_WRAP:
671
      g_value_set_boolean (value, champlain_view_get_horizontal_wrap (view));
672 673
      break;

674 675
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
676 677 678
    }
}

679

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
680
static void
681
champlain_view_set_property (GObject *object,
682 683 684
    guint prop_id,
    const GValue *value,
    GParamSpec *pspec)
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
685
{
686
  DEBUG_LOG ()
687

688
  ChamplainView *view = CHAMPLAIN_VIEW (object);
689
  ChamplainViewPrivate *priv = view->priv;
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
690

691
  switch (prop_id)
692
    {
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
693
    case PROP_LONGITUDE:
694 695 696
      champlain_view_center_on (view, priv->latitude,
          g_value_get_double (value));
      break;
697

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
698
    case PROP_LATITUDE:
699 700 701
      champlain_view_center_on (view, g_value_get_double (value),
          priv->longitude);
      break;
702

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
703
    case PROP_ZOOM_LEVEL:
Jiří Techet's avatar
Jiří Techet committed
704
      champlain_view_set_zoom_level (view, g_value_get_uint (value));
705
      break;
706

707
    case PROP_MIN_ZOOM_LEVEL:
Jiří Techet's avatar
Jiří Techet committed
708
      champlain_view_set_min_zoom_level (view, g_value_get_uint (value));
709
      break;
710

711
    case PROP_MAX_ZOOM_LEVEL:
Jiří Techet's avatar
Jiří Techet committed
712
      champlain_view_set_max_zoom_level (view, g_value_get_uint (value));
713
      break;
714

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
715
    case PROP_MAP_SOURCE:
716 717
      champlain_view_set_map_source (view, g_value_get_object (value));
      break;
718

719 720
    case PROP_KINETIC_MODE:
      champlain_view_set_kinetic_mode (view, g_value_get_boolean (value));
721
      break;
722

723 724
    case PROP_DECELERATION:
      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 732 733
    case PROP_ZOOM_ON_DOUBLE_CLICK:
      champlain_view_set_zoom_on_double_click (view, g_value_get_boolean (value));
      break;
734

735 736 737 738
    case PROP_ANIMATE_ZOOM:
      champlain_view_set_animate_zoom (view, g_value_get_boolean (value));
      break;
      
739 740
    case PROP_BACKGROUND_PATTERN:
      champlain_view_set_background_pattern (view, g_value_get_object (value));
741 742
      break;

743 744 745 746 747 748 749 750
    case PROP_GOTO_ANIMATION_MODE:
      priv->goto_mode = g_value_get_enum (value);
      break;

    case PROP_GOTO_ANIMATION_DURATION:
      priv->goto_duration = g_value_get_uint (value);
      break;

751 752 753 754
    case PROP_WORLD:
      champlain_view_set_world (view, g_value_get_boxed (value));
      break;

755 756 757 758
    case PROP_HORIZONTAL_WRAP:
      champlain_view_set_horizontal_wrap (view, g_value_get_boolean (value));
      break;

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
759
    default:
760
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
761
    }
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
762 763
}

764

765 766 767
static void
champlain_view_dispose (GObject *object)
{
768
  DEBUG_LOG ()
769
  
770
  ChamplainView *view = CHAMPLAIN_VIEW (object);
771
  ChamplainViewPrivate *priv = view->priv;
772

773 774 775
  if (priv->goto_context != NULL)
    champlain_view_stop_go_to (view);

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
    }

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
782
  if (priv->viewport != NULL)
783
    {
784
      champlain_viewport_stop (CHAMPLAIN_VIEWPORT (priv->viewport));
785 786 787
      priv->viewport = NULL;
    }

788 789 790 791 792
  if (priv->map_source != NULL)
    {
      g_object_unref (priv->map_source);
      priv->map_source = NULL;
    }
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
793

794 795
  g_list_free_full (priv->overlay_sources, g_object_unref);
  priv->overlay_sources = NULL;
796

797
  if (priv->background_content)
798
    {
799 800
      g_object_unref (priv->background_content);
      priv->background_content = NULL;
801
    }
802 803 804 805 806 807
    
  if (priv->redraw_timeout != 0)
    {
      g_source_remove (priv->redraw_timeout);
      priv->redraw_timeout = 0;
    }
808
    
809
  if (priv->zoom_actor_timeout != 0)
810 811 812 813
    {
      g_source_remove (priv->zoom_actor_timeout);
      priv->zoom_actor_timeout = 0;
    }
814

815 816 817 818 819 820
  if (priv->zoom_timeout != 0)
    {
      g_source_remove (priv->zoom_timeout);
      priv->zoom_timeout = 0;
    }

821 822 823 824 825 826
  if (priv->tile_map != NULL)
    {
      g_hash_table_destroy (priv->tile_map);
      priv->tile_map = NULL;
    }

827 828 829 830 831 832 833
  if (priv->zoom_gesture)
    {
      clutter_actor_remove_action (CLUTTER_ACTOR (view),
                                   CLUTTER_ACTION (priv->zoom_gesture));
      priv->zoom_gesture = NULL;
    }

834 835 836 837 838
  if (priv->visible_tiles != NULL)
    {
      g_hash_table_destroy (priv->visible_tiles);
      priv->visible_tiles = NULL;
    }
839

840 841
  priv->map_layer = NULL;
  priv->license_actor = NULL;
842 843 844 845

  /* This is needed to prevent race condition see bug #760012 */
  if (priv->user_layers)
      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 852 853
    {
      champlain_bounding_box_free (priv->world_bbox);
      priv->world_bbox = NULL;
    }
854

855
  G_OBJECT_CLASS (champlain_view_parent_class)->dispose (object);
856
}
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
857

858

859 860 861
static void
champlain_view_finalize (GObject *object)
{
862
  DEBUG_LOG ()
863 864 865 866

  G_OBJECT_CLASS (champlain_view_parent_class)->finalize (object);
}

867

868 869 870 871 872 873
/* These return fixed sizes because either a.) We expect the user to size
 * explicitly with clutter_actor_get_size or b.) place it in a container that
 * allocates it whatever it wants.
 */
static void
champlain_view_get_preferred_width (ClutterActor *actor,
874
    G_GNUC_UNUSED gfloat for_height,
Jiří Techet's avatar
Jiří Techet committed
875 876
    gfloat *min_width,
    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 884 885 886 887 888 889

  if (min_width)
    *min_width = 1;

  if (nat_width)
    *nat_width = width;
}

890

891 892
static void
champlain_view_get_preferred_height (ClutterActor *actor,
893
    G_GNUC_UNUSED gfloat for_width,
Jiří Techet's avatar
Jiří Techet committed
894 895
    gfloat *min_height,
    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 903 904 905 906 907 908

  if (min_height)
    *min_height = 1;

  if (nat_height)
    *nat_height = height;
}

909

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
910
static void
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
911
champlain_view_class_init (ChamplainViewClass *champlainViewClass)
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
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 919
  object_class->get_property = champlain_view_get_property;
  object_class->set_property = champlain_view_set_property;
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
920

921
  ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (champlainViewClass);
922 923
  actor_class->get_preferred_width = champlain_view_get_preferred_width;
  actor_class->get_preferred_height = champlain_view_get_preferred_height;
924

Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
925
  /**
926 927 928 929 930 931
   * ChamplainView:longitude:
   *
   * The longitude coordonate of the map
   *
   * Since: 0.1
   */
932 933
  g_object_class_install_property (object_class,
      PROP_LONGITUDE,
934
      g_param_spec_double ("longitude",
935 936
          "Longitude",
          "The longitude coordonate of the map",
937 938 939 940
          -180.0f, 
          180.0f, 
          0.0f, 
          CHAMPLAIN_PARAM_READWRITE));
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
941 942

  /**
943 944 945 946 947 948
   * ChamplainView:latitude:
   *
   * The latitude coordonate of the map
   *
   * Since: 0.1
   */
949 950
  g_object_class_install_property (object_class,
      PROP_LATITUDE,
951
      g_param_spec_double ("latitude",
952 953
          "Latitude",
          "The latitude coordonate of the map",
954 955 956 957
          -90.0f, 
          90.0f, 
          0.0f, 
          CHAMPLAIN_PARAM_READWRITE));
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
958 959

  /**
960 961 962 963 964 965
   * ChamplainView:zoom-level:
   *
   * The level of zoom of the content.
   *
   * Since: 0.1
   */
966 967
  g_object_class_install_property (object_class,
      PROP_ZOOM_LEVEL,
Jiří Techet's avatar
Jiří Techet committed
968
      g_param_spec_uint ("zoom-level",
969 970
          "Zoom level",
          "The level of zoom of the map",
971 972 973 974
          0, 
          20, 
          3, 
          CHAMPLAIN_PARAM_READWRITE));
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
975

976
  /**
977 978 979 980 981 982
   * ChamplainView:min-zoom-level:
   *
   * The lowest allowed level of zoom of the content.
   *
   * Since: 0.4
   */
983 984
  g_object_class_install_property (object_class,
      PROP_MIN_ZOOM_LEVEL,
Jiří Techet's avatar
Jiří Techet committed
985
      g_param_spec_uint ("min-zoom-level",
986 987
          "Min zoom level",
          "The lowest allowed level of zoom",
988 989 990 991
          0, 
          20, 
          0, 
          CHAMPLAIN_PARAM_READWRITE));
992 993

  /**
994 995 996 997 998 999
   * ChamplainView:max-zoom-level:
   *
   * The highest allowed level of zoom of the content.
   *
   * Since: 0.4
   */
1000 1001
  g_object_class_install_property (object_class,
      PROP_MAX_ZOOM_LEVEL,
Jiří Techet's avatar
Jiří Techet committed
1002
      g_param_spec_uint ("max-zoom-level",
1003 1004
          "Max zoom level",
          "The highest allowed level of zoom",
1005 1006 1007 1008
          0, 
          20, 
          20, 
          CHAMPLAIN_PARAM_READWRITE));
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
1009 1010

  /**
1011 1012 1013 1014 1015 1016
   * ChamplainView:map-source:
   *
   * The #ChamplainMapSource being displayed
   *
   * Since: 0.2
   */
1017 1018
  g_object_class_install_property (object_class,
      PROP_MAP_SOURCE,
1019
      g_param_spec_object ("map-source",
1020 1021 1022 1023
          "Map source",
          "The map source being displayed",
          CHAMPLAIN_TYPE_MAP_SOURCE,
          CHAMPLAIN_PARAM_READWRITE));
Pierre-Luc Beaudoin's avatar
Pierre-Luc Beaudoin committed
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 1034 1035 1036
      PROP_KINETIC_MODE,
      g_param_spec_boolean ("kinetic-mode",
          "Kinetic Mode",
          "Determines whether the view should use kinetic mode.",
1037
          FALSE, 
1038
          CHAMPLAIN_PARAM_READWRITE));
1039

1040
  /**
1041
   * ChamplainView:deceleration:
1042 1043 1044
   *
   * The deceleration rate for the kinetic mode. The default value is 1.1.
   *
1045
   * Since: 0.10
1046
   */
1047
  g_object_class_install_property (object_class,
1048 1049
      PROP_DECELERATION,
      g_param_spec_double ("deceleration",
1050 1051
          "Deceleration rate",
          "Rate at which the view will decelerate in kinetic mode.",
1052 1053 1054 1055
          1.0001, 
          2.0, 
          1.1, 
          CHAMPLAIN_PARAM_READWRITE));
1056

1057
  /**
1058 1059 1060 1061 1062 1063
   * ChamplainView:keep-center-on-resize:
   *
   * Keep the current centered position when resizing the view.
   *
   * Since: 0.2.7
   */
1064
  g_object_class_install_property (object_class,
1065 1066 1067
      PROP_KEEP_CENTER_ON_RESIZE,
      g_param_spec_boolean ("keep-center-on-resize",
          "Keep center on resize",
1068
          "Keep the current centered position upon resizing",
1069 1070
          TRUE, 
          CHAMPLAIN_PARAM_READWRITE));
1071

1072
  /**
1073 1074 1075 1076 1077 1078
   * ChamplainView:zoom-on-double-click:
   *
   * Should the view zoom in and recenter when the user double click on the map.
   *
   * Since: 0.4
   */
1079
  g_object_class_install_property (object_class,
1080 1081 1082 1083
      PROP_ZOOM_ON_DOUBLE_CLICK,
      g_param_spec_boolean ("zoom-on-double-click",
          "Zoom in on double click",
          "Zoom in and recenter on double click on the map",
1084 1085
          TRUE, 
          CHAMPLAIN_PARAM_READWRITE));
1086

1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101
  /**
   * ChamplainView:animate-zoom:
   *
   * Animate zoom change when zooming in/out.
   *
   * Since: 0.12
   */
  g_object_class_install_property (object_class,
      PROP_ANIMATE_ZOOM,
      g_param_spec_boolean ("animate-zoom",
          "Animate zoom level change",
          "Animate zoom change when zooming in/out",
          TRUE, 
          CHAMPLAIN_PARAM_READWRITE));

1102
  /**
1103
   * ChamplainView:state:
1104 1105 1106 1107 1108 1109
   *
   * The view's global state. Useful to inform using if the view is busy loading
   * tiles or not.
   *
   * Since: 0.4
   */
1110
  g_object_class_install_property (object_class,
1111 1112 1113 1114 1115 1116 1117
      PROP_STATE,
      g_param_spec_enum ("state",
          "View's state",
          "View's global state",
          CHAMPLAIN_TYPE_STATE,
          CHAMPLAIN_STATE_NONE,
          G_PARAM_READABLE));
1118

1119
  /**
1120
   * ChamplainView:background-pattern:
1121 1122 1123 1124 1125 1126
   *
   * The pattern displayed in the background of the map.
   *
   * Since: 0.12.4
   */
  g_object_class_install_property (object_class,
1127 1128 1129
      PROP_BACKGROUND_PATTERN,
      g_param_spec_object ("background-pattern",
          "Background pattern",
1130 1131 1132 1133
          "The tile's background pattern",
          CLUTTER_TYPE_ACTOR,
          G_PARAM_READWRITE));

1134 1135 1136 1137
  /**
   * ChamplainView:goto-animation-mode:
   *
   * The mode of animation when going to a location.
1138 1139 1140 1141
   * 
   * Please note that animation of #champlain_view_ensure_visible also
   * involves a 'goto' animation.
   * 
1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
   */
  g_object_class_install_property (object_class,
      PROP_GOTO_ANIMATION_MODE,
      g_param_spec_enum ("goto-animation-mode",
          "Go to animation mode",
          "The mode of animation when going to a location",
          CLUTTER_TYPE_ANIMATION_MODE,
          CLUTTER_EASE_IN_OUT_CIRC,
          G_PARAM_READWRITE));

  /**
   * ChamplainView:goto-animation-duration:
   *
Jiří Techet's avatar
Jiří Techet committed
1155
   * The duration of an animation when going to a location.
1156 1157
   * A value of 0 means that the duration is calculated automatically for you.
   *
1158 1159 1160
   * Please note that animation of #champlain_view_ensure_visible also
   * involves a 'goto' animation.
   *
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171
   */
  g_object_class_install_property (object_class,
      PROP_GOTO_ANIMATION_DURATION,
      g_param_spec_uint ("goto-animation-duration",
          "Go to animation duration",
          "The duration of an animation when going to a location",
          0,
          G_MAXINT,
          0,
          G_PARAM_READWRITE));

1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
  /**
   * ChamplainView:world:
   *
   * Set a bounding box to limit the world to. No tiles will be loaded
   * outside of this bounding box. It will not be possible to scroll outside
   * of this bounding box.
   *
   * Default world is the actual world.
   *
   * Since: 0.12.11
   */
  g_object_class_install_property (object_class,
      PROP_WORLD,
      g_param_spec_boxed ("world",
          "The world",
          "The bounding box to limit the #ChamplainView to",
          CHAMPLAIN_TYPE_BOUNDING_BOX,
          G_PARAM_READWRITE));

1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203