aboutsummaryrefslogtreecommitdiffstats
path: root/widgets
diff options
context:
space:
mode:
Diffstat (limited to 'widgets')
-rw-r--r--widgets/misc/ChangeLog8
-rw-r--r--widgets/misc/Makefile.am3
-rw-r--r--widgets/misc/e-map.c1783
-rw-r--r--widgets/misc/e-map.h139
4 files changed, 1933 insertions, 0 deletions
diff --git a/widgets/misc/ChangeLog b/widgets/misc/ChangeLog
index dcd0c80d4b..3615bfba97 100644
--- a/widgets/misc/ChangeLog
+++ b/widgets/misc/ChangeLog
@@ -1,3 +1,11 @@
+2001-06-14 Damon Chaplin <damon@ximian.com>
+
+ * e-map.[hc]: copied the EMap widget verbatim from Ximian Setup Tools.
+ Hmm. Maybe it should go in gal, but its not a very general widget.
+
+ * Makefile.am (INCLUDES): defined MAP_DIR for e-map.c.
+ (libemiscwidgets_a_SOURCES): added e-map.[hc].
+
2001-05-31 Federico Mena Quintero <federico@ximian.com>
* e-dropdown-button.c (impl_button_press_event): Removed unused
diff --git a/widgets/misc/Makefile.am b/widgets/misc/Makefile.am
index 401aac64a0..f60c31f70e 100644
--- a/widgets/misc/Makefile.am
+++ b/widgets/misc/Makefile.am
@@ -5,6 +5,7 @@ INCLUDES = \
-I$(top_srcdir) \
-I$(top_srcdir)/widgets/shortcut-bar \
$(EXTRA_GNOME_CFLAGS) \
+ -DMAP_DIR=\""$(datadir)/images/evolution"\" \
-DG_LOG_DOMAIN=__FILE__
noinst_LIBRARIES = \
@@ -25,6 +26,8 @@ libemiscwidgets_a_SOURCES = \
e-dropdown-button.h \
e-filter-bar.c \
e-filter-bar.h \
+ e-map.c \
+ e-map.h \
e-messagebox.c \
e-messagebox.h \
e-search-bar.c \
diff --git a/widgets/misc/e-map.c b/widgets/misc/e-map.c
new file mode 100644
index 0000000000..a514f1ee9d
--- /dev/null
+++ b/widgets/misc/e-map.c
@@ -0,0 +1,1783 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Map widget.
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Authors: Hans Petter Jansson <hpj@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <math.h>
+#include <stdlib.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtksignal.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <libart_lgpl/art_filterlevel.h>
+
+#include "e-map.h"
+
+/* Scroll step increment */
+
+#define SCROLL_STEP_SIZE 32
+
+
+/* */
+
+#define E_MAP_GET_WIDTH(map) gdk_pixbuf_get_width(((EMapPrivate *) E_MAP(map)->priv)->map_render_pixbuf)
+#define E_MAP_GET_HEIGHT(map) gdk_pixbuf_get_height(((EMapPrivate *) E_MAP(map)->priv)->map_render_pixbuf)
+
+
+/* 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;
+
+
+/* Private part of the EMap structure */
+
+typedef struct
+{
+ /* Pointer to map image */
+ GdkPixbuf *map_pixbuf, *map_render_pixbuf;
+
+ /* Settings */
+ gboolean frozen, smooth_zoom;
+
+ /* Adjustments for scrolling */
+ GtkAdjustment *hadj;
+ GtkAdjustment *vadj;
+
+ /* Current scrolling offsets */
+ int xofs, yofs;
+
+ /* Realtime zoom data */
+ EMapZoomState zoom_state;
+ double zoom_target_long, zoom_target_lat;
+
+ /* Dots */
+ GPtrArray *points;
+}
+EMapPrivate;
+
+
+/* Signal IDs */
+
+enum
+{
+ LAST_SIGNAL
+};
+
+static guint e_map_signals[LAST_SIGNAL];
+
+
+/* Internal prototypes */
+
+static void e_map_class_init (EMapClass *class);
+static void e_map_init (EMap *view);
+static void e_map_destroy (GtkObject *object);
+static void e_map_finalize (GtkObject *object);
+static void e_map_unmap (GtkWidget *widget);
+static void e_map_realize (GtkWidget *widget);
+static void e_map_unrealize (GtkWidget *widget);
+static void e_map_size_request (GtkWidget *widget, GtkRequisition *requisition);
+static void e_map_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
+static void e_map_draw (GtkWidget *widget, GdkRectangle *area);
+static gint e_map_button_press (GtkWidget *widget, GdkEventButton *event);
+static gint e_map_button_release (GtkWidget *widget, GdkEventButton *event);
+static gint e_map_motion (GtkWidget *widget, GdkEventMotion *event);
+static gint e_map_expose (GtkWidget *widget, GdkEventExpose *event);
+static gint e_map_key_press (GtkWidget *widget, GdkEventKey *event);
+static void e_map_set_scroll_adjustments (GtkWidget *widget, GtkAdjustment *hadj, GtkAdjustment *vadj);
+
+static void update_render_pixbuf (EMap *map, ArtFilterLevel interp, gboolean render_overlays);
+static void set_scroll_area (EMap *view);
+static void request_paint_area (EMap *view, GdkRectangle *area);
+static void center_at (EMap *map, int x, int y, gboolean scroll);
+static void smooth_center_at (EMap *map, int x, int y);
+static void scroll_to (EMap *view, int x, int y);
+static void zoom_do (EMap *map);
+static gint load_map_background (EMap *view, gchar *name);
+static void adjustment_changed_cb (GtkAdjustment *adj, gpointer data);
+static void update_and_paint (EMap *map);
+static void update_render_point (EMap *map, EMapPoint *point);
+static void repaint_point (EMap *map, EMapPoint *point);
+
+static GtkWidgetClass *parent_class;
+
+
+/* ----------------- *
+ * Widget management *
+ * ----------------- */
+
+
+/**
+ * e_map_get_type:
+ * @void:
+ *
+ * Registers the #EMap class if necessary, and returns the type ID
+ * associated to it.
+ *
+ * Return value: The type ID of the #EMap class.
+ **/
+
+GtkType
+e_map_get_type (void)
+{
+ static GtkType e_map_type = 0;
+
+ if (!e_map_type)
+ {
+ static const GtkTypeInfo e_map_info =
+ {
+ "EMap",
+ sizeof (EMap),
+ sizeof (EMapClass),
+ (GtkClassInitFunc) e_map_class_init,
+ (GtkObjectInitFunc) e_map_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ e_map_type = gtk_type_unique (GTK_TYPE_WIDGET, &e_map_info);
+ }
+
+ return e_map_type;
+}
+
+/* Class initialization function for the map view */
+
+static void
+e_map_class_init (EMapClass *class)
+{
+ GtkObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = (GtkObjectClass *) class;
+ widget_class = (GtkWidgetClass *) class;
+
+ parent_class = gtk_type_class (GTK_TYPE_WIDGET);
+
+ object_class->destroy = e_map_destroy;
+ object_class->finalize = e_map_finalize;
+
+ class->set_scroll_adjustments = e_map_set_scroll_adjustments;
+ widget_class->set_scroll_adjustments_signal = gtk_signal_new ("set_scroll_adjustments", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET (EMapClass, set_scroll_adjustments), gtk_marshal_NONE__POINTER_POINTER, GTK_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
+
+ gtk_object_class_add_signals (object_class, e_map_signals, LAST_SIGNAL);
+
+ widget_class->unmap = e_map_unmap;
+ widget_class->realize = e_map_realize;
+ widget_class->unrealize = e_map_unrealize;
+ widget_class->size_request = e_map_size_request;
+ 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->expose_event = e_map_expose;
+ widget_class->key_press_event = e_map_key_press;
+}
+
+
+/* Object initialization function for the map view */
+
+static void
+e_map_init (EMap *view)
+{
+ EMapPrivate *priv;
+
+ priv = g_new0 (EMapPrivate, 1);
+ view->priv = priv;
+
+ load_map_background (view, MAP_DIR"/world_map-960.png");
+ priv->frozen = FALSE;
+ priv->smooth_zoom = TRUE;
+ priv->zoom_state = E_MAP_ZOOMED_OUT;
+ priv->points = g_ptr_array_new ();
+
+ GTK_WIDGET_SET_FLAGS (view, GTK_CAN_FOCUS);
+ GTK_WIDGET_UNSET_FLAGS (view, GTK_NO_WINDOW);
+}
+
+
+/* Destroy handler for the map view */
+
+static void
+e_map_destroy (GtkObject *object)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (IS_E_MAP (object));
+
+ view = E_MAP (object);
+ priv = view->priv;
+
+ gtk_signal_disconnect_by_data (GTK_OBJECT (priv->hadj), view);
+ gtk_signal_disconnect_by_data (GTK_OBJECT (priv->vadj), view);
+
+ if (GTK_OBJECT_CLASS (parent_class)->destroy)
+ (*GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+
+/* Finalize handler for the map view */
+
+static void
+e_map_finalize (GtkObject *object)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (IS_E_MAP (object));
+
+ view = E_MAP (object);
+ priv = view->priv;
+
+ gtk_object_unref (GTK_OBJECT (priv->hadj));
+ priv->hadj = NULL;
+
+ gtk_object_unref (GTK_OBJECT (priv->vadj));
+ priv->vadj = NULL;
+
+ /* TODO: Unref pixbufs here */
+
+ g_free (priv);
+ view->priv = NULL;
+
+ if (GTK_OBJECT_CLASS (parent_class)->finalize)
+ (*GTK_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+
+/* Unmap handler for the map view */
+
+static void
+e_map_unmap (GtkWidget *widget)
+{
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+
+ if (GTK_WIDGET_CLASS (parent_class)->unmap)
+ (*GTK_WIDGET_CLASS (parent_class)->unmap) (widget);
+}
+
+
+/* Realize handler for the map view */
+
+static void
+e_map_realize (GtkWidget *widget)
+{
+ GdkWindowAttr attr;
+ int attr_mask;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+
+ GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
+
+ attr.window_type = GDK_WINDOW_CHILD;
+ attr.x = widget->allocation.x;
+ attr.y = widget->allocation.y;
+ attr.width = widget->allocation.width;
+ attr.height = widget->allocation.height;
+ attr.wclass = GDK_INPUT_OUTPUT;
+ attr.visual = gdk_rgb_get_visual ();
+ attr.colormap = gdk_rgb_get_cmap ();
+ 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 | GDK_WA_COLORMAP;
+
+ widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attr, attr_mask);
+ gdk_window_set_user_data (widget->window, widget);
+
+ widget->style = gtk_style_attach (widget->style, widget->window);
+
+ gdk_window_set_back_pixmap (widget->window, NULL, FALSE);
+ update_render_pixbuf (E_MAP (widget), GDK_INTERP_BILINEAR, TRUE);
+}
+
+
+/* Unrealize handler for the map view */
+
+static void
+e_map_unrealize (GtkWidget *widget)
+{
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+
+ if (GTK_WIDGET_CLASS (parent_class)->unrealize)
+ (*GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
+}
+
+
+/* Size_request handler for the map view */
+
+static void
+e_map_size_request (GtkWidget *widget, GtkRequisition *requisition)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+ g_return_if_fail (requisition != NULL);
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ /* TODO: Put real sizes here. */
+
+ requisition->width = gdk_pixbuf_get_width (priv->map_pixbuf);
+ requisition->height = gdk_pixbuf_get_height (priv->map_pixbuf);
+}
+
+
+/* Size_allocate handler for the map view */
+
+static void
+e_map_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
+{
+ EMap *view;
+ EMapPrivate *priv;
+ int xofs, yofs;
+ GdkRectangle area;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+ g_return_if_fail (allocation != NULL);
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ xofs = priv->xofs;
+ yofs = priv->yofs;
+
+ /* Resize the window */
+
+ widget->allocation = *allocation;
+
+ if (GTK_WIDGET_REALIZED (widget))
+ {
+ gdk_window_move_resize (widget->window, allocation->x, allocation->y, allocation->width, allocation->height);
+
+ area.x = 0;
+ area.y = 0;
+ area.width = allocation->width;
+ area.height = allocation->height;
+ request_paint_area (E_MAP (widget), &area);
+ }
+
+ update_render_pixbuf (view, GDK_INTERP_BILINEAR, TRUE);
+}
+
+
+/* Draw handler for the map view */
+
+static void
+e_map_draw (GtkWidget *widget, GdkRectangle *area)
+{
+ EMap *view;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+ g_return_if_fail (area != NULL);
+
+ view = E_MAP (widget);
+
+ request_paint_area (view, area);
+}
+
+
+/* Button press handler for the map view */
+
+static gint
+e_map_button_press (GtkWidget *widget, GdkEventButton *event)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ if (!GTK_WIDGET_HAS_FOCUS (widget)) gtk_widget_grab_focus (widget);
+ return TRUE;
+}
+
+
+/* Button release handler for the map view */
+
+static gint
+e_map_button_release (GtkWidget *widget, GdkEventButton *event)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ if (event->button != 1) return FALSE;
+
+ gdk_pointer_ungrab (event->time);
+ return TRUE;
+}
+
+
+/* Motion handler for the map view */
+
+static gint
+e_map_motion (GtkWidget *widget, GdkEventMotion *event)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ return FALSE;
+
+/*
+ * if (event->is_hint)
+ * gdk_window_get_pointer(widget->window, &x, &y, &mods);
+ * else
+ * {
+ * x = event->x;
+ * y = event->y;
+ * }
+ *
+ * return TRUE;
+ */
+}
+
+
+/* Expose handler for the map view */
+
+static gint
+e_map_expose (GtkWidget *widget, GdkEventExpose *event)
+{
+ EMap *view;
+
+ g_return_val_if_fail (widget != NULL, FALSE);
+ g_return_val_if_fail (IS_E_MAP (widget), FALSE);
+ g_return_val_if_fail (event != NULL, FALSE);
+
+ view = E_MAP (widget);
+
+ request_paint_area (view, &event->area);
+ return TRUE;
+}
+
+
+/* Set_scroll_adjustments handler for the map view */
+
+static void
+e_map_set_scroll_adjustments (GtkWidget *widget, GtkAdjustment *hadj, GtkAdjustment *vadj)
+{
+ EMap *view;
+ EMapPrivate *priv;
+ gboolean need_adjust;
+
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (IS_E_MAP (widget));
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ if (hadj) g_return_if_fail (GTK_IS_ADJUSTMENT (hadj));
+ else hadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+
+ if (vadj) g_return_if_fail (GTK_IS_ADJUSTMENT (vadj));
+ else vadj = GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+
+ if (priv->hadj && priv->hadj != hadj)
+ {
+ gtk_signal_disconnect_by_data (GTK_OBJECT (priv->hadj), view);
+ gtk_object_unref (GTK_OBJECT (priv->hadj));
+ }
+
+ if (priv->vadj && priv->vadj != vadj)
+ {
+ gtk_signal_disconnect_by_data (GTK_OBJECT (priv->vadj), view);
+ gtk_object_unref (GTK_OBJECT (priv->vadj));
+ }
+
+ need_adjust = FALSE;
+
+ if (priv->hadj != hadj)
+ {
+ priv->hadj = hadj;
+ gtk_object_ref (GTK_OBJECT (priv->hadj));
+ gtk_object_sink (GTK_OBJECT (priv->hadj));
+
+ gtk_signal_connect (GTK_OBJECT (priv->hadj), "value_changed", GTK_SIGNAL_FUNC (adjustment_changed_cb), view);
+
+ need_adjust = TRUE;
+ }
+
+ if (priv->vadj != vadj)
+ {
+ priv->vadj = vadj;
+ gtk_object_ref (GTK_OBJECT (priv->vadj));
+ gtk_object_sink (GTK_OBJECT (priv->vadj));
+
+ gtk_signal_connect (GTK_OBJECT (priv->vadj), "value_changed", GTK_SIGNAL_FUNC (adjustment_changed_cb), view);
+
+ need_adjust = TRUE;
+ }
+
+ if (need_adjust) adjustment_changed_cb (NULL, view);
+}
+
+
+/* Key press handler for the map view */
+
+static gint
+e_map_key_press (GtkWidget *widget, GdkEventKey *event)
+{
+ EMap *view;
+ EMapPrivate *priv;
+ gboolean do_scroll;
+ int xofs, yofs;
+
+ view = E_MAP (widget);
+ priv = view->priv;
+
+ do_scroll = FALSE;
+ xofs = yofs = 0;
+
+ switch (event->keyval)
+ {
+ case GDK_Up:
+ do_scroll = TRUE;
+ xofs = 0;
+ yofs = -SCROLL_STEP_SIZE;
+ break;
+
+ case GDK_Down:
+ do_scroll = TRUE;
+ xofs = 0;
+ yofs = SCROLL_STEP_SIZE;
+ break;
+
+ case GDK_Left:
+ do_scroll = TRUE;
+ xofs = -SCROLL_STEP_SIZE;
+ yofs = 0;
+ break;
+
+ case GDK_Right:
+ do_scroll = TRUE;
+ xofs = SCROLL_STEP_SIZE;
+ yofs = 0;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (do_scroll)
+ {
+ int x, y;
+
+ x = CLAMP (priv->xofs + xofs, 0, priv->hadj->upper - priv->hadj->page_size);
+ y = CLAMP (priv->yofs + yofs, 0, priv->vadj->upper - priv->vadj->page_size);
+
+ scroll_to (view, x, y);
+
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->hadj), view);
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->vadj), view);
+
+ priv->hadj->value = x;
+ priv->vadj->value = y;
+
+ gtk_signal_emit_by_name (GTK_OBJECT (priv->hadj), "value_changed");
+ gtk_signal_emit_by_name (GTK_OBJECT (priv->vadj), "value_changed");
+
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->hadj), view);
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->vadj), view);
+ }
+
+ return TRUE;
+}
+
+
+/* ---------------- *
+ * Widget interface *
+ * ---------------- */
+
+
+/**
+ * e_map_new:
+ * @void:
+ *
+ * Creates a new empty map widget.
+ *
+ * Return value: A newly-created map widget.
+ **/
+
+EMap *
+e_map_new ()
+{
+ GtkWidget *widget;
+
+ widget = GTK_WIDGET (gtk_type_new (TYPE_E_MAP));
+ 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, double win_x, double win_y, double *world_longitude, double *world_latitude)
+{
+ EMapPrivate *priv;
+ int width, height;
+
+ g_return_if_fail (map);
+
+ priv = map->priv;
+ g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (map)));
+
+ width = gdk_pixbuf_get_width (priv->map_render_pixbuf);
+ height = gdk_pixbuf_get_height (priv->map_render_pixbuf);
+
+ *world_longitude = (win_x + priv->xofs - (double) width / 2.0) /
+ ((double) width / 2.0) * 180.0;
+ *world_latitude = ((double) height / 2.0 - win_y - priv->yofs) /
+ ((double) height / 2.0) * 90.0;
+}
+
+
+void
+e_map_world_to_window (EMap *map, double world_longitude, double world_latitude, double *win_x, double *win_y)
+{
+ EMapPrivate *priv;
+ int width, height;
+
+ g_return_if_fail (map);
+
+ priv = map->priv;
+ g_return_if_fail (priv->map_render_pixbuf);
+ g_return_if_fail (world_longitude >= -180.0 && world_longitude <= 180.0);
+ g_return_if_fail (world_latitude >= -90.0 && world_latitude <= 90.0);
+
+ width = gdk_pixbuf_get_width (priv->map_render_pixbuf);
+ height = gdk_pixbuf_get_height (priv->map_render_pixbuf);
+
+ *win_x = (width / 2.0 + (width / 2.0) * world_longitude / 180.0) - priv->xofs;
+ *win_y = (height / 2.0 - (height / 2.0) * world_latitude / 90.0) - priv->yofs;
+
+#ifdef DEBUG
+ printf ("Map size: (%d, %d)\nCoords: (%.1f, %.1f) -> (%.1f, %.1f)\n---\n", width, height, world_longitude, world_latitude, *win_x, *win_y);
+#endif
+}
+
+
+/* --- Zoom --- */
+
+
+double
+e_map_get_magnification (EMap *map)
+{
+ EMapPrivate *priv;
+
+ priv = map->priv;
+ if (priv->zoom_state == E_MAP_ZOOMED_IN) return 2.0;
+ else return 1.0;
+}
+
+
+void
+e_map_zoom_to_location (EMap *map, double longitude, double latitude)
+{
+ EMapPrivate *priv;
+ int width, height;
+
+ g_return_if_fail (map);
+ g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (map)));
+
+ priv = map->priv;
+
+ if (priv->zoom_state == E_MAP_ZOOMED_IN) e_map_zoom_out (map);
+ else if (priv->zoom_state != E_MAP_ZOOMED_OUT) return;
+
+ width = gdk_pixbuf_get_width (priv->map_render_pixbuf);
+ height = gdk_pixbuf_get_height (priv->map_render_pixbuf);
+
+ priv->zoom_state = E_MAP_ZOOMING_IN;
+ priv->zoom_target_long = longitude;
+ priv->zoom_target_lat = latitude;
+
+ zoom_do (map);
+}
+
+
+void
+e_map_zoom_out (EMap *map)
+{
+ EMapPrivate *priv;
+ int width, height;
+
+ g_return_if_fail (map);
+ g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (map)));
+
+ priv = map->priv;
+
+ if (priv->zoom_state != E_MAP_ZOOMED_IN) return;
+
+ width = gdk_pixbuf_get_width (priv->map_render_pixbuf);
+ height = gdk_pixbuf_get_height (priv->map_render_pixbuf);
+
+ priv->zoom_state = E_MAP_ZOOMING_OUT;
+ zoom_do (map);
+ priv->zoom_state = E_MAP_ZOOMED_OUT;
+}
+
+
+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, double longitude, double latitude, guint32 color_rgba)
+{
+ EMapPrivate *priv;
+ EMapPoint *point;
+
+ priv = map->priv;
+ 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 (priv->points, (gpointer) point);
+
+ if (!priv->frozen)
+ {
+ update_render_point (map, point);
+ repaint_point (map, point);
+ }
+
+ return point;
+}
+
+
+void
+e_map_remove_point (EMap *map, EMapPoint *point)
+{
+ EMapPrivate *priv;
+
+ priv = map->priv;
+ g_ptr_array_remove (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_pixbuf (map, GDK_INTERP_BILINEAR, TRUE);
+ repaint_point (map, point);
+ }
+
+ g_free (point);
+}
+
+
+void
+e_map_point_get_location (EMapPoint *point, double *longitude, double *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)
+{
+ EMapPrivate *priv;
+ double x, y;
+
+ priv = map->priv;
+ if (!priv->map_render_pixbuf) return FALSE;
+
+ e_map_world_to_window (map, point->longitude, point->latitude, &x, &y);
+
+ if (x >= 0 && x < GTK_WIDGET (map)->allocation.width &&
+ y >= 0 && y < GTK_WIDGET (map)->allocation.height)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+EMapPoint *
+e_map_get_closest_point (EMap *map, double longitude, double latitude, gboolean in_view)
+{
+ EMapPrivate *priv;
+ EMapPoint *point_chosen = NULL, *point;
+ double min_dist = 0.0, dist;
+ double dx, dy;
+ int i;
+
+ priv = map->priv;
+
+ for (i = 0; i < priv->points->len; i++)
+ {
+ point = g_ptr_array_index (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
+repaint_visible (EMap *map)
+{
+ GdkRectangle area;
+
+ area.x = 0;
+ area.y = 0;
+ area.width = GTK_WIDGET (map)->allocation.width;
+ area.height = GTK_WIDGET (map)->allocation.height;
+
+ request_paint_area (map, &area);
+}
+
+
+static void
+update_and_paint (EMap *map)
+{
+ update_render_pixbuf (map, GDK_INTERP_BILINEAR, TRUE);
+ repaint_visible (map);
+}
+
+
+static gint
+load_map_background (EMap *view, gchar *name)
+{
+ EMapPrivate *priv;
+ GdkPixbuf *pb0;
+
+ priv = view->priv;
+
+ pb0 = gdk_pixbuf_new_from_file (name);
+/* pb0 = tool_load_image (name);*/
+ if (!pb0) return (FALSE);
+
+ if (priv->map_pixbuf) gdk_pixbuf_unref (priv->map_pixbuf);
+ priv->map_pixbuf = pb0;
+ update_render_pixbuf (view, GDK_INTERP_BILINEAR, TRUE);
+
+ return (TRUE);
+}
+
+
+#define SET_PIXEL_RGB(pixbuf, x, y, rgba) \
+ *(gdk_pixbuf_get_pixels (pixbuf) + \
+ ((gint) (y) * gdk_pixbuf_get_rowstride (pixbuf)) + \
+ ((gint) (x) * 3)) = ((rgba) >> 24) & 0xff; \
+\
+ *(gdk_pixbuf_get_pixels (pixbuf) + \
+ ((gint) (y) * gdk_pixbuf_get_rowstride (pixbuf)) + \
+ ((gint) (x) * 3) + 1) = ((rgba) >> 16) & 0xff; \
+\
+ *(gdk_pixbuf_get_pixels (pixbuf) + \
+ ((gint) (y) * gdk_pixbuf_get_rowstride (pixbuf)) + \
+ ((gint) (x) * 3) + 2) = ((rgba) >> 8) & 0xff
+
+
+static void
+update_render_pixbuf (EMap *map, ArtFilterLevel interp, gboolean render_overlays)
+{
+ EMapPrivate *priv;
+ EMapPoint *point;
+ int width, height, orig_width, orig_height;
+ double zoom;
+ int i;
+
+ if (!GTK_WIDGET_REALIZED (GTK_WIDGET (map))) return;
+
+ /* Set up value shortcuts */
+
+ priv = map->priv;
+ width = GTK_WIDGET (map)->allocation.width;
+ height = GTK_WIDGET (map)->allocation.height;
+ orig_width = gdk_pixbuf_get_width (priv->map_pixbuf);
+ orig_height = gdk_pixbuf_get_height (priv->map_pixbuf);
+
+ /* Compute scaled width and height based on the extreme dimension */
+
+ if ((double) width / orig_width > (double) height / orig_height)
+ {
+ zoom = (double) width / (double) orig_width;
+ }
+ else
+ {
+ zoom = (double) height / (double) orig_height;
+ }
+
+ if (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 (priv->map_render_pixbuf) gdk_pixbuf_unref (priv->map_render_pixbuf);
+ priv->map_render_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, /* No alpha */
+ 8, width, height);
+
+ /* Scale the original map into the rendering pixbuf */
+
+ gdk_pixbuf_scale (priv->map_pixbuf, priv->map_render_pixbuf, 0, 0, /* Dest (x, y) */
+ width, height, 0, 0, /* Offset (x, y) */
+ zoom, zoom, /* Scale (x, y) */
+ interp);
+
+ if (render_overlays)
+ {
+ /* Add points */
+
+ for (i = 0; i < priv->points->len; i++)
+ {
+ point = g_ptr_array_index (priv->points, i);
+ update_render_point (map, point);
+ }
+ }
+
+ /* Compute image offsets with respect to window */
+
+ set_scroll_area (map);
+}
+
+
+/* Queues a repaint of the specified area in window coordinates */
+
+static void
+request_paint_area (EMap *view, GdkRectangle *area)
+{
+ EMapPrivate *priv;
+ int width, height;
+
+ if (!GTK_WIDGET_DRAWABLE (GTK_WIDGET (view)) ||
+ !GTK_WIDGET_REALIZED (GTK_WIDGET (view))) return;
+
+ priv = view->priv;
+ if (!priv->map_render_pixbuf) return;
+
+ width = MIN (area->width, E_MAP_GET_WIDTH (view));
+ height = MIN (area->height, E_MAP_GET_HEIGHT (view));
+
+ /* This satisfies paranoia. To be removed */
+
+ if (priv->xofs + width > gdk_pixbuf_get_width (priv->map_render_pixbuf))
+ width = gdk_pixbuf_get_width (priv->map_render_pixbuf) - priv->xofs;
+
+ if (priv->yofs + height > gdk_pixbuf_get_height (priv->map_render_pixbuf))
+ height = gdk_pixbuf_get_height (priv->map_render_pixbuf) - priv->yofs;
+
+ /* We rely on the fast case always being the case, since we load and
+ * preprocess the source pixbuf ourselves */
+
+ if (gdk_pixbuf_get_colorspace (priv->map_render_pixbuf) == GDK_COLORSPACE_RGB && !gdk_pixbuf_get_has_alpha (priv->map_render_pixbuf) &&
+ gdk_pixbuf_get_bits_per_sample (priv->map_render_pixbuf) == 8)
+ {
+ guchar *pixels;
+ int rowstride;
+
+ rowstride = gdk_pixbuf_get_rowstride (priv->map_render_pixbuf);
+ pixels = gdk_pixbuf_get_pixels (priv->map_render_pixbuf) + (area->y + priv->yofs) * rowstride + 3 * (area->x + priv->xofs);
+ gdk_draw_rgb_image_dithalign (GTK_WIDGET (view)->window, GTK_WIDGET (view)->style->black_gc, area->x, area->y, width, height, GDK_RGB_DITHER_NORMAL, pixels, rowstride, 0, 0);
+ return;
+ }
+
+#ifdef DEBUG
+ g_print ("Doing hard redraw.\n");
+#endif
+}
+
+
+/* Redraw point in client pixbuf */
+
+static void
+update_render_point (EMap *map, EMapPoint *point)
+{
+ EMapPrivate *priv;
+ GdkPixbuf *pb;
+ int width, height;
+ double px, py;
+
+ priv = map->priv;
+ pb = priv->map_render_pixbuf;
+ if (!pb) return;
+
+ width = gdk_pixbuf_get_width (pb);
+ height = gdk_pixbuf_get_height (pb);
+
+ e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
+ px += priv->xofs;
+ py += priv->yofs;
+
+ if (px < width && px >= 0 && py < height && py >= 0)
+ {
+ /* Area */
+
+ SET_PIXEL_RGB (pb, px, py, point->rgba);
+ if (px > 0) SET_PIXEL_RGB (pb, px - 1, py, point->rgba);
+ if (px < width - 1) SET_PIXEL_RGB (pb, px + 1, py, point->rgba);
+ if (py > 0) SET_PIXEL_RGB (pb, px, py - 1, point->rgba);
+ if (py < height - 1) SET_PIXEL_RGB (pb, px, py + 1, point->rgba);
+
+ /* Outline */
+
+ if (px > 1) SET_PIXEL_RGB (pb, px - 2, py, 0x000000ff);
+ if (px < width - 2) SET_PIXEL_RGB (pb, px + 2, py, 0x000000ff);
+ if (py > 1) SET_PIXEL_RGB (pb, px, py - 2, 0x000000ff);
+ if (py < height - 2) SET_PIXEL_RGB (pb, px, py + 2, 0x000000ff);
+ if (px > 0 && py > 0) SET_PIXEL_RGB (pb, px - 1, py - 1, 0x000000ff);
+ if (px > 0 && py < height - 1) SET_PIXEL_RGB (pb, px - 1, py + 1, 0x000000ff);
+ if (px < width - 1 && py > 0) SET_PIXEL_RGB (pb, px + 1, py - 1, 0x000000ff);
+ if (px < width - 1 && py < height - 1) SET_PIXEL_RGB (pb, px + 1, py + 1, 0x000000ff);
+ }
+}
+
+
+/* Repaint point on X server */
+
+static void
+repaint_point (EMap *map, EMapPoint *point)
+{
+ EMapPrivate *priv;
+ GdkRectangle area;
+ double px, py;
+
+ if (!e_map_point_is_in_view (map, point)) return;
+ priv = map->priv;
+
+ e_map_world_to_window (map, point->longitude, point->latitude, &px, &py);
+
+ area.x = (int) px - 2;
+ area.y = (int) py - 2;
+ area.width = 5;
+ area.height = 5;
+ request_paint_area (map, &area);
+}
+
+
+static void
+center_at (EMap *map, int x, int y, gboolean scroll)
+{
+ EMapPrivate *priv;
+ int pb_width, pb_height,
+ view_width, view_height;
+
+ priv = map->priv;
+
+ pb_width = E_MAP_GET_WIDTH (map);
+ pb_height = E_MAP_GET_HEIGHT (map);
+
+ view_width = GTK_WIDGET (map)->allocation.width;
+ view_height = GTK_WIDGET (map)->allocation.height;
+
+ x = CLAMP (x - (view_width / 2), 0, pb_width - view_width);
+ y = CLAMP (y - (view_height / 2), 0, pb_height - view_height);
+
+ if (scroll) scroll_to (map, x, y);
+ else
+ {
+ priv->xofs = x;
+ priv->yofs = y;
+ }
+}
+
+
+static void
+smooth_center_at (EMap *map, int x, int y)
+{
+ EMapPrivate *priv;
+ int pb_width, pb_height,
+ view_width, view_height;
+ int dx, dy;
+
+ priv = map->priv;
+
+ pb_width = E_MAP_GET_WIDTH (map);
+ pb_height = E_MAP_GET_HEIGHT (map);
+
+ view_width = GTK_WIDGET (map)->allocation.width;
+ view_height = GTK_WIDGET (map)->allocation.height;
+
+ x = CLAMP (x - (view_width / 2), 0, pb_width - view_width);
+ y = CLAMP (y - (view_height / 2), 0, pb_height - view_height);
+
+ for (;;)
+ {
+ if (priv->xofs == x && priv->yofs == y) break;
+
+ dx = (x < priv->xofs) ? -1 : (x > priv->xofs) ? 1 : 0;
+ dy = (y < priv->yofs) ? -1 : (y > priv->yofs) ? 1 : 0;
+
+ scroll_to (map, priv->xofs + dx, priv->yofs + dy);
+ }
+}
+
+
+/* Scrolls the view to the specified offsets. Does not perform range checking! */
+
+static void
+scroll_to (EMap *view, int x, int y)
+{
+ EMapPrivate *priv;
+ int xofs, yofs;
+ GdkWindow *window;
+ GdkGC *gc;
+ int width, height;
+ int src_x, src_y;
+ int dest_x, dest_y;
+ GdkEvent *event;
+
+ priv = view->priv;
+
+ /* Compute offsets and check bounds */
+
+ xofs = x - priv->xofs;
+ yofs = y - priv->yofs;
+
+ if (xofs == 0 && yofs == 0) return;
+
+ priv->xofs = x;
+ priv->yofs = y;
+
+ if (!GTK_WIDGET_DRAWABLE (view)) return;
+
+ width = GTK_WIDGET (view)->allocation.width;
+ height = GTK_WIDGET (view)->allocation.height;
+
+ if (abs (xofs) >= width || abs (yofs) >= height)
+ {
+ GdkRectangle area;
+
+ area.x = 0;
+ area.y = 0;
+ area.width = width;
+ area.height = height;
+
+ request_paint_area (view, &area);
+ return;
+ }
+
+ window = GTK_WIDGET (view)->window;
+
+ /* Copy the window area */
+
+ src_x = xofs < 0 ? 0 : xofs;
+ src_y = yofs < 0 ? 0 : yofs;
+ dest_x = xofs < 0 ? -xofs : 0;
+ dest_y = yofs < 0 ? -yofs : 0;
+
+ gc = gdk_gc_new (window);
+ gdk_gc_set_exposures (gc, TRUE);
+
+ gdk_window_copy_area (window, gc, dest_x, dest_y, window, src_x, src_y, width - abs (xofs), height - abs (yofs));
+
+ gdk_gc_destroy (gc);
+
+ /* Add the scrolled-in region */
+
+ if (xofs)
+ {
+ GdkRectangle r;
+
+ r.x = xofs < 0 ? 0 : width - xofs;
+ r.y = 0;
+ r.width = abs (xofs);
+ r.height = height;
+
+ request_paint_area (view, &r);
+ }
+
+ if (yofs)
+ {
+ GdkRectangle r;
+
+ r.x = 0;
+ r.y = yofs < 0 ? 0 : height - yofs;
+ r.width = width;
+ r.height = abs (yofs);
+
+ request_paint_area (view, &r);
+ }
+
+ /* Process graphics exposures */
+
+ while ((event = gdk_event_get_graphics_expose (window)) != NULL)
+ {
+ gtk_widget_event (GTK_WIDGET (view), event);
+
+ if (event->expose.count == 0)
+ {
+ gdk_event_free (event);
+ break;
+ }
+
+ gdk_event_free (event);
+ }
+}
+
+
+static int divide_seq[] =
+{
+ /* Dividends for divisor of 2 */
+
+ -2,
+
+ 1,
+
+ /* Dividends for divisor of 4 */
+
+ -4,
+
+ 1, 3,
+
+ /* Dividends for divisor of 8 */
+
+ -8,
+
+ 1, 5, 3, 7,
+
+ /* Dividends for divisor of 16 */
+
+ -16,
+
+ 1, 9, 5, 13, 3, 11, 7, 15,
+
+ /* Dividends for divisor of 32 */
+
+ -32,
+
+ 1, 17, 9, 25, 5, 21, 13, 29, 3, 19,
+ 11, 27, 7, 23, 15, 31,
+
+ /* Dividends for divisor of 64 */
+
+ -64,
+
+ 1, 33, 17, 49, 9, 41, 25, 57, 5, 37,
+ 21, 53, 13, 45, 29, 61, 3, 35, 19, 51,
+ 11, 43, 27, 59, 7, 39, 23, 55, 15, 47,
+ 31, 63,
+
+ /* Dividends for divisor of 128 */
+
+ -128,
+
+ 1, 65, 33, 97, 17, 81, 49, 113, 9, 73,
+ 41, 105, 25, 89, 57, 121, 5, 69, 37, 101,
+ 21, 85, 53, 117, 13, 77, 45, 109, 29, 93,
+ 61, 125, 3, 67, 35, 99, 19, 83, 51, 115,
+ 11, 75, 43, 107, 27, 91, 59, 123, 7, 71,
+ 39, 103, 23, 87, 55, 119, 15, 79, 47, 111,
+ 31, 95, 63, 127,
+
+ /* Dividends for divisor of 256 */
+
+ -256,
+
+ 1, 129, 65, 193, 33, 161, 97, 225, 17, 145,
+ 81, 209, 49, 177, 113, 241, 9, 137, 73, 201,
+ 41, 169, 105, 233, 25, 153, 89, 217, 57, 185,
+ 121, 249, 5, 133, 69, 197, 37, 165, 101, 229,
+ 21, 149, 85, 213, 53, 181, 117, 245, 13, 141,
+ 77, 205, 45, 173, 109, 237, 29, 157, 93, 221,
+ 61, 189, 125, 253, 3, 131, 67, 195, 35, 163,
+ 99, 227, 19, 147, 83, 211, 51, 179, 115, 243,
+ 11, 139, 75, 203, 43, 171, 107, 235, 27, 155,
+ 91, 219, 59, 187, 123, 251, 7, 135, 71, 199,
+ 39, 167, 103, 231, 23, 151, 87, 215, 55, 183,
+ 119, 247, 15, 143, 79, 207, 47, 175, 111, 239,
+ 31, 159, 95, 223, 63, 191, 127, 255,
+
+ 0
+};
+
+
+typedef enum
+{
+ AXIS_X,
+ AXIS_Y
+}
+AxisType;
+
+
+static void
+blowup_window_area (GdkWindow *window, gint area_x, gint area_y, gint target_x, gint target_y, gint total_width, gint total_height, gfloat zoom_factor)
+{
+ GdkGC *gc;
+ AxisType strong_axis;
+ gfloat axis_factor, axis_counter;
+ gint zoom_chunk;
+ gint divisor_width = 0, divisor_height = 0;
+ gint divide_width_index, divide_height_index;
+ gint area_width, area_height;
+ gint i, j;
+ int line;
+
+
+ /* Set up the GC we'll be using */
+
+ gc = gdk_gc_new (window);
+ gdk_gc_set_exposures (gc, FALSE);
+
+ /* Get area constraints */
+
+ gdk_window_get_size (window, &area_width, &area_height);
+
+ /* Initialize area division array indexes */
+
+ divide_width_index = divide_height_index = 0;
+
+ /* Initialize axis counter */
+
+ axis_counter = 0.0;
+
+ /* Find the strong axis (which is the basis for iteration) and the ratio
+ * at which the other axis will be scaled.
+ *
+ * Also determine how many lines to expand in one fell swoop, and store
+ * this figure in zoom_chunk. */
+
+ if (area_width > area_height)
+ {
+ strong_axis = AXIS_X;
+ axis_factor = (double) area_height / (double) area_width;
+ zoom_chunk = MAX (1, area_width / 250);
+ i = (area_width * (zoom_factor - 1.0)) / zoom_chunk;
+ }
+ else
+ {
+ strong_axis = AXIS_Y;
+ axis_factor = (double) area_width / (double) area_height;
+ zoom_chunk = MAX (1, area_height / 250);
+ i = (area_height * (zoom_factor - 1.0)) / zoom_chunk;
+ }
+
+ /* Go, go, devil bunnies! Gogo devil bunnies! */
+
+ for (; i > 0; i--)
+ {
+ /* Reset division sequence table indexes as necessary */
+
+ if (!divide_seq[divide_width_index]) divide_width_index = 0;
+ if (!divide_seq[divide_height_index]) divide_height_index = 0;
+
+ /* Set new divisor if found in table */
+
+ if (divide_seq[divide_width_index] < 0)
+ divisor_width = abs (divide_seq[divide_width_index++]);
+ if (divide_seq[divide_height_index] < 0)
+ divisor_height = abs (divide_seq[divide_height_index++]);
+
+ /* Widen */
+
+ if (strong_axis == AXIS_X || axis_counter >= 1.0)
+ {
+ line = ((divide_seq[divide_width_index] * area_width) / divisor_width) + 0.5;
+
+ if ((line < target_x && target_x > area_width / 2) || (line > target_x && target_x > (area_width / 2) + zoom_chunk))
+ {
+ /* Push left */
+
+ for (j = 0; j < zoom_chunk - 1; j++)
+ gdk_window_copy_area (window, gc, line + j + 1, 0, window, line, 0, 1, area_height);
+
+ gdk_window_copy_area (window, gc, 0, 0, window, zoom_chunk, 0, line, area_height);
+ if (line > target_x) target_x -= zoom_chunk;
+ }
+ else
+ {
+ /* Push right */
+
+ for (j = 0; j < zoom_chunk - 1; j++)
+ gdk_window_copy_area (window, gc, line + j - (zoom_chunk - 1), 0, window, line - zoom_chunk, 0, 1, area_height);
+
+ gdk_window_copy_area (window, gc, line, 0, window, line - zoom_chunk, 0, area_width - line, area_height);
+ if (line < target_x) target_x += zoom_chunk;
+ }
+ }
+
+ if (strong_axis == AXIS_Y || axis_counter >= 1.0)
+ {
+ /* Heighten */
+
+ line = ((divide_seq[divide_height_index] * area_height) / divisor_height) + 0.5;
+
+ if ((line < target_y && target_y > area_height / 2) || (line > target_y && target_y > (area_height / 2) + zoom_chunk))
+ {
+ /* Push up */
+
+ for (j = 0; j < zoom_chunk - 1; j++)
+ gdk_window_copy_area (window, gc, 0, line + j + 1, window, 0, line, area_width, 1);
+
+ gdk_window_copy_area (window, gc, 0, 0, window, 0, zoom_chunk, area_width, line);
+ if (line > target_y) target_y -= zoom_chunk;
+ }
+ else
+ {
+ /* Push down */
+
+ for (j = 0; j < zoom_chunk - 1; j++)
+ gdk_window_copy_area (window, gc, 0, line + j - (zoom_chunk - 1), window, 0, line - zoom_chunk, area_width, 1);
+
+ gdk_window_copy_area (window, gc, 0, line, window, 0, line - zoom_chunk, area_width, area_height - line);
+ if (line < target_y) target_y += zoom_chunk;
+ }
+ }
+
+ divide_width_index++;
+ divide_height_index++;
+ if (axis_counter >= 1.0) axis_counter -= 1.0;
+ axis_counter += axis_factor;
+ }
+
+ /* Free our GC */
+
+ gdk_gc_destroy (gc);
+}
+
+
+static void
+zoom_in_smooth (EMap *map)
+{
+ GdkRectangle area;
+ EMapPrivate *priv;
+ GdkWindow *window;
+ int width, height;
+ int win_width, win_height;
+ int target_width, target_height;
+ double x, y;
+
+ g_return_if_fail (map);
+ g_return_if_fail (GTK_WIDGET_REALIZED (GTK_WIDGET (map)));
+
+ area.x = 0;
+ area.y = 0;
+ area.width = GTK_WIDGET (map)->allocation.width;
+ area.height = GTK_WIDGET (map)->allocation.height;
+
+ priv = map->priv;
+ window = GTK_WIDGET (map)->window;
+ width = gdk_pixbuf_get_width (priv->map_render_pixbuf);
+ height = gdk_pixbuf_get_height (priv->map_render_pixbuf);
+ win_width = GTK_WIDGET (map)->allocation.width;
+ win_height = GTK_WIDGET (map)->allocation.height;
+ target_width = win_width / 4;
+ target_height = win_height / 4;
+
+ /* Center the target point as much as possible */
+
+ e_map_world_to_window (map, priv->zoom_target_long, priv->zoom_target_lat, &x, &y);
+ smooth_center_at (map, x + priv->xofs, y + priv->yofs);
+
+ /* Render and paint a temporary map without overlays, so they don't get in
+ * the way (look ugly) while zooming */
+
+ update_render_pixbuf (map, GDK_INTERP_BILINEAR, FALSE);
+ request_paint_area (map, &area);
+
+ /* Find out where in the area we're going to zoom to */
+
+ e_map_world_to_window (map, priv->zoom_target_long, priv->zoom_target_lat, &x, &y);
+
+ /* Pre-render the zoomed-in map, so we can put it there quickly when the
+ * blowup sequence ends */
+
+ priv->zoom_state = E_MAP_ZOOMED_IN;
+ update_render_pixbuf (map, GDK_INTERP_BILINEAR, TRUE);
+
+ /* Do the blowup */
+
+ blowup_window_area (window, priv->xofs, priv->yofs, x, y, width, height, 1.68);
+
+ /* Set new scroll offsets and paint the zoomed map */
+
+ e_map_world_to_window (map, priv->zoom_target_long, priv->zoom_target_lat, &x, &y);
+ priv->xofs = CLAMP (priv->xofs + x - area.width / 2.0, 0, E_MAP_GET_WIDTH (map) - area.width);
+ priv->yofs = CLAMP (priv->yofs + y - area.height / 2.0, 0, E_MAP_GET_HEIGHT (map) - area.height);
+
+ request_paint_area (map, &area);
+}
+
+
+static void
+zoom_in (EMap *map)
+{
+ GdkRectangle area;
+ EMapPrivate *priv;
+ double x, y;
+
+ priv = map->priv;
+
+ area.x = 0;
+ area.y = 0;
+ area.width = GTK_WIDGET (map)->allocation.width;
+ area.height = GTK_WIDGET (map)->allocation.height;
+
+ priv->zoom_state = E_MAP_ZOOMED_IN;
+
+ update_render_pixbuf (map, GDK_INTERP_BILINEAR, TRUE);
+
+ e_map_world_to_window (map, priv->zoom_target_long, priv->zoom_target_lat, &x, &y);
+ priv->xofs = CLAMP (priv->xofs + x - area.width / 2.0, 0, E_MAP_GET_WIDTH (map) - area.width);
+ priv->yofs = CLAMP (priv->yofs + y - area.height / 2.0, 0, E_MAP_GET_HEIGHT (map) - area.height);
+
+ request_paint_area (map, &area);
+}
+
+
+static void
+zoom_out (EMap *map)
+{
+ GdkRectangle area;
+ EMapPrivate *priv;
+ double longitude, latitude;
+ double x, y;
+
+ priv = map->priv;
+
+ area.x = 0;
+ area.y = 0;
+ area.width = GTK_WIDGET (map)->allocation.width;
+ area.height = GTK_WIDGET (map)->allocation.height;
+
+ /* Must be done before update_render_pixbuf() */
+
+ e_map_window_to_world (map, area.width / 2, area.height / 2,
+ &longitude, &latitude);
+
+ priv->zoom_state = E_MAP_ZOOMED_OUT;
+ update_render_pixbuf (map, GDK_INTERP_BILINEAR, TRUE);
+
+ e_map_world_to_window (map, longitude, latitude, &x, &y);
+ center_at (map, x + priv->xofs, y + priv->yofs, FALSE);
+/* request_paint_area (map, &area); */
+ repaint_visible (map);
+}
+
+
+static void
+zoom_do (EMap *map)
+{
+ EMapPrivate *priv;
+
+ priv = map->priv;
+
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->hadj), map);
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->vadj), map);
+
+ if (priv->zoom_state == E_MAP_ZOOMING_IN)
+ {
+ if (e_map_get_smooth_zoom (map)) zoom_in_smooth (map);
+ else zoom_in (map);
+ }
+ else if (priv->zoom_state == E_MAP_ZOOMING_OUT)
+ {
+/* if (e_map_get_smooth_zoom(map)) zoom_out_smooth(map); */
+ zoom_out (map);
+ }
+
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->hadj), map);
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->vadj), map);
+
+ set_scroll_area(map);
+}
+
+
+/* Callback used when an adjustment is changed */
+
+static void
+adjustment_changed_cb (GtkAdjustment *adj, gpointer data)
+{
+ EMap *view;
+ EMapPrivate *priv;
+
+ view = E_MAP (data);
+ priv = view->priv;
+
+ scroll_to (view, priv->hadj->value, priv->vadj->value);
+}
+
+
+static void
+set_scroll_area (EMap *view)
+{
+ EMapPrivate *priv;
+
+ priv = view->priv;
+
+ if (!GTK_WIDGET_REALIZED (GTK_WIDGET (view))) return;
+ if (!priv->hadj || !priv->vadj) return;
+
+ /* Set scroll increments */
+
+ priv->hadj->page_size = GTK_WIDGET (view)->allocation.width;
+ priv->hadj->page_increment = GTK_WIDGET (view)->allocation.width / 2;
+ priv->hadj->step_increment = SCROLL_STEP_SIZE;
+
+ priv->vadj->page_size = GTK_WIDGET (view)->allocation.height;
+ priv->vadj->page_increment = GTK_WIDGET (view)->allocation.height / 2;
+ priv->vadj->step_increment = SCROLL_STEP_SIZE;
+
+ /* Set scroll bounds and new offsets */
+
+ priv->hadj->lower = 0;
+ if (priv->map_render_pixbuf)
+ priv->hadj->upper = gdk_pixbuf_get_width (priv->map_render_pixbuf);
+
+ priv->vadj->lower = 0;
+ if (priv->map_render_pixbuf)
+ priv->vadj->upper = gdk_pixbuf_get_height (priv->map_render_pixbuf);
+
+ gtk_signal_emit_by_name (GTK_OBJECT (priv->hadj), "changed");
+ gtk_signal_emit_by_name (GTK_OBJECT (priv->vadj), "changed");
+
+ priv->xofs = CLAMP (priv->xofs, 0, priv->hadj->upper - priv->hadj->page_size);
+ priv->yofs = CLAMP (priv->yofs, 0, priv->vadj->upper - priv->vadj->page_size);
+
+ if (priv->hadj->value != priv->xofs)
+ {
+ priv->hadj->value = priv->xofs;
+
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->hadj), view);
+ gtk_signal_emit_by_name (GTK_OBJECT (priv->hadj), "value_changed");
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->hadj), view);
+ }
+
+ if (priv->vadj->value != priv->yofs)
+ {
+ priv->vadj->value = priv->yofs;
+
+ gtk_signal_handler_block_by_data (GTK_OBJECT (priv->vadj), view);
+ gtk_signal_emit_by_name (GTK_OBJECT (priv->vadj), "value_changed");
+ gtk_signal_handler_unblock_by_data (GTK_OBJECT (priv->vadj), view);
+ }
+}
diff --git a/widgets/misc/e-map.h b/widgets/misc/e-map.h
new file mode 100644
index 0000000000..df914bec72
--- /dev/null
+++ b/widgets/misc/e-map.h
@@ -0,0 +1,139 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Map widget.
+ *
+ * Copyright (C) 2000-2001 Ximian, Inc.
+ *
+ * Authors: Hans Petter Jansson <hpj@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef E_MAP_H
+#define E_MAP_H
+
+#include <libgnome/gnome-defs.h>
+#include <gtk/gtkwidget.h>
+
+#define TYPE_E_MAP (e_map_get_type ())
+#define E_MAP(obj) (GTK_CHECK_CAST ((obj), TYPE_E_MAP, EMap))
+#define E_MAP_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TYPE_E_MAP, EMapClass))
+#define IS_E_MAP(obj) (GTK_CHECK_TYPE ((obj), TYPE_E_MAP))
+#define IS_E_MAP_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), TYPE_E_MAP))
+
+typedef struct _EMap EMap;
+typedef struct _EMapClass EMapClass;
+typedef struct _EMapPoint EMapPoint;
+
+struct _EMap
+{
+ GtkWidget widget;
+
+ /* Private data */
+ gpointer priv;
+};
+
+struct _EMapClass
+{
+ GtkWidgetClass parent_class;
+
+ /* Notification signals */
+ void (*zoom_fit) (EMap * view);
+
+ /* GTK+ scrolling interface */
+ void (*set_scroll_adjustments) (GtkWidget * widget,
+ GtkAdjustment * hadj,
+ GtkAdjustment * vadj);
+};
+
+/* The definition of Dot */
+
+struct _EMapPoint
+{
+ gchar *name; /* Can be NULL */
+ double longitude, latitude;
+ guint32 rgba;
+ gpointer user_data;
+};
+
+
+/* --- Widget --- */
+
+GtkType e_map_get_type (void);
+
+EMap *e_map_new (void);
+
+/* Stop doing redraws when map data changes (e.g. by modifying points) */
+void e_map_freeze (EMap *map);
+
+/* Do an immediate repaint, and start doing realtime repaints again */
+void e_map_thaw (EMap *map);
+
+/* --- Coordinate translation --- */
+
+/* Translates window-relative coords to lat/long */
+void e_map_window_to_world (EMap *map,
+ double win_x, double win_y,
+ double *world_longitude, double *world_latitude);
+
+/* Translates lat/long to window-relative coordinates. Note that the
+ * returned coordinates can be negative or greater than the current size
+ * of the allocation area */
+void e_map_world_to_window (EMap *map,
+ double world_longitude, double world_latitude,
+ double *win_x, double *win_y);
+
+/* --- Zoom --- */
+
+double e_map_get_magnification (EMap *map);
+
+/* Pass TRUE if we want the smooth zoom hack */
+void e_map_set_smooth_zoom (EMap *map, gboolean state);
+
+/* TRUE if smooth zoom hack will be employed */
+gboolean e_map_get_smooth_zoom (EMap *map);
+
+/* NB: Function definition will change shortly */
+void e_map_zoom_to_location (EMap *map, double longitude, double latitude);
+
+/* Zoom to mag factor 1.0 */
+void e_map_zoom_out (EMap *map);
+
+/* --- Points --- */
+
+EMapPoint *e_map_add_point (EMap *map, gchar *name,
+ double longitude, double latitude,
+ guint32 color_rgba);
+
+void e_map_remove_point (EMap *map, EMapPoint *point);
+
+void e_map_point_get_location (EMapPoint *point,
+ double *longitude, double *latitude);
+
+gchar *e_map_point_get_name (EMapPoint *point);
+
+guint32 e_map_point_get_color_rgba (EMapPoint *point);
+
+void e_map_point_set_color_rgba (EMap *map, EMapPoint *point, guint32 color_rgba);
+
+void e_map_point_set_data (EMapPoint *point, gpointer data);
+
+gpointer e_map_point_get_data (EMapPoint *point);
+
+gboolean e_map_point_is_in_view (EMap *map, EMapPoint *point);
+
+EMapPoint *e_map_get_closest_point (EMap *map, double longitude, double latitude,
+ gboolean in_view);
+
+#endif