/* * Copyright (C) 2011-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-scale * @short_description: An actor displaying a scale. * * An actor displaying a scale. */ #include "config.h" #include "champlain-scale.h" #include "champlain-defines.h" #include "champlain-private.h" #include "champlain-enum-types.h" #include "champlain-view.h" #include #include #include #include #include #include enum { /* normal signals */ LAST_SIGNAL }; enum { PROP_0, PROP_SCALE_UNIT, PROP_MAX_SCALE_WIDTH, }; /* static guint champlain_scale_signals[LAST_SIGNAL] = { 0, }; */ struct _ChamplainScalePrivate { ChamplainUnit scale_unit; guint max_scale_width; gfloat text_height; ClutterContent *canvas; ChamplainView *view; gboolean redraw_scheduled; }; G_DEFINE_TYPE_WITH_PRIVATE (ChamplainScale, champlain_scale, CLUTTER_TYPE_ACTOR) #define SCALE_HEIGHT 5 #define GAP_SIZE 2 #define SCALE_INSIDE_PADDING 10 #define SCALE_LINE_WIDTH 2 static void champlain_scale_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { ChamplainScalePrivate *priv = CHAMPLAIN_SCALE (object)->priv; switch (prop_id) { case PROP_MAX_SCALE_WIDTH: g_value_set_uint (value, priv->max_scale_width); break; case PROP_SCALE_UNIT: g_value_set_enum (value, priv->scale_unit); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void champlain_scale_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { ChamplainScale *scale = CHAMPLAIN_SCALE (object); switch (prop_id) { case PROP_MAX_SCALE_WIDTH: champlain_scale_set_max_width (scale, g_value_get_uint (value)); break; case PROP_SCALE_UNIT: champlain_scale_set_unit (scale, g_value_get_enum (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } } static void champlain_scale_dispose (GObject *object) { ChamplainScalePrivate *priv = CHAMPLAIN_SCALE (object)->priv; if (priv->view) { champlain_scale_disconnect_view (CHAMPLAIN_SCALE (object)); priv->view = NULL; } if (priv->canvas) { g_object_unref (priv->canvas); priv->canvas = NULL; } G_OBJECT_CLASS (champlain_scale_parent_class)->dispose (object); } static void champlain_scale_finalize (GObject *object) { G_OBJECT_CLASS (champlain_scale_parent_class)->finalize (object); } static void champlain_scale_class_init (ChamplainScaleClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = champlain_scale_finalize; object_class->dispose = champlain_scale_dispose; object_class->get_property = champlain_scale_get_property; object_class->set_property = champlain_scale_set_property; /** * ChamplainScale:max-width: * * The size of the map scale on screen in pixels. * * Since: 0.10 */ g_object_class_install_property (object_class, PROP_MAX_SCALE_WIDTH, g_param_spec_uint ("max-width", "The width of the scale", "The max width of the scale" "on screen", 1, 2000, 100, G_PARAM_READWRITE)); /** * ChamplainScale:unit: * * The scale's units. * * Since: 0.10 */ g_object_class_install_property (object_class, PROP_SCALE_UNIT, g_param_spec_enum ("unit", "The scale's unit", "The map scale's unit", CHAMPLAIN_TYPE_UNIT, CHAMPLAIN_UNIT_KM, G_PARAM_READWRITE)); } static gboolean redraw_scale (ClutterCanvas *canvas, cairo_t *cr, int w, int h, ChamplainScale *scale) { gboolean is_small_unit = TRUE; /* indicates if using meters */ ClutterActor *text; gfloat width, height; ChamplainScalePrivate *priv = scale->priv; gfloat m_per_pixel; gfloat scale_width = priv->max_scale_width; gchar *label; gfloat base; gfloat factor; gboolean final_unit = FALSE; gint zoom_level; gdouble lat, lon; gfloat offset; ChamplainMapSource *map_source; if (!priv->view) return FALSE; zoom_level = champlain_view_get_zoom_level (priv->view); map_source = champlain_view_get_map_source (priv->view); lat = champlain_view_get_center_latitude (priv->view); lon = champlain_view_get_center_longitude (priv->view); m_per_pixel = champlain_map_source_get_meters_per_pixel (map_source, zoom_level, lat, lon); if (priv->scale_unit == CHAMPLAIN_UNIT_MILES) m_per_pixel *= 3.28; /* m_per_pixel is now in ft */ /* This loop will find the pretty value to display on the scale. * It will be run once for metric units, and twice for imperials * so that both feet and miles have pretty numbers. */ do { /* Keep the previous power of 10 */ base = floor (log (m_per_pixel * scale_width) / log (10)); base = pow (10, base); /* How many times can it be fitted in our max scale width */ g_assert (base > 0); g_assert (m_per_pixel * scale_width / base > 0); scale_width /= m_per_pixel * scale_width / base; g_assert (scale_width > 0); factor = floor (priv->max_scale_width / scale_width); base *= factor; scale_width *= factor; if (priv->scale_unit == CHAMPLAIN_UNIT_KM) { if (base / 1000.0 >= 1) { base /= 1000.0; /* base is now in km */ is_small_unit = FALSE; } final_unit = TRUE; /* Don't need to recompute */ } else if (priv->scale_unit == CHAMPLAIN_UNIT_MILES) { if (is_small_unit && base / 5280.0 >= 1) { m_per_pixel /= 5280.0; /* m_per_pixel is now in miles */ is_small_unit = FALSE; /* we need to recompute the base because 1000 ft != 1 mile */ } else final_unit = TRUE; } } while (!final_unit); text = clutter_container_find_child_by_name (CLUTTER_CONTAINER (scale), "scale-far-label"); label = g_strdup_printf ("%g", base); /* Get only digits width for centering */ clutter_text_set_text (CLUTTER_TEXT (text), label); g_free (label); clutter_actor_get_size (text, &width, NULL); /* actual label with unit */ label = g_strdup_printf ("%g %s", base, priv->scale_unit == CHAMPLAIN_UNIT_KM ? (is_small_unit ? "m" : "km") : (is_small_unit ? "ft" : "miles")); clutter_text_set_text (CLUTTER_TEXT (text), label); g_free (label); clutter_actor_set_position (text, ceil (scale_width - width / 2) + SCALE_INSIDE_PADDING, SCALE_INSIDE_PADDING); text = clutter_container_find_child_by_name (CLUTTER_CONTAINER (scale), "scale-mid-label"); label = g_strdup_printf ("%g", base / 2.0); clutter_text_set_text (CLUTTER_TEXT (text), label); clutter_actor_get_size (text, &width, &height); clutter_actor_set_position (text, ceil ((scale_width - width) / 2) + SCALE_INSIDE_PADDING, SCALE_INSIDE_PADDING); g_free (label); /* Draw the line */ cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); cairo_paint (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgb (cr, 0, 0, 0); cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); cairo_set_line_width (cr, SCALE_LINE_WIDTH); offset = SCALE_INSIDE_PADDING + priv->text_height + GAP_SIZE; /* First tick */ cairo_move_to (cr, SCALE_INSIDE_PADDING, offset); cairo_line_to (cr, SCALE_INSIDE_PADDING, offset + SCALE_HEIGHT); cairo_stroke (cr); /* Line */ cairo_move_to (cr, SCALE_INSIDE_PADDING, offset + SCALE_HEIGHT); cairo_line_to (cr, scale_width + SCALE_INSIDE_PADDING, offset + SCALE_HEIGHT); cairo_stroke (cr); /* Middle tick */ cairo_move_to (cr, scale_width / 2 + SCALE_INSIDE_PADDING, offset); cairo_line_to (cr, scale_width / 2 + SCALE_INSIDE_PADDING, offset + SCALE_HEIGHT); cairo_stroke (cr); /* Last tick */ cairo_move_to (cr, scale_width + SCALE_INSIDE_PADDING, offset); cairo_line_to (cr, scale_width + SCALE_INSIDE_PADDING, offset + SCALE_HEIGHT); cairo_stroke (cr); return FALSE; } static gboolean invalidate_canvas (ChamplainScale *layer) { ChamplainScalePrivate *priv = layer->priv; clutter_content_invalidate (priv->canvas); priv->redraw_scheduled = FALSE; return FALSE; } static void schedule_redraw (ChamplainScale *layer) { if (!layer->priv->redraw_scheduled) { layer->priv->redraw_scheduled = TRUE; g_idle_add_full (CLUTTER_PRIORITY_REDRAW, (GSourceFunc) invalidate_canvas, g_object_ref (layer), (GDestroyNotify) g_object_unref); } } static void create_scale (ChamplainScale *scale) { ClutterActor *text, *scale_actor; gfloat width, height; ChamplainScalePrivate *priv = scale->priv; clutter_actor_destroy_all_children (CLUTTER_ACTOR (scale)); text = clutter_text_new_with_text ("Sans 9", "X km"); clutter_actor_set_name (text, "scale-far-label"); clutter_actor_add_child (CLUTTER_ACTOR (scale), text); text = clutter_text_new_with_text ("Sans 9", "X"); clutter_actor_set_name (text, "scale-mid-label"); clutter_actor_add_child (CLUTTER_ACTOR (scale), text); text = clutter_text_new_with_text ("Sans 9", "0"); clutter_actor_add_child (CLUTTER_ACTOR (scale), text); clutter_actor_get_size (text, &width, &priv->text_height); clutter_actor_set_position (text, SCALE_INSIDE_PADDING - ceil (width / 2), SCALE_INSIDE_PADDING); width = priv->max_scale_width + 2 * SCALE_INSIDE_PADDING; height = SCALE_HEIGHT + priv->text_height + GAP_SIZE + 2 * SCALE_INSIDE_PADDING; priv->canvas = clutter_canvas_new (); clutter_canvas_set_size (CLUTTER_CANVAS (priv->canvas), width, height); g_signal_connect (priv->canvas, "draw", G_CALLBACK (redraw_scale), scale); scale_actor = clutter_actor_new (); clutter_actor_set_size (scale_actor, width, height); clutter_actor_set_content (scale_actor, priv->canvas); clutter_actor_add_child (CLUTTER_ACTOR (scale), scale_actor); clutter_actor_set_opacity (CLUTTER_ACTOR (scale), 200); schedule_redraw (scale); } static void champlain_scale_init (ChamplainScale *scale) { ChamplainScalePrivate *priv = champlain_scale_get_instance_private (scale); scale->priv = priv; priv->scale_unit = CHAMPLAIN_UNIT_KM; priv->max_scale_width = 100; priv->view = NULL; priv->redraw_scheduled = FALSE; create_scale (scale); } /** * champlain_scale_new: * * Creates an instance of #ChamplainScale. * * Returns: a new #ChamplainScale. * * Since: 0.10 */ ClutterActor * champlain_scale_new (void) { return CLUTTER_ACTOR (g_object_new (CHAMPLAIN_TYPE_SCALE, NULL)); } /** * champlain_scale_set_max_width: * @scale: a #ChamplainScale * @value: the number of pixels * * Sets the maximum width of the scale on the screen in pixels * * Since: 0.10 */ void champlain_scale_set_max_width (ChamplainScale *scale, guint value) { g_return_if_fail (CHAMPLAIN_IS_SCALE (scale)); scale->priv->max_scale_width = value; create_scale (scale); g_object_notify (G_OBJECT (scale), "max-width"); } /** * champlain_scale_set_unit: * @scale: a #ChamplainScale * @unit: a #ChamplainUnit * * Sets the scale unit. * * Since: 0.10 */ void champlain_scale_set_unit (ChamplainScale *scale, ChamplainUnit unit) { g_return_if_fail (CHAMPLAIN_IS_SCALE (scale)); scale->priv->scale_unit = unit; g_object_notify (G_OBJECT (scale), "unit"); schedule_redraw (scale); } /** * champlain_scale_get_max_width: * @scale: a #ChamplainScale * * Gets the maximum scale width. * * Returns: The maximum scale width in pixels. * * Since: 0.10 */ guint champlain_scale_get_max_width (ChamplainScale *scale) { g_return_val_if_fail (CHAMPLAIN_IS_SCALE (scale), FALSE); return scale->priv->max_scale_width; } /** * champlain_scale_get_unit: * @scale: a #ChamplainScale * * Gets the unit used by the scale. * * Returns: The unit used by the scale * * Since: 0.10 */ ChamplainUnit champlain_scale_get_unit (ChamplainScale *scale) { g_return_val_if_fail (CHAMPLAIN_IS_SCALE (scale), FALSE); return scale->priv->scale_unit; } static void redraw_scale_cb (G_GNUC_UNUSED GObject *gobject, G_GNUC_UNUSED GParamSpec *arg1, ChamplainScale *scale) { schedule_redraw (scale); } /** * champlain_scale_connect_view: * @scale: a #ChamplainScale * @view: a #ChamplainView * * This method connects to the necessary signals of #ChamplainView to make the * scale adapt to the current latitude and longitude. * * Since: 0.10 */ void champlain_scale_connect_view (ChamplainScale *scale, ChamplainView *view) { g_return_if_fail (CHAMPLAIN_IS_SCALE (scale)); scale->priv->view = g_object_ref (view); g_signal_connect (view, "notify::latitude", G_CALLBACK (redraw_scale_cb), scale); schedule_redraw (scale); } /** * champlain_scale_disconnect_view: * @scale: a #ChamplainScale * * This method disconnects from the signals previously connected by champlain_scale_connect_view(). * * Since: 0.10 */ void champlain_scale_disconnect_view (ChamplainScale *scale) { g_return_if_fail (CHAMPLAIN_IS_SCALE (scale)); g_signal_handlers_disconnect_by_func (scale->priv->view, redraw_scale_cb, scale); g_object_unref (scale->priv->view); scale->priv->view = NULL; }