/* * Copyright (C) 2008-2009 Pierre-Luc Beaudoin * Copyright (C) 2010-2013 Jiri Techet * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /** * SECTION:champlain-tile * @short_description: An object that represent map tiles * * This object represents map tiles. Tiles are loaded by #ChamplainMapSource. */ #include "champlain-tile.h" #include "champlain-enum-types.h" #include "champlain-private.h" #include #include #include #include #include #include static void set_surface (ChamplainExportable *exportable, cairo_surface_t *surface); static cairo_surface_t *get_surface (ChamplainExportable *exportable); static void exportable_interface_init (ChamplainExportableIface *iface); struct _ChamplainTilePrivate { guint x; /* The x position on the map (in pixels) */ guint y; /* The y position on the map (in pixels) */ guint size; /* The tile's width and height (only support square tiles */ guint zoom_level; /* The tile's zoom level */ ChamplainState state; /* The tile state: loading, validation, done */ /* The tile actor that will be displayed after champlain_tile_display_content () */ ClutterActor *content_actor; gboolean fade_in; GTimeVal *modified_time; /* The last modified time of the cache */ gchar *etag; /* The HTTP ETag sent by the server */ gboolean content_displayed; cairo_surface_t *surface; }; G_DEFINE_TYPE_WITH_CODE (ChamplainTile, champlain_tile, CLUTTER_TYPE_ACTOR, G_ADD_PRIVATE (ChamplainTile) G_IMPLEMENT_INTERFACE (CHAMPLAIN_TYPE_EXPORTABLE, exportable_interface_init)) enum { PROP_0, PROP_X, PROP_Y, PROP_ZOOM_LEVEL, PROP_SIZE, PROP_STATE, PROP_CONTENT, PROP_ETAG, PROP_FADE_IN, PROP_SURFACE }; enum { /* normal signals */ RENDER_COMPLETE, LAST_SIGNAL }; static guint champlain_tile_signals[LAST_SIGNAL] = { 0, }; static void champlain_tile_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { ChamplainTile *self = CHAMPLAIN_TILE (object); switch (property_id) { case PROP_X: g_value_set_uint (value, champlain_tile_get_x (self)); break; case PROP_Y: g_value_set_uint (value, champlain_tile_get_y (self)); break; case PROP_ZOOM_LEVEL: g_value_set_uint (value, champlain_tile_get_zoom_level (self)); break; case PROP_SIZE: g_value_set_uint (value, champlain_tile_get_size (self)); break; case PROP_STATE: g_value_set_enum (value, champlain_tile_get_state (self)); break; case PROP_CONTENT: g_value_set_object (value, champlain_tile_get_content (self)); break; case PROP_ETAG: g_value_set_string (value, champlain_tile_get_etag (self)); break; case PROP_FADE_IN: g_value_set_boolean (value, champlain_tile_get_fade_in (self)); break; case PROP_SURFACE: g_value_set_boxed (value, get_surface (CHAMPLAIN_EXPORTABLE (self))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void champlain_tile_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { ChamplainTile *self = CHAMPLAIN_TILE (object); switch (property_id) { case PROP_X: champlain_tile_set_x (self, g_value_get_uint (value)); break; case PROP_Y: champlain_tile_set_y (self, g_value_get_uint (value)); break; case PROP_ZOOM_LEVEL: champlain_tile_set_zoom_level (self, g_value_get_uint (value)); break; case PROP_SIZE: champlain_tile_set_size (self, g_value_get_uint (value)); break; case PROP_STATE: champlain_tile_set_state (self, g_value_get_enum (value)); break; case PROP_CONTENT: champlain_tile_set_content (self, g_value_get_object (value)); break; case PROP_ETAG: champlain_tile_set_etag (self, g_value_get_string (value)); break; case PROP_FADE_IN: champlain_tile_set_fade_in (self, g_value_get_boolean (value)); break; case PROP_SURFACE: set_surface (CHAMPLAIN_EXPORTABLE (self), g_value_get_boxed (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void champlain_tile_dispose (GObject *object) { ChamplainTilePrivate *priv = CHAMPLAIN_TILE (object)->priv; if (!priv->content_displayed && priv->content_actor) { clutter_actor_destroy (priv->content_actor); priv->content_actor = NULL; } g_clear_pointer (&priv->surface, cairo_surface_destroy); G_OBJECT_CLASS (champlain_tile_parent_class)->dispose (object); } static void champlain_tile_finalize (GObject *object) { ChamplainTilePrivate *priv = CHAMPLAIN_TILE (object)->priv; g_free (priv->modified_time); g_free (priv->etag); G_OBJECT_CLASS (champlain_tile_parent_class)->finalize (object); } static void champlain_tile_class_init (ChamplainTileClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = champlain_tile_get_property; object_class->set_property = champlain_tile_set_property; object_class->dispose = champlain_tile_dispose; object_class->finalize = champlain_tile_finalize; /** * ChamplainTile:x: * * The x position of the tile * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_X, g_param_spec_uint ("x", "x", "The X position of the tile", 0, G_MAXINT, 0, G_PARAM_READWRITE)); /** * ChamplainTile:y: * * The y position of the tile * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_Y, g_param_spec_uint ("y", "y", "The Y position of the tile", 0, G_MAXINT, 0, G_PARAM_READWRITE)); /** * ChamplainTile:zoom-level: * * The zoom level of the tile * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_ZOOM_LEVEL, g_param_spec_uint ("zoom-level", "Zoom Level", "The zoom level of the tile", 0, G_MAXINT, 0, G_PARAM_READWRITE)); /** * ChamplainTile:size: * * The size of the tile in pixels * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_SIZE, g_param_spec_uint ("size", "Size", "The size of the tile", 0, G_MAXINT, 256, G_PARAM_READWRITE)); /** * ChamplainTile:state: * * The state of the tile * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_STATE, g_param_spec_enum ("state", "State", "The state of the tile", CHAMPLAIN_TYPE_STATE, CHAMPLAIN_STATE_NONE, G_PARAM_READWRITE)); /** * ChamplainTile:content: * * The #ClutterActor with the specific image content. When changing this * property, the new actor will be faded in. * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_CONTENT, g_param_spec_object ("content", "Content", "The tile's content", CLUTTER_TYPE_ACTOR, G_PARAM_READWRITE)); /** * ChamplainTile:etag: * * The tile's ETag. This information is sent by some web servers as a mean * to identify if a tile has changed. This information is saved in the cache * and sent in GET queries. * * Since: 0.4 */ g_object_class_install_property (object_class, PROP_ETAG, g_param_spec_string ("etag", "Entity Tag", "The entity tag of the tile", NULL, G_PARAM_READWRITE)); /** * ChamplainTile:fade-in: * * Specifies whether the tile should fade in when loading * * Since: 0.6 */ g_object_class_install_property (object_class, PROP_FADE_IN, g_param_spec_boolean ("fade-in", "Fade In", "Tile should fade in", FALSE, G_PARAM_READWRITE)); g_object_class_override_property (object_class, PROP_SURFACE, "surface"); /** * ChamplainTile::render-complete: * @self: a #ChamplainTile * @data: the result of the rendering * @size: size of data * @error: TRUE if there was an error during rendering * * The #ChamplainTile::render-complete signal is emitted when rendering of the tile is * completed by the renderer. * * Since: 0.10 */ champlain_tile_signals[RENDER_COMPLETE] = g_signal_new ("render-complete", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_POINTER, G_TYPE_UINT, G_TYPE_BOOLEAN); } static void champlain_tile_init (ChamplainTile *self) { ChamplainTilePrivate *priv = champlain_tile_get_instance_private (self); self->priv = priv; priv->state = CHAMPLAIN_STATE_NONE; priv->x = 0; priv->y = 0; priv->zoom_level = 0; priv->size = 0; priv->modified_time = NULL; priv->etag = NULL; priv->fade_in = FALSE; priv->content_displayed = FALSE; priv->content_actor = NULL; } static void set_surface (ChamplainExportable *exportable, cairo_surface_t *surface) { g_return_if_fail (CHAMPLAIN_TILE (exportable)); g_return_if_fail (surface != NULL); ChamplainTile *self = CHAMPLAIN_TILE (exportable); if (self->priv->surface == surface) return; cairo_surface_destroy (self->priv->surface); self->priv->surface = cairo_surface_reference (surface); g_object_notify (G_OBJECT (self), "surface"); } static cairo_surface_t * get_surface (ChamplainExportable *exportable) { g_return_val_if_fail (CHAMPLAIN_IS_TILE (exportable), NULL); return CHAMPLAIN_TILE (exportable)->priv->surface; } static void exportable_interface_init (ChamplainExportableIface *iface) { iface->get_surface = get_surface; iface->set_surface = set_surface; } /** * champlain_tile_new: * * Creates an instance of #ChamplainTile. * * Returns: a new #ChamplainTile * * Since: 0.4 */ ChamplainTile * champlain_tile_new (void) { return g_object_new (CHAMPLAIN_TYPE_TILE, NULL); } /** * champlain_tile_get_x: * @self: the #ChamplainTile * * Gets the tile's x position. * * Returns: the tile's x position * * Since: 0.4 */ guint champlain_tile_get_x (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), 0); return self->priv->x; } /** * champlain_tile_get_y: * @self: the #ChamplainTile * * Gets the tile's y position. * * Returns: the tile's y position * * Since: 0.4 */ guint champlain_tile_get_y (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), 0); return self->priv->y; } /** * champlain_tile_get_zoom_level: * @self: the #ChamplainTile * * Gets the tile's zoom level. * * Returns: the tile's zoom level * * Since: 0.4 */ guint champlain_tile_get_zoom_level (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), 0); return self->priv->zoom_level; } /** * champlain_tile_get_size: * @self: the #ChamplainTile * * Gets the tile's size. * * Returns: the tile's size in pixels * * Since: 0.4 */ guint champlain_tile_get_size (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), 0); return self->priv->size; } /** * champlain_tile_get_state: * @self: the #ChamplainTile * * Gets the current state of tile loading. * * Returns: the tile's #ChamplainState * * Since: 0.4 */ ChamplainState champlain_tile_get_state (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), CHAMPLAIN_STATE_NONE); return self->priv->state; } /** * champlain_tile_set_x: * @self: the #ChamplainTile * @x: the position * * Sets the tile's x position * * Since: 0.4 */ void champlain_tile_set_x (ChamplainTile *self, guint x) { g_return_if_fail (CHAMPLAIN_TILE (self)); self->priv->x = x; g_object_notify (G_OBJECT (self), "x"); } /** * champlain_tile_set_y: * @self: the #ChamplainTile * @y: the position * * Sets the tile's y position * * Since: 0.4 */ void champlain_tile_set_y (ChamplainTile *self, guint y) { g_return_if_fail (CHAMPLAIN_TILE (self)); self->priv->y = y; g_object_notify (G_OBJECT (self), "y"); } /** * champlain_tile_set_zoom_level: * @self: the #ChamplainTile * @zoom_level: the zoom level * * Sets the tile's zoom level * * Since: 0.4 */ void champlain_tile_set_zoom_level (ChamplainTile *self, guint zoom_level) { g_return_if_fail (CHAMPLAIN_TILE (self)); self->priv->zoom_level = zoom_level; g_object_notify (G_OBJECT (self), "zoom-level"); } /** * champlain_tile_set_size: * @self: the #ChamplainTile * @size: the size in pixels * * Sets the tile's size * * Since: 0.4 */ void champlain_tile_set_size (ChamplainTile *self, guint size) { g_return_if_fail (CHAMPLAIN_TILE (self)); self->priv->size = size; g_object_notify (G_OBJECT (self), "size"); } /** * champlain_tile_set_state: * @self: the #ChamplainTile * @state: a #ChamplainState * * Sets the tile's #ChamplainState * * Since: 0.4 */ void champlain_tile_set_state (ChamplainTile *self, ChamplainState state) { g_return_if_fail (CHAMPLAIN_TILE (self)); ChamplainTilePrivate *priv = self->priv; if (state == priv->state) return; priv->state = state; g_object_notify (G_OBJECT (self), "state"); } /** * champlain_tile_new_full: * @x: the x position * @y: the y position * @size: the size in pixels * @zoom_level: the zoom level * * Creates an instance of #ChamplainTile. * * Returns: a #ChamplainTile * * Since: 0.4 */ ChamplainTile * champlain_tile_new_full (guint x, guint y, guint size, guint zoom_level) { return g_object_new (CHAMPLAIN_TYPE_TILE, "x", x, "y", y, "zoom-level", zoom_level, "size", size, NULL); } /** * champlain_tile_get_modified_time: * @self: the #ChamplainTile * * Gets the tile's last modified time. * * Returns: the tile's last modified time * * Since: 0.4 */ G_CONST_RETURN GTimeVal * champlain_tile_get_modified_time (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), NULL); return self->priv->modified_time; } /** * champlain_tile_set_modified_time: * @self: the #ChamplainTile * @time: a #GTimeVal, the value will be copied * * Sets the tile's modified time * * Since: 0.4 */ void champlain_tile_set_modified_time (ChamplainTile *self, const GTimeVal *time_) { g_return_if_fail (CHAMPLAIN_TILE (self)); g_return_if_fail (time_ != NULL); ChamplainTilePrivate *priv = self->priv; g_free (priv->modified_time); priv->modified_time = g_memdup (time_, sizeof (GTimeVal)); } /** * champlain_tile_get_etag: * @self: the #ChamplainTile * * Gets the tile's ETag. * * Returns: the tile's ETag * * Since: 0.4 */ G_CONST_RETURN gchar * champlain_tile_get_etag (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), ""); return self->priv->etag; } /** * champlain_tile_set_etag: * @self: the #ChamplainTile * @etag: the tile's ETag as sent by the server * * Sets the tile's ETag * * Since: 0.4 */ void champlain_tile_set_etag (ChamplainTile *self, const gchar *etag) { g_return_if_fail (CHAMPLAIN_TILE (self)); ChamplainTilePrivate *priv = self->priv; g_free (priv->etag); priv->etag = g_strdup (etag); g_object_notify (G_OBJECT (self), "etag"); } /** * champlain_tile_set_content: * @self: the #ChamplainTile * @actor: the new content * * Sets the tile's content. To also disppay the tile, you have to call * champlain_tile_display_content() in addition. * * Since: 0.4 */ void champlain_tile_set_content (ChamplainTile *self, ClutterActor *actor) { g_return_if_fail (CHAMPLAIN_TILE (self)); g_return_if_fail (CLUTTER_ACTOR (actor)); ChamplainTilePrivate *priv = self->priv; if (!priv->content_displayed && priv->content_actor) clutter_actor_destroy (priv->content_actor); priv->content_actor = g_object_ref_sink (actor); priv->content_displayed = FALSE; g_object_notify (G_OBJECT (self), "content"); } static void fade_in_completed (ClutterActor *actor, const gchar *transition_name, gboolean is_finished, ChamplainTile *self) { if (clutter_actor_get_n_children (CLUTTER_ACTOR (self)) > 1) clutter_actor_destroy (clutter_actor_get_first_child (CLUTTER_ACTOR (self))); g_signal_handlers_disconnect_by_func (actor, fade_in_completed, self); } /** * champlain_tile_display_content: * @self: the #ChamplainTile * * Displays the tile's content. * * Since: 0.8 */ void champlain_tile_display_content (ChamplainTile *self) { g_return_if_fail (CHAMPLAIN_TILE (self)); ChamplainTilePrivate *priv = self->priv; if (!priv->content_actor || priv->content_displayed) return; clutter_actor_add_child (CLUTTER_ACTOR (self), priv->content_actor); g_object_unref (priv->content_actor); priv->content_displayed = TRUE; clutter_actor_set_opacity (priv->content_actor, 0); clutter_actor_save_easing_state (priv->content_actor); if (priv->fade_in) { clutter_actor_set_easing_mode (priv->content_actor, CLUTTER_EASE_IN_CUBIC); clutter_actor_set_easing_duration (priv->content_actor, 500); } else { clutter_actor_set_easing_mode (priv->content_actor, CLUTTER_LINEAR); clutter_actor_set_easing_duration (priv->content_actor, 150); } clutter_actor_set_opacity (priv->content_actor, 255); clutter_actor_restore_easing_state (priv->content_actor); g_signal_connect (priv->content_actor, "transition-stopped::opacity", G_CALLBACK (fade_in_completed), self); } /** * champlain_tile_get_content: * @self: the #ChamplainTile * * Gets the tile's content actor. * * Returns: (transfer none): the tile's content, this actor will change each time the tile's content changes. * You should not unref this content, it is owned by the tile. * * Since: 0.4 */ ClutterActor * champlain_tile_get_content (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), NULL); return self->priv->content_actor; } /** * champlain_tile_get_fade_in: * @self: the #ChamplainTile * * Checks whether the tile should fade in. * * Returns: the return value determines whether the tile should fade in when loading. * * Since: 0.6 */ gboolean champlain_tile_get_fade_in (ChamplainTile *self) { g_return_val_if_fail (CHAMPLAIN_TILE (self), FALSE); return self->priv->fade_in; } /** * champlain_tile_set_fade_in: * @self: the #ChamplainTile * @fade_in: determines whether the tile should fade in when loading * * Sets the flag determining whether the tile should fade in when loading * * Since: 0.6 */ void champlain_tile_set_fade_in (ChamplainTile *self, gboolean fade_in) { g_return_if_fail (CHAMPLAIN_TILE (self)); self->priv->fade_in = fade_in; g_object_notify (G_OBJECT (self), "fade-in"); }