According to [1], we don't need to worry about GDK's global lock since we don't call gdk_threads_init() or gdk_threads_set_lock_functions(). The GDK threads API is being aggressively deprecated by GTK+ developers so let's just abandon it entirely. I've never really understood when you're supposed to use it or not use it anyway, so it's good to be rid of this confusion. [1] https://mail.gnome.org/archives/desktop-devel-list/2012-August/msg00005.html
1426 lines
33 KiB
C
1426 lines
33 KiB
C
/*
|
|
* Map widget.
|
|
*
|
|
* This program 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 of the License, or (at your option) version 3.
|
|
*
|
|
* This program 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 the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*
|
|
* Authors:
|
|
* Hans Petter Jansson <hpj@ximian.com>
|
|
*
|
|
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <math.h>
|
|
#include <stdlib.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "e-util/e-util-private.h"
|
|
#include "e-util/e-util.h"
|
|
|
|
#include "e-map.h"
|
|
|
|
#define E_MAP_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE \
|
|
((obj), E_TYPE_MAP, EMapPrivate))
|
|
|
|
#define E_MAP_TWEEN_TIMEOUT_MSECS 25
|
|
#define E_MAP_TWEEN_DURATION_MSECS 150
|
|
|
|
/* Scroll step increment */
|
|
|
|
#define SCROLL_STEP_SIZE 32
|
|
|
|
/* */
|
|
|
|
#define E_MAP_GET_WIDTH(map) gtk_adjustment_get_upper((map)->priv->hadjustment)
|
|
#define E_MAP_GET_HEIGHT(map) gtk_adjustment_get_upper((map)->priv->vadjustment)
|
|
|
|
/* Zoom state - keeps track of animation hacks */
|
|
|
|
typedef enum
|
|
{
|
|
E_MAP_ZOOMED_IN,
|
|
E_MAP_ZOOMED_OUT,
|
|
E_MAP_ZOOMING_IN,
|
|
E_MAP_ZOOMING_OUT
|
|
}
|
|
EMapZoomState;
|
|
|
|
/* The Tween struct used for zooming */
|
|
|
|
typedef struct _EMapTween EMapTween;
|
|
|
|
struct _EMapTween {
|
|
guint start_time;
|
|
guint end_time;
|
|
gdouble longitude_offset;
|
|
gdouble latitude_offset;
|
|
gdouble zoom_factor;
|
|
};
|
|
|
|
/* Private part of the EMap structure */
|
|
|
|
struct _EMapPrivate {
|
|
/* Pointer to map image */
|
|
GdkPixbuf *map_pixbuf;
|
|
cairo_surface_t *map_render_surface;
|
|
|
|
/* Settings */
|
|
gboolean frozen, smooth_zoom;
|
|
|
|
/* Adjustments for scrolling */
|
|
GtkAdjustment *hadjustment;
|
|
GtkAdjustment *vadjustment;
|
|
|
|
/* GtkScrollablePolicy needs to be checked when
|
|
* driving the scrollable adjustment values */
|
|
guint hscroll_policy : 1;
|
|
guint vscroll_policy : 1;
|
|
|
|
/* Current scrolling offsets */
|
|
gint xofs, yofs;
|
|
|
|
/* Realtime zoom data */
|
|
EMapZoomState zoom_state;
|
|
gdouble zoom_target_long, zoom_target_lat;
|
|
|
|
/* Dots */
|
|
GPtrArray *points;
|
|
|
|
/* Tweens */
|
|
GSList *tweens;
|
|
GTimer *timer;
|
|
guint timer_current_ms;
|
|
guint tween_id;
|
|
};
|
|
|
|
/* Properties */
|
|
|
|
enum {
|
|
PROP_0,
|
|
|
|
/* For scrollable interface */
|
|
PROP_HADJUSTMENT,
|
|
PROP_VADJUSTMENT,
|
|
PROP_HSCROLL_POLICY,
|
|
PROP_VSCROLL_POLICY
|
|
};
|
|
|
|
/* Internal prototypes */
|
|
|
|
static void update_render_surface (EMap *map, gboolean render_overlays);
|
|
static void set_scroll_area (EMap *map, gint width, gint height);
|
|
static void center_at (EMap *map, gdouble longitude, gdouble latitude);
|
|
static void scroll_to (EMap *map, gint x, gint y);
|
|
static gint load_map_background (EMap *map, gchar *name);
|
|
static void update_and_paint (EMap *map);
|
|
static void update_render_point (EMap *map, EMapPoint *point);
|
|
static void repaint_point (EMap *map, EMapPoint *point);
|
|
|
|
/* ------ *
|
|
* Tweens *
|
|
* ------ */
|
|
|
|
static gboolean
|
|
e_map_is_tweening (EMap *map)
|
|
{
|
|
return map->priv->timer != NULL;
|
|
}
|
|
|
|
static void
|
|
e_map_stop_tweening (EMap *map)
|
|
{
|
|
g_assert (map->priv->tweens == NULL);
|
|
|
|
if (!e_map_is_tweening (map))
|
|
return;
|
|
|
|
g_timer_destroy (map->priv->timer);
|
|
map->priv->timer = NULL;
|
|
g_source_remove (map->priv->tween_id);
|
|
map->priv->tween_id = 0;
|
|
}
|
|
|
|
static void
|
|
e_map_tween_destroy (EMap *map,
|
|
EMapTween *tween)
|
|
{
|
|
map->priv->tweens = g_slist_remove (map->priv->tweens, tween);
|
|
g_slice_free (EMapTween, tween);
|
|
|
|
if (map->priv->tweens == NULL)
|
|
e_map_stop_tweening (map);
|
|
}
|
|
|
|
static gboolean
|
|
e_map_do_tween_cb (gpointer data)
|
|
{
|
|
EMap *map = data;
|
|
GSList *walk;
|
|
|
|
map->priv->timer_current_ms =
|
|
g_timer_elapsed (map->priv->timer, NULL) * 1000;
|
|
gtk_widget_queue_draw (GTK_WIDGET (map));
|
|
|
|
/* Can't use for loop here, because we need to advance
|
|
* the list before deleting.
|
|
*/
|
|
walk = map->priv->tweens;
|
|
while (walk)
|
|
{
|
|
EMapTween *tween = walk->data;
|
|
|
|
walk = walk->next;
|
|
|
|
if (tween->end_time <= map->priv->timer_current_ms)
|
|
e_map_tween_destroy (map, tween);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
e_map_start_tweening (EMap *map)
|
|
{
|
|
if (e_map_is_tweening (map))
|
|
return;
|
|
|
|
map->priv->timer = g_timer_new ();
|
|
map->priv->timer_current_ms = 0;
|
|
map->priv->tween_id = g_timeout_add (
|
|
E_MAP_TWEEN_TIMEOUT_MSECS, e_map_do_tween_cb, map);
|
|
g_timer_start (map->priv->timer);
|
|
}
|
|
|
|
static void
|
|
e_map_tween_new (EMap *map,
|
|
guint msecs,
|
|
gdouble longitude_offset,
|
|
gdouble latitude_offset,
|
|
gdouble zoom_factor)
|
|
{
|
|
EMapTween *tween;
|
|
|
|
if (!map->priv->smooth_zoom)
|
|
return;
|
|
|
|
e_map_start_tweening (map);
|
|
|
|
tween = g_slice_new (EMapTween);
|
|
|
|
tween->start_time = map->priv->timer_current_ms;
|
|
tween->end_time = tween->start_time + msecs;
|
|
tween->longitude_offset = longitude_offset;
|
|
tween->latitude_offset = latitude_offset;
|
|
tween->zoom_factor = zoom_factor;
|
|
|
|
map->priv->tweens = g_slist_prepend (map->priv->tweens, tween);
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (map));
|
|
}
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (
|
|
EMap,
|
|
e_map,
|
|
GTK_TYPE_WIDGET,
|
|
G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL))
|
|
|
|
static void
|
|
e_map_get_current_location (EMap *map,
|
|
gdouble *longitude,
|
|
gdouble *latitude)
|
|
{
|
|
GtkAllocation allocation;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
|
|
e_map_window_to_world (
|
|
map, allocation.width / 2.0,
|
|
allocation.height / 2.0,
|
|
longitude, latitude);
|
|
}
|
|
|
|
static void
|
|
e_map_world_to_render_surface (EMap *map,
|
|
gdouble world_longitude,
|
|
gdouble world_latitude,
|
|
gdouble *win_x,
|
|
gdouble *win_y)
|
|
{
|
|
gint width, height;
|
|
|
|
width = E_MAP_GET_WIDTH (map);
|
|
height = E_MAP_GET_HEIGHT (map);
|
|
|
|
*win_x = (width / 2.0 + (width / 2.0) * world_longitude / 180.0);
|
|
*win_y = (height / 2.0 - (height / 2.0) * world_latitude / 90.0);
|
|
}
|
|
|
|
static void
|
|
e_map_tween_new_from (EMap *map,
|
|
guint msecs,
|
|
gdouble longitude,
|
|
gdouble latitude,
|
|
gdouble zoom)
|
|
{
|
|
gdouble current_longitude, current_latitude;
|
|
|
|
e_map_get_current_location (
|
|
map, ¤t_longitude, ¤t_latitude);
|
|
|
|
e_map_tween_new (
|
|
map, msecs,
|
|
longitude - current_longitude,
|
|
latitude - current_latitude,
|
|
zoom / e_map_get_magnification (map));
|
|
}
|
|
|
|
static gdouble
|
|
e_map_get_tween_effect (EMap *map,
|
|
EMapTween *tween)
|
|
{
|
|
gdouble elapsed;
|
|
|
|
elapsed = (gdouble)
|
|
(map->priv->timer_current_ms - tween->start_time) /
|
|
tween->end_time;
|
|
|
|
return MAX (0.0, 1.0 - elapsed);
|
|
}
|
|
|
|
static void
|
|
e_map_apply_tween (EMapTween *tween,
|
|
gdouble effect,
|
|
gdouble *longitude,
|
|
gdouble *latitude,
|
|
gdouble *zoom)
|
|
{
|
|
*zoom *= pow (tween->zoom_factor, effect);
|
|
*longitude += tween->longitude_offset * effect;
|
|
*latitude += tween->latitude_offset * effect;
|
|
}
|
|
|
|
static void
|
|
e_map_tweens_compute_matrix (EMap *map,
|
|
cairo_matrix_t *matrix)
|
|
{
|
|
GSList *walk;
|
|
gdouble zoom, x, y, latitude, longitude, effect;
|
|
GtkAllocation allocation;
|
|
|
|
if (!e_map_is_tweening (map)) {
|
|
cairo_matrix_init_translate (
|
|
matrix, -map->priv->xofs, -map->priv->yofs);
|
|
return;
|
|
}
|
|
|
|
e_map_get_current_location (map, &longitude, &latitude);
|
|
zoom = 1.0;
|
|
|
|
for (walk = map->priv->tweens; walk; walk = walk->next) {
|
|
EMapTween *tween = walk->data;
|
|
|
|
effect = e_map_get_tween_effect (map, tween);
|
|
e_map_apply_tween (tween, effect, &longitude, &latitude, &zoom);
|
|
}
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
cairo_matrix_init_translate (matrix,
|
|
allocation.width / 2.0,
|
|
allocation.height / 2.0);
|
|
|
|
e_map_world_to_render_surface (map, longitude, latitude, &x, &y);
|
|
cairo_matrix_scale (matrix, zoom, zoom);
|
|
cairo_matrix_translate (matrix, -x, -y);
|
|
}
|
|
|
|
/* GtkScrollable implementation */
|
|
|
|
static void
|
|
e_map_adjustment_changed (GtkAdjustment *adjustment,
|
|
EMap *map)
|
|
{
|
|
EMapPrivate *priv = map->priv;
|
|
|
|
if (gtk_widget_get_realized (GTK_WIDGET (map))) {
|
|
gint hadj_value;
|
|
gint vadj_value;
|
|
|
|
hadj_value = gtk_adjustment_get_value (priv->hadjustment);
|
|
vadj_value = gtk_adjustment_get_value (priv->vadjustment);
|
|
|
|
scroll_to (map, hadj_value, vadj_value);
|
|
}
|
|
}
|
|
|
|
static void
|
|
e_map_set_hadjustment_values (EMap *map)
|
|
{
|
|
GtkAllocation allocation;
|
|
EMapPrivate *priv = map->priv;
|
|
GtkAdjustment *adj = priv->hadjustment;
|
|
gdouble old_value;
|
|
gdouble new_value;
|
|
gdouble new_upper;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
|
|
old_value = gtk_adjustment_get_value (adj);
|
|
new_upper = MAX (allocation.width, gdk_pixbuf_get_width (priv->map_pixbuf));
|
|
|
|
g_object_set (adj,
|
|
"lower", 0.0,
|
|
"upper", new_upper,
|
|
"page-size", (gdouble)allocation.height,
|
|
"step-increment", allocation.height * 0.1,
|
|
"page-increment", allocation.height * 0.9,
|
|
NULL);
|
|
|
|
new_value = CLAMP (old_value, 0, new_upper - allocation.width);
|
|
if (new_value != old_value)
|
|
gtk_adjustment_set_value (adj, new_value);
|
|
}
|
|
|
|
static void
|
|
e_map_set_vadjustment_values (EMap *map)
|
|
{
|
|
GtkAllocation allocation;
|
|
EMapPrivate *priv = map->priv;
|
|
GtkAdjustment *adj = priv->vadjustment;
|
|
gdouble old_value;
|
|
gdouble new_value;
|
|
gdouble new_upper;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
|
|
old_value = gtk_adjustment_get_value (adj);
|
|
new_upper = MAX (allocation.height, gdk_pixbuf_get_height (priv->map_pixbuf));
|
|
|
|
g_object_set (adj,
|
|
"lower", 0.0,
|
|
"upper", new_upper,
|
|
"page-size", (gdouble)allocation.height,
|
|
"step-increment", allocation.height * 0.1,
|
|
"page-increment", allocation.height * 0.9,
|
|
NULL);
|
|
|
|
new_value = CLAMP (old_value, 0, new_upper - allocation.height);
|
|
if (new_value != old_value)
|
|
gtk_adjustment_set_value (adj, new_value);
|
|
}
|
|
|
|
static void
|
|
e_map_set_hadjustment (EMap *map,
|
|
GtkAdjustment *adjustment)
|
|
{
|
|
EMapPrivate *priv = map->priv;
|
|
|
|
if (adjustment && priv->hadjustment == adjustment)
|
|
return;
|
|
|
|
if (priv->hadjustment != NULL) {
|
|
g_signal_handlers_disconnect_matched (
|
|
priv->hadjustment, G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL, NULL, map);
|
|
g_object_unref (priv->hadjustment);
|
|
}
|
|
|
|
if (!adjustment)
|
|
adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
|
|
|
g_signal_connect (
|
|
adjustment, "value-changed",
|
|
G_CALLBACK (e_map_adjustment_changed), map);
|
|
priv->hadjustment = g_object_ref_sink (adjustment);
|
|
e_map_set_hadjustment_values (map);
|
|
|
|
g_object_notify (G_OBJECT (map), "hadjustment");
|
|
}
|
|
|
|
static void
|
|
e_map_set_vadjustment (EMap *map,
|
|
GtkAdjustment *adjustment)
|
|
{
|
|
EMapPrivate *priv = map->priv;
|
|
|
|
if (adjustment && priv->vadjustment == adjustment)
|
|
return;
|
|
|
|
if (priv->vadjustment != NULL) {
|
|
g_signal_handlers_disconnect_matched (
|
|
priv->vadjustment, G_SIGNAL_MATCH_DATA,
|
|
0, 0, NULL, NULL, map);
|
|
g_object_unref (priv->vadjustment);
|
|
}
|
|
|
|
if (!adjustment)
|
|
adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
|
|
|
|
g_signal_connect (
|
|
adjustment, "value-changed",
|
|
G_CALLBACK (e_map_adjustment_changed), map);
|
|
priv->vadjustment = g_object_ref_sink (adjustment);
|
|
e_map_set_vadjustment_values (map);
|
|
|
|
g_object_notify (G_OBJECT (map), "vadjustment");
|
|
}
|
|
|
|
/* ----------------- *
|
|
* Widget management *
|
|
* ----------------- */
|
|
|
|
static void
|
|
e_map_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
EMap *map;
|
|
|
|
map = E_MAP (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_HADJUSTMENT:
|
|
e_map_set_hadjustment (map, g_value_get_object (value));
|
|
break;
|
|
case PROP_VADJUSTMENT:
|
|
e_map_set_vadjustment (map, g_value_get_object (value));
|
|
break;
|
|
case PROP_HSCROLL_POLICY:
|
|
map->priv->hscroll_policy = g_value_get_enum (value);
|
|
gtk_widget_queue_resize (GTK_WIDGET (map));
|
|
break;
|
|
case PROP_VSCROLL_POLICY:
|
|
map->priv->vscroll_policy = g_value_get_enum (value);
|
|
gtk_widget_queue_resize (GTK_WIDGET (map));
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
e_map_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
EMap *map;
|
|
|
|
map = E_MAP (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_HADJUSTMENT:
|
|
g_value_set_object (value, map->priv->hadjustment);
|
|
break;
|
|
case PROP_VADJUSTMENT:
|
|
g_value_set_object (value, map->priv->vadjustment);
|
|
break;
|
|
case PROP_HSCROLL_POLICY:
|
|
g_value_set_enum (value, map->priv->hscroll_policy);
|
|
break;
|
|
case PROP_VSCROLL_POLICY:
|
|
g_value_set_enum (value, map->priv->vscroll_policy);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
e_map_finalize (GObject *object)
|
|
{
|
|
EMap *map;
|
|
|
|
map = E_MAP (object);
|
|
|
|
while (map->priv->tweens)
|
|
e_map_tween_destroy (map, map->priv->tweens->data);
|
|
e_map_stop_tweening (map);
|
|
|
|
if (map->priv->map_pixbuf) {
|
|
g_object_unref (map->priv->map_pixbuf);
|
|
map->priv->map_pixbuf = NULL;
|
|
}
|
|
|
|
/* gone in unrealize */
|
|
g_assert (map->priv->map_render_surface == NULL);
|
|
|
|
G_OBJECT_CLASS (e_map_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
e_map_realize (GtkWidget *widget)
|
|
{
|
|
GtkAllocation allocation;
|
|
GdkWindowAttr attr;
|
|
GdkWindow *window;
|
|
GtkStyle *style;
|
|
gint attr_mask;
|
|
|
|
g_return_if_fail (widget != NULL);
|
|
g_return_if_fail (E_IS_MAP (widget));
|
|
|
|
gtk_widget_set_realized (widget, TRUE);
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
attr.window_type = GDK_WINDOW_CHILD;
|
|
attr.x = allocation.x;
|
|
attr.y = allocation.y;
|
|
attr.width = allocation.width;
|
|
attr.height = allocation.height;
|
|
attr.wclass = GDK_INPUT_OUTPUT;
|
|
attr.visual = gtk_widget_get_visual (widget);
|
|
attr.event_mask = gtk_widget_get_events (widget) |
|
|
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_KEY_PRESS_MASK |
|
|
GDK_POINTER_MOTION_MASK;
|
|
|
|
attr_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
|
|
|
|
window = gdk_window_new (
|
|
gtk_widget_get_parent_window (widget), &attr, attr_mask);
|
|
gtk_widget_set_window (widget, window);
|
|
gdk_window_set_user_data (window, widget);
|
|
|
|
style = gtk_widget_get_style (widget);
|
|
style = gtk_style_attach (style, window);
|
|
gtk_widget_set_style (widget, style);
|
|
|
|
update_render_surface (E_MAP (widget), TRUE);
|
|
}
|
|
|
|
static void
|
|
e_map_unrealize (GtkWidget *widget)
|
|
{
|
|
EMap *map = E_MAP (widget);
|
|
|
|
cairo_surface_destroy (map->priv->map_render_surface);
|
|
map->priv->map_render_surface = NULL;
|
|
|
|
if (GTK_WIDGET_CLASS (e_map_parent_class)->unrealize)
|
|
(*GTK_WIDGET_CLASS (e_map_parent_class)->unrealize) (widget);
|
|
}
|
|
|
|
static void
|
|
e_map_get_preferred_width (GtkWidget *widget,
|
|
gint *minimum,
|
|
gint *natural)
|
|
{
|
|
EMap *map;
|
|
|
|
g_return_if_fail (widget != NULL);
|
|
g_return_if_fail (E_IS_MAP (widget));
|
|
|
|
map = E_MAP (widget);
|
|
|
|
/* TODO: Put real sizes here. */
|
|
|
|
*minimum = *natural = gdk_pixbuf_get_width (map->priv->map_pixbuf);
|
|
}
|
|
|
|
static void
|
|
e_map_get_preferred_height (GtkWidget *widget,
|
|
gint *minimum,
|
|
gint *natural)
|
|
{
|
|
EMap *view;
|
|
EMapPrivate *priv;
|
|
|
|
g_return_if_fail (widget != NULL);
|
|
g_return_if_fail (E_IS_MAP (widget));
|
|
|
|
view = E_MAP (widget);
|
|
priv = view->priv;
|
|
|
|
/* TODO: Put real sizes here. */
|
|
|
|
*minimum = *natural = gdk_pixbuf_get_height (priv->map_pixbuf);
|
|
}
|
|
|
|
static void
|
|
e_map_size_allocate (GtkWidget *widget,
|
|
GtkAllocation *allocation)
|
|
{
|
|
EMap *map;
|
|
|
|
g_return_if_fail (widget != NULL);
|
|
g_return_if_fail (E_IS_MAP (widget));
|
|
g_return_if_fail (allocation != NULL);
|
|
|
|
map = E_MAP (widget);
|
|
|
|
/* Resize the window */
|
|
|
|
gtk_widget_set_allocation (widget, allocation);
|
|
|
|
if (gtk_widget_get_realized (widget)) {
|
|
GdkWindow *window;
|
|
|
|
window = gtk_widget_get_window (widget);
|
|
|
|
gdk_window_move_resize (
|
|
window, allocation->x, allocation->y,
|
|
allocation->width, allocation->height);
|
|
|
|
gtk_widget_queue_draw (widget);
|
|
}
|
|
|
|
update_render_surface (map, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
e_map_draw (GtkWidget *widget,
|
|
cairo_t *cr)
|
|
{
|
|
EMap *map;
|
|
cairo_matrix_t matrix;
|
|
|
|
if (!gtk_widget_is_drawable (widget))
|
|
return FALSE;
|
|
|
|
map = E_MAP (widget);
|
|
|
|
cairo_save (cr);
|
|
|
|
e_map_tweens_compute_matrix (map, &matrix);
|
|
cairo_transform (cr, &matrix);
|
|
|
|
cairo_set_source_surface (cr, map->priv->map_render_surface, 0, 0);
|
|
cairo_paint (cr);
|
|
|
|
cairo_restore (cr);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
e_map_button_press (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
if (!gtk_widget_has_focus (widget)) gtk_widget_grab_focus (widget);
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
e_map_button_release (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
if (event->button != 1) return FALSE;
|
|
|
|
gdk_pointer_ungrab (event->time);
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
e_map_motion (GtkWidget *widget,
|
|
GdkEventMotion *event)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
static gint
|
|
e_map_key_press (GtkWidget *widget,
|
|
GdkEventKey *event)
|
|
{
|
|
EMap *map;
|
|
gboolean do_scroll;
|
|
gint xofs, yofs;
|
|
|
|
map = E_MAP (widget);
|
|
|
|
switch (event->keyval)
|
|
{
|
|
case GDK_KEY_Up:
|
|
do_scroll = TRUE;
|
|
xofs = 0;
|
|
yofs = -SCROLL_STEP_SIZE;
|
|
break;
|
|
|
|
case GDK_KEY_Down:
|
|
do_scroll = TRUE;
|
|
xofs = 0;
|
|
yofs = SCROLL_STEP_SIZE;
|
|
break;
|
|
|
|
case GDK_KEY_Left:
|
|
do_scroll = TRUE;
|
|
xofs = -SCROLL_STEP_SIZE;
|
|
yofs = 0;
|
|
break;
|
|
|
|
case GDK_KEY_Right:
|
|
do_scroll = TRUE;
|
|
xofs = SCROLL_STEP_SIZE;
|
|
yofs = 0;
|
|
break;
|
|
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
if (do_scroll) {
|
|
gint page_size;
|
|
gint upper;
|
|
gint x, y;
|
|
|
|
page_size = gtk_adjustment_get_page_size (map->priv->hadjustment);
|
|
upper = gtk_adjustment_get_upper (map->priv->hadjustment);
|
|
x = CLAMP (map->priv->xofs + xofs, 0, upper - page_size);
|
|
|
|
page_size = gtk_adjustment_get_page_size (map->priv->vadjustment);
|
|
upper = gtk_adjustment_get_upper (map->priv->vadjustment);
|
|
y = CLAMP (map->priv->yofs + yofs, 0, upper - page_size);
|
|
|
|
scroll_to (map, x, y);
|
|
|
|
gtk_adjustment_set_value (map->priv->hadjustment, x);
|
|
gtk_adjustment_set_value (map->priv->vadjustment, y);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
e_map_class_init (EMapClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
GtkWidgetClass *widget_class;
|
|
|
|
g_type_class_add_private (class, sizeof (EMapPrivate));
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->set_property = e_map_set_property;
|
|
object_class->get_property = e_map_get_property;
|
|
object_class->finalize = e_map_finalize;
|
|
|
|
/* Scrollable interface properties */
|
|
g_object_class_override_property (
|
|
object_class, PROP_HADJUSTMENT, "hadjustment");
|
|
g_object_class_override_property (
|
|
object_class, PROP_VADJUSTMENT, "vadjustment");
|
|
g_object_class_override_property (
|
|
object_class, PROP_HSCROLL_POLICY, "hscroll-policy");
|
|
g_object_class_override_property (
|
|
object_class, PROP_VSCROLL_POLICY, "vscroll-policy");
|
|
|
|
widget_class = GTK_WIDGET_CLASS (class);
|
|
widget_class->realize = e_map_realize;
|
|
widget_class->unrealize = e_map_unrealize;
|
|
widget_class->get_preferred_height = e_map_get_preferred_height;
|
|
widget_class->get_preferred_width = e_map_get_preferred_width;
|
|
widget_class->size_allocate = e_map_size_allocate;
|
|
widget_class->draw = e_map_draw;
|
|
widget_class->button_press_event = e_map_button_press;
|
|
widget_class->button_release_event = e_map_button_release;
|
|
widget_class->motion_notify_event = e_map_motion;
|
|
widget_class->key_press_event = e_map_key_press;
|
|
}
|
|
|
|
static void
|
|
e_map_init (EMap *map)
|
|
{
|
|
GtkWidget *widget;
|
|
gchar *map_file_name;
|
|
|
|
map_file_name = g_build_filename (
|
|
EVOLUTION_IMAGESDIR, "world_map-960.png", NULL);
|
|
|
|
widget = GTK_WIDGET (map);
|
|
|
|
map->priv = E_MAP_GET_PRIVATE (map);
|
|
|
|
load_map_background (map, map_file_name);
|
|
g_free (map_file_name);
|
|
map->priv->frozen = FALSE;
|
|
map->priv->smooth_zoom = TRUE;
|
|
map->priv->zoom_state = E_MAP_ZOOMED_OUT;
|
|
map->priv->points = g_ptr_array_new ();
|
|
|
|
gtk_widget_set_can_focus (widget, TRUE);
|
|
gtk_widget_set_has_window (widget, TRUE);
|
|
}
|
|
|
|
/* ---------------- *
|
|
* Widget interface *
|
|
* ---------------- */
|
|
|
|
/**
|
|
* e_map_new:
|
|
* @void:
|
|
*
|
|
* Creates a new empty map widget.
|
|
*
|
|
* Return value: A newly-created map widget.
|
|
**/
|
|
|
|
EMap *
|
|
e_map_new (void)
|
|
{
|
|
GtkWidget *widget;
|
|
AtkObject *a11y;
|
|
|
|
widget = g_object_new (E_TYPE_MAP, NULL);
|
|
a11y = gtk_widget_get_accessible (widget);
|
|
atk_object_set_name (a11y, _("World Map"));
|
|
atk_object_set_role (a11y, ATK_ROLE_IMAGE);
|
|
atk_object_set_description (
|
|
a11y, _("Mouse-based interactive map widget for selecting "
|
|
"timezone. Keyboard users should instead select the timezone "
|
|
"from the drop-down combination box below."));
|
|
return (E_MAP (widget));
|
|
}
|
|
|
|
/* --- Coordinate translation --- */
|
|
|
|
/* These functions translate coordinates between longitude/latitude and
|
|
* the image x/y offsets, using the equidistant cylindrical projection.
|
|
*
|
|
* Longitude E <-180, 180]
|
|
* Latitude E <-90, 90] */
|
|
|
|
void
|
|
e_map_window_to_world (EMap *map,
|
|
gdouble win_x,
|
|
gdouble win_y,
|
|
gdouble *world_longitude,
|
|
gdouble *world_latitude)
|
|
{
|
|
gint width, height;
|
|
|
|
g_return_if_fail (map);
|
|
|
|
g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
|
|
|
|
width = E_MAP_GET_WIDTH (map);
|
|
height = E_MAP_GET_HEIGHT (map);
|
|
|
|
*world_longitude = (win_x + map->priv->xofs - (gdouble) width / 2.0) /
|
|
((gdouble) width / 2.0) * 180.0;
|
|
*world_latitude = ((gdouble) height / 2.0 - win_y - map->priv->yofs) /
|
|
((gdouble) height / 2.0) * 90.0;
|
|
}
|
|
|
|
void
|
|
e_map_world_to_window (EMap *map,
|
|
gdouble world_longitude,
|
|
gdouble world_latitude,
|
|
gdouble *win_x,
|
|
gdouble *win_y)
|
|
{
|
|
g_return_if_fail (E_IS_MAP (map));
|
|
g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
|
|
g_return_if_fail (world_longitude >= -180.0 && world_longitude <= 180.0);
|
|
g_return_if_fail (world_latitude >= -90.0 && world_latitude <= 90.0);
|
|
|
|
e_map_world_to_render_surface (
|
|
map, world_longitude, world_latitude, win_x, win_y);
|
|
|
|
*win_x -= map->priv->xofs;
|
|
*win_y -= map->priv->yofs;
|
|
}
|
|
|
|
/* --- Zoom --- */
|
|
|
|
gdouble
|
|
e_map_get_magnification (EMap *map)
|
|
{
|
|
if (map->priv->zoom_state == E_MAP_ZOOMED_IN) return 2.0;
|
|
else return 1.0;
|
|
}
|
|
|
|
static void
|
|
e_map_set_zoom (EMap *map,
|
|
EMapZoomState zoom)
|
|
{
|
|
if (map->priv->zoom_state == zoom)
|
|
return;
|
|
|
|
map->priv->zoom_state = zoom;
|
|
update_render_surface (map, TRUE);
|
|
gtk_widget_queue_draw (GTK_WIDGET (map));
|
|
}
|
|
|
|
void
|
|
e_map_zoom_to_location (EMap *map,
|
|
gdouble longitude,
|
|
gdouble latitude)
|
|
{
|
|
gdouble prevlong, prevlat;
|
|
gdouble prevzoom;
|
|
|
|
g_return_if_fail (map);
|
|
g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
|
|
|
|
e_map_get_current_location (map, &prevlong, &prevlat);
|
|
prevzoom = e_map_get_magnification (map);
|
|
|
|
e_map_set_zoom (map, E_MAP_ZOOMED_IN);
|
|
center_at (map, longitude, latitude);
|
|
|
|
e_map_tween_new_from (
|
|
map, E_MAP_TWEEN_DURATION_MSECS,
|
|
prevlong, prevlat, prevzoom);
|
|
}
|
|
|
|
void
|
|
e_map_zoom_out (EMap *map)
|
|
{
|
|
gdouble longitude, latitude;
|
|
gdouble prevzoom;
|
|
|
|
g_return_if_fail (map);
|
|
g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (map)));
|
|
|
|
e_map_get_current_location (map, &longitude, &latitude);
|
|
prevzoom = e_map_get_magnification (map);
|
|
e_map_set_zoom (map, E_MAP_ZOOMED_OUT);
|
|
center_at (map, longitude, latitude);
|
|
|
|
e_map_tween_new_from (
|
|
map, E_MAP_TWEEN_DURATION_MSECS,
|
|
longitude, latitude, prevzoom);
|
|
}
|
|
|
|
void
|
|
e_map_set_smooth_zoom (EMap *map,
|
|
gboolean state)
|
|
{
|
|
((EMapPrivate *) map->priv)->smooth_zoom = state;
|
|
}
|
|
|
|
gboolean
|
|
e_map_get_smooth_zoom (EMap *map)
|
|
{
|
|
return (((EMapPrivate *) map->priv)->smooth_zoom);
|
|
}
|
|
|
|
void
|
|
e_map_freeze (EMap *map)
|
|
{
|
|
((EMapPrivate *) map->priv)->frozen = TRUE;
|
|
}
|
|
|
|
void
|
|
e_map_thaw (EMap *map)
|
|
{
|
|
((EMapPrivate *) map->priv)->frozen = FALSE;
|
|
update_and_paint (map);
|
|
}
|
|
|
|
/* --- Point manipulation --- */
|
|
|
|
EMapPoint *
|
|
e_map_add_point (EMap *map,
|
|
gchar *name,
|
|
gdouble longitude,
|
|
gdouble latitude,
|
|
guint32 color_rgba)
|
|
{
|
|
EMapPoint *point;
|
|
|
|
point = g_new0 (EMapPoint, 1);
|
|
|
|
point->name = name; /* Can be NULL */
|
|
point->longitude = longitude;
|
|
point->latitude = latitude;
|
|
point->rgba = color_rgba;
|
|
|
|
g_ptr_array_add (map->priv->points, (gpointer) point);
|
|
|
|
if (!map->priv->frozen)
|
|
{
|
|
update_render_point (map, point);
|
|
repaint_point (map, point);
|
|
}
|
|
|
|
return point;
|
|
}
|
|
|
|
void
|
|
e_map_remove_point (EMap *map,
|
|
EMapPoint *point)
|
|
{
|
|
g_ptr_array_remove (map->priv->points, point);
|
|
|
|
if (!((EMapPrivate *) map->priv)->frozen)
|
|
{
|
|
/* FIXME: Re-scaling the whole pixbuf is more than a little
|
|
* overkill when just one point is removed */
|
|
|
|
update_render_surface (map, TRUE);
|
|
repaint_point (map, point);
|
|
}
|
|
|
|
g_free (point);
|
|
}
|
|
|
|
void
|
|
e_map_point_get_location (EMapPoint *point,
|
|
gdouble *longitude,
|
|
gdouble *latitude)
|
|
{
|
|
*longitude = point->longitude;
|
|
*latitude = point->latitude;
|
|
}
|
|
|
|
gchar *
|
|
e_map_point_get_name (EMapPoint *point)
|
|
{
|
|
return point->name;
|
|
}
|
|
|
|
guint32
|
|
e_map_point_get_color_rgba (EMapPoint *point)
|
|
{
|
|
return point->rgba;
|
|
}
|
|
|
|
void
|
|
e_map_point_set_color_rgba (EMap *map,
|
|
EMapPoint *point,
|
|
guint32 color_rgba)
|
|
{
|
|
point->rgba = color_rgba;
|
|
|
|
if (!((EMapPrivate *) map->priv)->frozen)
|
|
{
|
|
/* TODO: Redraw area around point only */
|
|
|
|
update_render_point (map, point);
|
|
repaint_point (map, point);
|
|
}
|
|
}
|
|
|
|
void
|
|
e_map_point_set_data (EMapPoint *point,
|
|
gpointer data)
|
|
{
|
|
point->user_data = data;
|
|
}
|
|
|
|
gpointer
|
|
e_map_point_get_data (EMapPoint *point)
|
|
{
|
|
return point->user_data;
|
|
}
|
|
|
|
gboolean
|
|
e_map_point_is_in_view (EMap *map,
|
|
EMapPoint *point)
|
|
{
|
|
GtkAllocation allocation;
|
|
gdouble x, y;
|
|
|
|
if (!map->priv->map_render_surface) return FALSE;
|
|
|
|
e_map_world_to_window (map, point->longitude, point->latitude, &x, &y);
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
|
|
if (x >= 0 && x < allocation.width &&
|
|
y >= 0 && y < allocation.height)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
EMapPoint *
|
|
e_map_get_closest_point (EMap *map,
|
|
gdouble longitude,
|
|
gdouble latitude,
|
|
gboolean in_view)
|
|
{
|
|
EMapPoint *point_chosen = NULL, *point;
|
|
gdouble min_dist = 0.0, dist;
|
|
gdouble dx, dy;
|
|
gint i;
|
|
|
|
for (i = 0; i < map->priv->points->len; i++)
|
|
{
|
|
point = g_ptr_array_index (map->priv->points, i);
|
|
if (in_view && !e_map_point_is_in_view (map, point)) continue;
|
|
|
|
dx = point->longitude - longitude;
|
|
dy = point->latitude - latitude;
|
|
dist = dx * dx + dy * dy;
|
|
|
|
if (!point_chosen || dist < min_dist)
|
|
{
|
|
min_dist = dist;
|
|
point_chosen = point;
|
|
}
|
|
}
|
|
|
|
return point_chosen;
|
|
}
|
|
|
|
/* ------------------ *
|
|
* Internal functions *
|
|
* ------------------ */
|
|
|
|
static void
|
|
update_and_paint (EMap *map)
|
|
{
|
|
update_render_surface (map, TRUE);
|
|
gtk_widget_queue_draw (GTK_WIDGET (map));
|
|
}
|
|
|
|
static gint
|
|
load_map_background (EMap *map,
|
|
gchar *name)
|
|
{
|
|
GdkPixbuf *pb0;
|
|
|
|
pb0 = gdk_pixbuf_new_from_file (name, NULL);
|
|
if (!pb0)
|
|
return FALSE;
|
|
|
|
if (map->priv->map_pixbuf) g_object_unref (map->priv->map_pixbuf);
|
|
map->priv->map_pixbuf = pb0;
|
|
update_render_surface (map, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
update_render_surface (EMap *map,
|
|
gboolean render_overlays)
|
|
{
|
|
EMapPoint *point;
|
|
GtkAllocation allocation;
|
|
gint width, height, orig_width, orig_height;
|
|
gdouble zoom;
|
|
gint i;
|
|
|
|
if (!gtk_widget_get_realized (GTK_WIDGET (map)))
|
|
return;
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
|
|
/* Set up value shortcuts */
|
|
|
|
width = allocation.width;
|
|
height = allocation.height;
|
|
orig_width = gdk_pixbuf_get_width (map->priv->map_pixbuf);
|
|
orig_height = gdk_pixbuf_get_height (map->priv->map_pixbuf);
|
|
|
|
/* Compute scaled width and height based on the extreme dimension */
|
|
|
|
if ((gdouble) width / orig_width > (gdouble) height / orig_height)
|
|
zoom = (gdouble) width / (gdouble) orig_width;
|
|
else
|
|
zoom = (gdouble) height / (gdouble) orig_height;
|
|
|
|
if (map->priv->zoom_state == E_MAP_ZOOMED_IN)
|
|
zoom *= 2.0;
|
|
height = (orig_height * zoom) + 0.5;
|
|
width = (orig_width * zoom) + 0.5;
|
|
|
|
/* Reallocate the pixbuf */
|
|
|
|
if (map->priv->map_render_surface)
|
|
cairo_surface_destroy (map->priv->map_render_surface);
|
|
map->priv->map_render_surface = gdk_window_create_similar_surface (
|
|
gtk_widget_get_window (GTK_WIDGET (map)),
|
|
CAIRO_CONTENT_COLOR, width, height);
|
|
|
|
/* Scale the original map into the rendering pixbuf */
|
|
|
|
if (width > 1 && height > 1) {
|
|
cairo_t *cr = cairo_create (map->priv->map_render_surface);
|
|
cairo_scale (
|
|
cr,
|
|
(gdouble) width / orig_width,
|
|
(gdouble) height / orig_height);
|
|
gdk_cairo_set_source_pixbuf (cr, map->priv->map_pixbuf, 0, 0);
|
|
cairo_paint (cr);
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
/* Compute image offsets with respect to window */
|
|
|
|
set_scroll_area (map, width, height);
|
|
|
|
if (render_overlays) {
|
|
/* Add points */
|
|
|
|
for (i = 0; i < map->priv->points->len; i++) {
|
|
point = g_ptr_array_index (map->priv->points, i);
|
|
update_render_point (map, point);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Redraw point in client surface */
|
|
|
|
static void
|
|
update_render_point (EMap *map,
|
|
EMapPoint *point)
|
|
{
|
|
cairo_t *cr;
|
|
gdouble px, py;
|
|
static guchar mask1[] = { 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
|
0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00,
|
|
0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
static guchar mask2[] = { 0x00, 0xff, 0x00, 0x00,
|
|
0xff, 0xff, 0xff, 0x00,
|
|
0x00, 0xff, 0x00, 0x00 };
|
|
cairo_surface_t *mask;
|
|
|
|
if (map->priv->map_render_surface == NULL)
|
|
return;
|
|
|
|
cr = cairo_create (map->priv->map_render_surface);
|
|
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
|
|
|
|
e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
|
|
px = floor (px + map->priv->xofs);
|
|
py = floor (py + map->priv->yofs);
|
|
|
|
cairo_set_source_rgb (cr, 0, 0, 0);
|
|
mask = cairo_image_surface_create_for_data (mask1, CAIRO_FORMAT_A8, 5, 5, 8);
|
|
cairo_mask_surface (cr, mask, px - 2, py - 2);
|
|
cairo_surface_destroy (mask);
|
|
|
|
cairo_set_source_rgba (cr,
|
|
((point->rgba >> 24) & 0xff) / 255.0,
|
|
((point->rgba >> 16) & 0xff) / 255.0,
|
|
((point->rgba >> 8) & 0xff) / 255.0,
|
|
( point->rgba & 0xff) / 255.0);
|
|
mask = cairo_image_surface_create_for_data (mask2, CAIRO_FORMAT_A8, 3, 3, 4);
|
|
cairo_mask_surface (cr, mask, px - 1, py - 1);
|
|
cairo_surface_destroy (mask);
|
|
|
|
cairo_destroy (cr);
|
|
}
|
|
|
|
/* Repaint point on X server */
|
|
|
|
static void
|
|
repaint_point (EMap *map,
|
|
EMapPoint *point)
|
|
{
|
|
gdouble px, py;
|
|
|
|
if (!gtk_widget_is_drawable (GTK_WIDGET (map)))
|
|
return;
|
|
|
|
e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
|
|
|
|
gtk_widget_queue_draw_area (GTK_WIDGET (map),
|
|
(gint) px - 2, (gint) py - 2,
|
|
5, 5);
|
|
}
|
|
|
|
static void
|
|
center_at (EMap *map,
|
|
gdouble longitude,
|
|
gdouble latitude)
|
|
{
|
|
GtkAllocation allocation;
|
|
gint pb_width, pb_height;
|
|
gdouble x, y;
|
|
|
|
e_map_world_to_render_surface (map, longitude, latitude, &x, &y);
|
|
|
|
pb_width = E_MAP_GET_WIDTH (map);
|
|
pb_height = E_MAP_GET_HEIGHT (map);
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (map), &allocation);
|
|
|
|
x = CLAMP (x - (allocation.width / 2), 0, pb_width - allocation.width);
|
|
y = CLAMP (y - (allocation.height / 2), 0, pb_height - allocation.height);
|
|
|
|
gtk_adjustment_set_value (map->priv->hadjustment, x);
|
|
gtk_adjustment_set_value (map->priv->vadjustment, y);
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (map));
|
|
}
|
|
|
|
/* Scrolls the view to the specified offsets. Does not perform range checking! */
|
|
|
|
static void
|
|
scroll_to (EMap *map,
|
|
gint x,
|
|
gint y)
|
|
{
|
|
gint xofs, yofs;
|
|
|
|
/* Compute offsets and check bounds */
|
|
|
|
xofs = x - map->priv->xofs;
|
|
yofs = y - map->priv->yofs;
|
|
|
|
if (xofs == 0 && yofs == 0)
|
|
return;
|
|
|
|
map->priv->xofs = x;
|
|
map->priv->yofs = y;
|
|
|
|
gtk_widget_queue_draw (GTK_WIDGET (map));
|
|
}
|
|
|
|
static void
|
|
set_scroll_area (EMap *view,
|
|
gint width,
|
|
gint height)
|
|
{
|
|
EMapPrivate *priv;
|
|
GtkAllocation allocation;
|
|
|
|
priv = view->priv;
|
|
|
|
if (!gtk_widget_get_realized (GTK_WIDGET (view)))
|
|
return;
|
|
|
|
if (!priv->hadjustment || !priv->vadjustment)
|
|
return;
|
|
|
|
g_object_freeze_notify (G_OBJECT (priv->hadjustment));
|
|
g_object_freeze_notify (G_OBJECT (priv->vadjustment));
|
|
|
|
gtk_widget_get_allocation (GTK_WIDGET (view), &allocation);
|
|
|
|
priv->xofs = CLAMP (priv->xofs, 0, width - allocation.width);
|
|
priv->yofs = CLAMP (priv->yofs, 0, height - allocation.height);
|
|
|
|
gtk_adjustment_configure (priv->hadjustment,
|
|
priv->xofs,
|
|
0, width,
|
|
SCROLL_STEP_SIZE,
|
|
allocation.width / 2,
|
|
allocation.width);
|
|
gtk_adjustment_configure (priv->vadjustment,
|
|
priv->yofs,
|
|
0, height,
|
|
SCROLL_STEP_SIZE,
|
|
allocation.height / 2,
|
|
allocation.height);
|
|
|
|
g_object_thaw_notify (G_OBJECT (priv->hadjustment));
|
|
g_object_thaw_notify (G_OBJECT (priv->vadjustment));
|
|
}
|