diff options
Diffstat (limited to 'applets/clock/clock-map.c')
-rw-r--r-- | applets/clock/clock-map.c | 700 |
1 files changed, 700 insertions, 0 deletions
diff --git a/applets/clock/clock-map.c b/applets/clock/clock-map.c new file mode 100644 index 00000000..5a6e0379 --- /dev/null +++ b/applets/clock/clock-map.c @@ -0,0 +1,700 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <cairo.h> +#include <sys/time.h> +#include <sys/timeb.h> +#include <gtk/gtk.h> +#include <math.h> + +#include "clock.h" +#include "clock-map.h" +#include "clock-sunpos.h" +#include "clock-marshallers.h" + +G_DEFINE_TYPE (ClockMap, clock_map, GTK_TYPE_WIDGET) + +enum { + NEED_LOCATIONS, + LAST_SIGNAL +}; + +enum { + MARKER_NORMAL = 0, + MARKER_HILIGHT, + MARKER_CURRENT, + MARKER_NB +}; + +static char *marker_files[MARKER_NB] = { + ICONDIR "/clock-map-location-marker.png", + ICONDIR "/clock-map-location-hilight.png", + ICONDIR "/clock-map-location-current.png" +}; + +static guint signals[LAST_SIGNAL]; + +typedef struct { + time_t last_refresh; + + gint width; + gint height; + + guint highlight_timeout_id; + + GdkPixbuf *stock_map_pixbuf; + GdkPixbuf *location_marker_pixbuf[MARKER_NB]; + + GdkPixbuf *location_map_pixbuf; + + /* The shadow itself */ + GdkPixbuf *shadow_pixbuf; + + /* The map with the shadow composited onto it */ + GdkPixbuf *shadow_map_pixbuf; +} ClockMapPrivate; + +static void clock_map_finalize (GObject *); +static void clock_map_size_request (GtkWidget *this, + GtkRequisition *requisition); +static void clock_map_size_allocate (GtkWidget *this, + GtkAllocation *allocation); +static gboolean clock_map_expose (GtkWidget *this, + GdkEventExpose *expose); + +static void clock_map_place_locations (ClockMap *this); +static void clock_map_render_shadow (ClockMap *this); +static void clock_map_display (ClockMap *this); + +#define PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLOCK_MAP_TYPE, ClockMapPrivate)) + +ClockMap * +clock_map_new (void) +{ + ClockMap *this; + + this = g_object_new (CLOCK_MAP_TYPE, NULL); + + return this; +} + +static void +clock_map_class_init (ClockMapClass *this_class) +{ + GObjectClass *g_obj_class = G_OBJECT_CLASS (this_class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (g_obj_class); + + g_obj_class->finalize = clock_map_finalize; + + /* GtkWidget signals */ + widget_class->size_request = clock_map_size_request; + widget_class->size_allocate = clock_map_size_allocate; + widget_class->expose_event = clock_map_expose; + + g_type_class_add_private (this_class, sizeof (ClockMapPrivate)); + + /** + * ClockMap::need-locations + * + * The map widget emits this signal when it needs to know which + * locations to display. + * + * Returns: the handler should return a (GList *) of (ClockLocation *). + * The map widget will not modify this list, so the caller should keep + * it alive. + */ + signals[NEED_LOCATIONS] = g_signal_new ("need-locations", + G_TYPE_FROM_CLASS (g_obj_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClockMapClass, need_locations), + NULL, + NULL, + _clock_marshal_POINTER__VOID, + G_TYPE_POINTER, 0); +} + +static void +clock_map_init (ClockMap *this) +{ + int i; + ClockMapPrivate *priv = PRIVATE (this); + + gtk_widget_set_has_window (GTK_WIDGET (this), FALSE); + + priv->last_refresh = 0; + priv->width = 0; + priv->height = 0; + priv->highlight_timeout_id = 0; + priv->stock_map_pixbuf = NULL; + + g_assert (sizeof (marker_files)/sizeof (char *) == MARKER_NB); + + for (i = 0; i < MARKER_NB; i++) { + priv->location_marker_pixbuf[i] = gdk_pixbuf_new_from_file + (marker_files[i], NULL); + } +} + +static void +clock_map_finalize (GObject *g_obj) +{ + ClockMapPrivate *priv = PRIVATE (g_obj); + int i; + + if (priv->highlight_timeout_id) { + g_source_remove (priv->highlight_timeout_id); + priv->highlight_timeout_id = 0; + } + + if (priv->stock_map_pixbuf) { + g_object_unref (priv->stock_map_pixbuf); + priv->stock_map_pixbuf = NULL; + } + + for (i = 0; i < MARKER_NB; i++) { + if (priv->location_marker_pixbuf[i]) { + g_object_unref (priv->location_marker_pixbuf[i]); + priv->location_marker_pixbuf[i] = NULL; + } + } + + if (priv->location_map_pixbuf) { + g_object_unref (priv->location_map_pixbuf); + priv->location_map_pixbuf = NULL; + } + + if (priv->shadow_pixbuf) { + g_object_unref (priv->shadow_pixbuf); + priv->shadow_pixbuf = NULL; + } + + if (priv->shadow_map_pixbuf) { + g_object_unref (priv->shadow_map_pixbuf); + priv->shadow_map_pixbuf = NULL; + } + + G_OBJECT_CLASS (clock_map_parent_class)->finalize (g_obj); +} + +void +clock_map_refresh (ClockMap *this) +{ + ClockMapPrivate *priv = PRIVATE (this); + GtkWidget *widget = GTK_WIDGET (this); + GtkAllocation allocation; + + gtk_widget_get_allocation (widget, &allocation); + + /* Only do something if we have some space allocated. + * Note that 1x1 is not really some space... */ + if (allocation.width <= 1 || allocation.height <= 1) + return; + + /* Allocation changed => we reload the map */ + if (priv->width != allocation.width || + priv->height != allocation.height) { + if (priv->stock_map_pixbuf) { + g_object_unref (priv->stock_map_pixbuf); + priv->stock_map_pixbuf = NULL; + } + + priv->width = allocation.width; + priv->height = allocation.height; + } + + if (!priv->stock_map_pixbuf) { + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale + (ICONDIR "/clock-map.png", + priv->width, priv->height, FALSE, NULL); + + priv->stock_map_pixbuf = pixbuf; + } + + clock_map_place_locations (this); + + clock_map_display (this); +} + +static gboolean +clock_map_expose (GtkWidget *this, GdkEventExpose *event) +{ + ClockMapPrivate *priv = PRIVATE (this); + GdkWindow *window; + GtkStyle *style; + GtkAllocation allocation; + GdkRectangle region; + cairo_t *cr; + + window = gtk_widget_get_window (this); + style = gtk_widget_get_style (this); + gtk_widget_get_allocation (this, &allocation); + + if (!priv->shadow_map_pixbuf) { + g_warning ("Needed to refresh the map in expose event."); + clock_map_refresh (CLOCK_MAP (this)); + } + + cr = gdk_cairo_create (window); + + region.x = allocation.x; + region.y = allocation.y; + region.width = gdk_pixbuf_get_width (priv->shadow_map_pixbuf); + region.height = gdk_pixbuf_get_height (priv->shadow_map_pixbuf); + + gdk_rectangle_intersect (®ion, &(event->area), ®ion); + gdk_draw_pixbuf (window, + style->black_gc, + priv->shadow_map_pixbuf, + region.x - allocation.x, + region.y - allocation.y, + region.x, + region.y, + region.width, + region.height, + GDK_RGB_DITHER_NORMAL, + 0, 0); + + /* draw a simple outline */ + cairo_rectangle ( + cr, + allocation.x + 0.5, allocation.y + 0.5, + gdk_pixbuf_get_width (priv->shadow_map_pixbuf) - 1, + gdk_pixbuf_get_height (priv->shadow_map_pixbuf) - 1); + + cairo_set_source_rgb ( + cr, + style->mid [GTK_STATE_ACTIVE].red / 65535.0, + style->mid [GTK_STATE_ACTIVE].green / 65535.0, + style->mid [GTK_STATE_ACTIVE].blue / 65535.0); + + cairo_set_line_width (cr, 1.0); + cairo_stroke (cr); + + cairo_destroy (cr); + + return FALSE; +} + +static void +clock_map_size_request (GtkWidget *this, GtkRequisition *requisition) +{ + requisition->width = 250; + requisition->height = 125; +} + +static void +clock_map_size_allocate (GtkWidget *this, GtkAllocation *allocation) +{ + ClockMapPrivate *priv = PRIVATE (this); + + if (GTK_WIDGET_CLASS (clock_map_parent_class)->size_allocate) + GTK_WIDGET_CLASS (clock_map_parent_class)->size_allocate (this, allocation); + + if (priv->width != allocation->width || + priv->height != allocation->height) + clock_map_refresh (CLOCK_MAP (this)); +} + +static void +clock_map_mark (ClockMap *this, gfloat latitude, gfloat longitude, gint mark) +{ + ClockMapPrivate *priv = PRIVATE (this); + GdkPixbuf *marker = priv->location_marker_pixbuf[mark]; + GdkPixbuf *partial = NULL; + + int x, y; + int width, height; + int marker_width, marker_height; + int dest_x, dest_y, dest_width, dest_height; + + width = gdk_pixbuf_get_width (priv->location_map_pixbuf); + height = gdk_pixbuf_get_height (priv->location_map_pixbuf); + + x = (width / 2.0 + (width / 2.0) * longitude / 180.0); + y = (height / 2.0 - (height / 2.0) * latitude / 90.0); + + marker_width = gdk_pixbuf_get_width (marker); + marker_height = gdk_pixbuf_get_height (marker); + + dest_x = x - marker_width / 2; + dest_y = y - marker_height / 2; + dest_width = marker_width; + dest_height = marker_height; + + /* create a small partial pixbuf if the mark is too close to + the north or south pole */ + if (dest_y < 0) { + partial = gdk_pixbuf_new_subpixbuf + (marker, 0, dest_y + marker_height, + marker_width, -dest_y); + + dest_y = 0.0; + marker_height = gdk_pixbuf_get_height (partial); + } else if (dest_y + dest_height > height) { + partial = gdk_pixbuf_new_subpixbuf + (marker, 0, 0, marker_width, height - dest_y); + marker_height = gdk_pixbuf_get_height (partial); + } + + if (partial) { + marker = partial; + } + + /* handle the cases where the marker needs to be placed across + the 180 degree longitude line */ + if (dest_x < 0) { + /* split into our two pixbufs for the left and right edges */ + GdkPixbuf *lhs = NULL; + GdkPixbuf *rhs = NULL; + + lhs = gdk_pixbuf_new_subpixbuf + (marker, -dest_x, 0, marker_width + dest_x, marker_height); + + gdk_pixbuf_composite (lhs, priv->location_map_pixbuf, + 0, dest_y, + gdk_pixbuf_get_width (lhs), + gdk_pixbuf_get_height (lhs), + 0, dest_y, + 1.0, 1.0, GDK_INTERP_NEAREST, 0xFF); + + rhs = gdk_pixbuf_new_subpixbuf + (marker, 0, 0, -dest_x, marker_height); + + gdk_pixbuf_composite (rhs, priv->location_map_pixbuf, + width - gdk_pixbuf_get_width (rhs) - 1, + dest_y, + gdk_pixbuf_get_width (rhs), + gdk_pixbuf_get_height (rhs), + width - gdk_pixbuf_get_width (rhs) - 1, + dest_y, + 1.0, 1.0, GDK_INTERP_NEAREST, 0xFF); + + g_object_unref (lhs); + g_object_unref (rhs); + } else if (dest_x + dest_width > width) { + /* split into our two pixbufs for the left and right edges */ + GdkPixbuf *lhs = NULL; + GdkPixbuf *rhs = NULL; + + lhs = gdk_pixbuf_new_subpixbuf + (marker, width - dest_x, 0, marker_width - width + dest_x, marker_height); + + gdk_pixbuf_composite (lhs, priv->location_map_pixbuf, + 0, dest_y, + gdk_pixbuf_get_width (lhs), + gdk_pixbuf_get_height (lhs), + 0, dest_y, + 1.0, 1.0, GDK_INTERP_NEAREST, 0xFF); + + rhs = gdk_pixbuf_new_subpixbuf + (marker, 0, 0, width - dest_x, marker_height); + + gdk_pixbuf_composite (rhs, priv->location_map_pixbuf, + width - gdk_pixbuf_get_width (rhs) - 1, + dest_y, + gdk_pixbuf_get_width (rhs), + gdk_pixbuf_get_height (rhs), + width - gdk_pixbuf_get_width (rhs) - 1, + dest_y, + 1.0, 1.0, GDK_INTERP_NEAREST, 0xFF); + + g_object_unref (lhs); + g_object_unref (rhs); + } else { + gdk_pixbuf_composite (marker, priv->location_map_pixbuf, + dest_x, dest_y, + gdk_pixbuf_get_width (marker), + gdk_pixbuf_get_height (marker), + dest_x, dest_y, + 1.0, 1.0, GDK_INTERP_NEAREST, 0xFF); + } + + if (partial != NULL) { + g_object_unref (partial); + } +} + +/** + * Return value: %TRUE if @loc can be placed on the map, %FALSE otherwise. + **/ +static gboolean +clock_map_place_location (ClockMap *this, ClockLocation *loc, gboolean hilight) +{ + gfloat latitude, longitude; + gint marker; + + clock_location_get_coords (loc, &latitude, &longitude); + /* 0/0 means unset, basically */ + if (latitude == 0 && longitude == 0) + return FALSE; + + if (hilight) + marker = MARKER_HILIGHT; + else if (clock_location_is_current (loc)) + marker = MARKER_CURRENT; + else + marker = MARKER_NORMAL; + + clock_map_mark (this, latitude, longitude, marker); + + return TRUE; +} + +static void +clock_map_place_locations (ClockMap *this) +{ + ClockMapPrivate *priv = PRIVATE (this); + GList *locs; + ClockLocation *loc; + + if (priv->location_map_pixbuf) { + g_object_unref (priv->location_map_pixbuf); + priv->location_map_pixbuf = NULL; + } + + priv->location_map_pixbuf = gdk_pixbuf_copy (priv->stock_map_pixbuf); + + locs = NULL; + g_signal_emit (this, signals[NEED_LOCATIONS], 0, &locs); + + while (locs) { + loc = CLOCK_LOCATION (locs->data); + + clock_map_place_location (this, loc, FALSE); + + locs = locs->next; + } + +#if 0 + /* map_mark test suite for the edge cases */ + + /* points around longitude 180 */ + clock_map_mark (this, 0.0, 180.0); + clock_map_mark (this, -15.0, -178.0); + clock_map_mark (this, -30.0, -176.0); + clock_map_mark (this, 15.0, 178.0); + clock_map_mark (this, 30.0, 176.0); + + clock_map_mark (this, 90.0, 180.0); + clock_map_mark (this, -90.0, 180.0); + + /* north pole & friends */ + clock_map_mark (this, 90.0, 0.0); + clock_map_mark (this, 88.0, -15.0); + clock_map_mark (this, 92.0, 15.0); + + /* south pole & friends */ + clock_map_mark (this, -90.0, 0.0); + clock_map_mark (this, -88.0, -15.0); + clock_map_mark (this, -92.0, 15.0); +#endif +} + +static void +clock_map_compute_vector (gdouble lat, gdouble lon, gdouble *vec) +{ + gdouble lat_rad, lon_rad; + lat_rad = lat * (M_PI/180.0); + lon_rad = lon * (M_PI/180.0); + + vec[0] = sin(lon_rad) * cos(lat_rad); + vec[1] = sin(lat_rad); + vec[2] = cos(lon_rad) * cos(lat_rad); +} + +static guchar +clock_map_is_sunlit (gdouble pos_lat, gdouble pos_long, + gdouble sun_lat, gdouble sun_long) +{ + gdouble pos_vec[3]; + gdouble sun_vec[3]; + gdouble dot; + + /* twilight */ + gdouble epsilon = 0.01; + + clock_map_compute_vector (pos_lat, pos_long, pos_vec); + clock_map_compute_vector (sun_lat, sun_long, sun_vec); + + /* compute the dot product of the two */ + dot = pos_vec[0]*sun_vec[0] + pos_vec[1]*sun_vec[1] + + pos_vec[2]*sun_vec[2]; + + if (dot > epsilon) { + return 0x00; + } + + if (dot < -epsilon) { + return 0xFF; + } + + return (guchar)(-128 * ((dot / epsilon) - 1)); +} + +static void +clock_map_render_shadow_pixbuf (GdkPixbuf *pixbuf) +{ + int x, y; + int height, width; + int n_channels, rowstride; + guchar *pixels, *p; + gdouble sun_lat, sun_lon; + time_t now = time (NULL); + + n_channels = gdk_pixbuf_get_n_channels (pixbuf); + rowstride = gdk_pixbuf_get_rowstride (pixbuf); + pixels = gdk_pixbuf_get_pixels (pixbuf); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + sun_position (now, &sun_lat, &sun_lon); + + for (y = 0; y < height; y++) { + gdouble lat = (height / 2.0 - y) / (height / 2.0) * 90.0; + + for (x = 0; x < width; x++) { + guchar shade; + gdouble lon = + (x - width / 2.0) / (width / 2.0) * 180.0; + + shade = clock_map_is_sunlit (lat, lon, + sun_lat, sun_lon); + + p = pixels + y * rowstride + x * n_channels; + p[3] = shade; + } + } +} + +static void +clock_map_render_shadow (ClockMap *this) +{ + ClockMapPrivate *priv = PRIVATE (this); + + if (priv->shadow_pixbuf) { + g_object_unref (priv->shadow_pixbuf); + } + + priv->shadow_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, + priv->width, priv->height); + + /* Initialize to all shadow */ + gdk_pixbuf_fill (priv->shadow_pixbuf, 0x6d9ccdff); + + clock_map_render_shadow_pixbuf (priv->shadow_pixbuf); + + if (priv->shadow_map_pixbuf) { + g_object_unref (priv->shadow_map_pixbuf); + } + + priv->shadow_map_pixbuf = gdk_pixbuf_copy (priv->location_map_pixbuf); + + gdk_pixbuf_composite (priv->shadow_pixbuf, priv->shadow_map_pixbuf, + 0, 0, priv->width, priv->height, + 0, 0, 1, 1, GDK_INTERP_NEAREST, 0x66); +} + +static void +clock_map_display (ClockMap *this) +{ + ClockMapPrivate *priv = PRIVATE (this); + + clock_map_render_shadow (this); + gtk_widget_queue_draw (GTK_WIDGET (this)); + + time (&priv->last_refresh); +} + +typedef struct { + ClockMap *map; + ClockLocation *location; + int count; +} BlinkData; + +static gboolean +highlight (gpointer user_data) +{ + BlinkData *data = user_data; + + if (data->count == 6) + return FALSE; + + if (data->count % 2 == 0) { + if (!clock_map_place_location (data->map, + data->location, TRUE)) + return FALSE; + } else + clock_map_place_locations (data->map); + clock_map_display (data->map); + + data->count++; + + return TRUE; +} + +static void +highlight_destroy (gpointer user_data) +{ + BlinkData *data = user_data; + ClockMapPrivate *priv; + + priv = PRIVATE (data->map); + priv->highlight_timeout_id = 0; + + g_object_unref (data->location); + g_free (data); +} + +void +clock_map_blink_location (ClockMap *this, ClockLocation *loc) +{ + BlinkData *data; + ClockMapPrivate *priv; + + priv = PRIVATE (this); + + g_return_if_fail (IS_CLOCK_MAP (this)); + g_return_if_fail (IS_CLOCK_LOCATION (loc)); + + data = g_new0 (BlinkData, 1); + data->map = this; + data->location = g_object_ref (loc); + + if (priv->highlight_timeout_id) { + g_source_remove (priv->highlight_timeout_id); + clock_map_place_locations (this); + } + + highlight (data); + + priv->highlight_timeout_id = g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, + 300, highlight, data, + highlight_destroy); +} + +static gboolean +clock_map_needs_refresh (ClockMap *this) +{ + ClockMapPrivate *priv = PRIVATE (this); + time_t now_t; + + time (&now_t); + + /* refresh once per minute */ + return (ABS (now_t - priv->last_refresh) >= 60); +} + +void +clock_map_update_time (ClockMap *this) +{ + + g_return_if_fail (IS_CLOCK_MAP (this)); + + if (!clock_map_needs_refresh (this)) { + return; + } + + clock_map_display (this); +} |