1
/*
2
 * Copyright (C) 2009 Emmanuel Rodriguez <emmanuel.rodriguez@gmail.com>
3
 *
4
 * This library is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * This library is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12
 * Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with this library; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
 */
18
19
#include <champlain/champlain.h>
20
#include <libsoup/soup.h>
21
#include <gdk-pixbuf/gdk-pixbuf.h>
22
23
/* The data needed for constructing a marker */
24
typedef struct
25
{
26
  ChamplainMarkerLayer *layer;
27
  gdouble latitude;
28
  gdouble longitude;
29
} MarkerData;
30
31
/**
32
 * Returns a GdkPixbuf from a given SoupMessage. This function assumes that the
33
 * message has completed successfully.
34
 * If there's an error building the GdkPixbuf the function will return NULL and
35
 * set error accordingly.
36
 *
37
 * The GdkPixbuf has to be freed with g_object_unref.
38
 */
39
static GdkPixbuf *
40
pixbuf_new_from_message (SoupMessage *message,
41
    GError **error)
42
{
43
  const gchar *mime_type = NULL;
44
  GdkPixbufLoader *loader = NULL;
45
  GdkPixbuf *pixbuf = NULL;
46
  gboolean pixbuf_is_open = FALSE;
47
48
  *error = NULL;
49
50
  /*  Use a pixbuf loader that can load images of the same mime-type as the
51
      message.
52
   */
53
  mime_type = soup_message_headers_get (message->response_headers,
54
        "Content-Type");
55
  loader = gdk_pixbuf_loader_new_with_mime_type (mime_type, error);
56
  if (loader != NULL)
57
    pixbuf_is_open = TRUE;
58
  if (*error != NULL)
59
    goto cleanup;
60
61
62
  gdk_pixbuf_loader_write (
63
      loader,
64
      (guchar *) message->response_body->data,
65
      message->response_body->length,
66
      error);
67
  if (*error != NULL)
68
    goto cleanup;
69
70
  gdk_pixbuf_loader_close (loader, error);
71
  pixbuf_is_open = FALSE;
72
  if (*error != NULL)
73
    goto cleanup;
74
75
  pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
76
  if (pixbuf == NULL)
77
    goto cleanup;
78
  g_object_ref (G_OBJECT (pixbuf));
79
80
cleanup:
81
  if (pixbuf_is_open)
82
    gdk_pixbuf_loader_close (loader, NULL);
83
84
  if (loader != NULL)
85
    g_object_unref (G_OBJECT (loader));
86
87
  return pixbuf;
88
}
89
90
91
/**
92
 * Transforms a GdkPixbuf into a ClutterTexture.
93
 * If there's an error building the ClutterActor (the texture) the function
94
 * will return NULL and set error accordingly.
95
 *
96
 * If you are using ClutterGtk, you can also use gtk_clutter_texture_set_from_pixbuf
97
 * instead of cluter_texture_set_from_rgb_data.
98
 *
99
 * The ClutterActor has to be freed with clutter_actor_destroy.
100
 */
101
static ClutterActor *
102
texture_new_from_pixbuf (GdkPixbuf *pixbuf, GError **error)
103
{
104
  ClutterActor *texture = NULL;
105
  const guchar *data;
106
  gboolean has_alpha, success;
107
  int width, height, rowstride;
108
  ClutterTextureFlags flags = 0;
109
110
  *error = NULL;
111
112
  data = gdk_pixbuf_get_pixels (pixbuf);
113
  width = gdk_pixbuf_get_width (pixbuf);
114
  height = gdk_pixbuf_get_height (pixbuf);
115
  has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
116
  rowstride = gdk_pixbuf_get_rowstride (pixbuf);
117
118
  texture = clutter_texture_new ();
119
  success = clutter_texture_set_from_rgb_data (CLUTTER_TEXTURE (texture),
120
        data,
121
        has_alpha,
122
        width,
123
        height,
124
        rowstride,
125
        (has_alpha ? 4 : 3),
126
        flags,
127
        error);
128
129
  if (!success)
130
    {
131
      clutter_actor_destroy (CLUTTER_ACTOR (texture));
132
      texture = NULL;
133
    }
134
135
  return texture;
136
}
137
138
139
/**
140
 * Called when an image has been downloaded. This callback will transform the
141
 * image data (binary chunk sent by the remote web server) into a valid Clutter
142
 * actor (a texture) and will use this as the source image for a new marker.
143
 * The marker will then be added to an existing layer.
144
 *
145
 * This callback expects the parameter data to be a valid ChamplainMarkerLayer.
146
 */
147
static void
148
image_downloaded_cb (SoupSession *session,
149
    SoupMessage *message,
150
    gpointer data)
151
{
152
  MarkerData *marker_data = NULL;
153
  SoupURI *uri = NULL;
154
  char *url = NULL;
155
  GError *error = NULL;
156
  GdkPixbuf *pixbuf = NULL;
157
  ClutterActor *texture = NULL;
158
  ClutterActor *marker = NULL;
159
160
  if (data == NULL)
161
    goto cleanup;
162
  marker_data = (MarkerData *) data;
163
164
  /* Deal only with finished messages */
165
  uri = soup_message_get_uri (message);
166
  url = soup_uri_to_string (uri, FALSE);
167
  if (!SOUP_STATUS_IS_SUCCESSFUL (message->status_code))
168
    {
169
      g_print ("Download of %s failed with error code %d\n", url,
170
          message->status_code);
171
      goto cleanup;
172
    }
173
174
  pixbuf = pixbuf_new_from_message (message, &error);
175
  if (error != NULL)
176
    {
177
      g_print ("Failed to convert %s into an image: %s\n", url, error->message);
178
      goto cleanup;
179
    }
180
181
  /* Then transform the pixbuf into a texture */
182
  texture = texture_new_from_pixbuf (pixbuf, &error);
183
  if (error != NULL)
184
    {
185
      g_print ("Failed to convert %s into a texture: %s\n", url,
186
          error->message);
187
      goto cleanup;
188
    }
189
190
  /* Finally create a marker with the texture */
191
  marker = champlain_label_new_with_image (texture);
192
  texture = NULL;
193
  champlain_location_set_location (CHAMPLAIN_LOCATION (marker),
194
      marker_data->latitude, marker_data->longitude);
195
  champlain_marker_layer_add_marker (marker_data->layer, CHAMPLAIN_MARKER (marker));
196
197
cleanup:
198
  if (marker_data)
199
    g_object_unref (marker_data->layer);
200
  g_slice_free (MarkerData, marker_data);
201
  g_free (url);
202
203
  if (error != NULL)
204
    g_error_free (error);
205
206
  if (pixbuf != NULL)
207
    g_object_unref (G_OBJECT (pixbuf));
208
209
  if (texture != NULL)
210
    clutter_actor_destroy (CLUTTER_ACTOR (texture));
211
}
212
213
214
/**
215
 * Creates a marker at the given position with an image that's downloaded from
216
 * the given URL.
217
 *
218
 */
219
static void
220
create_marker_from_url (ChamplainMarkerLayer *layer,
221
    SoupSession *session,
222
    gdouble latitude,
223
    gdouble longitude,
224
    const gchar *url)
225
{
226
  SoupMessage *message;
227
  MarkerData *data;
228
229
  data = g_slice_new (MarkerData);
230
  data->layer = g_object_ref (layer);
231
  data->latitude = latitude;
232
  data->longitude = longitude;
233
234
  message = soup_message_new ("GET", url);
235
  soup_session_queue_message (session, message, image_downloaded_cb, data);
236
}
237
238
239
int
240
main (int argc, char *argv[])
241
{
242
  ClutterActor *view, *stage;
243
  ChamplainMarkerLayer *layer;
244
  SoupSession *session;
245
246
  if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
247
    return 1;
248
249
  stage = clutter_stage_new ();
250
  clutter_actor_set_size (stage, 800, 600);
251
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
252
253
  /* Create the map view */
254
  view = champlain_view_new ();
255
  clutter_actor_set_size (CLUTTER_ACTOR (view), 800, 600);
256
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), view);
257
258
  /* Create the markers and marker layer */
259
  layer = champlain_marker_layer_new_full (CHAMPLAIN_SELECTION_SINGLE);
260
  champlain_view_add_layer (CHAMPLAIN_VIEW (view), CHAMPLAIN_LAYER (layer));
261
  session = soup_session_async_new ();
262
  create_marker_from_url (layer, session, 48.218611, 17.146397,
263
      "http://hexten.net/cpan-faces/potyl.jpg");
264
  create_marker_from_url (layer, session, 48.21066, 16.31476,
265
      "http://hexten.net/cpan-faces/jkutej.jpg");
266
  create_marker_from_url (layer, session, 48.14838, 17.10791,
267
      "http://bratislava.pm.org/images/whoiswho/jnthn.jpg");
268
269
  /* Finish initialising the map view */
270
  g_object_set (G_OBJECT (view), "zoom-level", 10,
271
      "kinetic-mode", TRUE, NULL);
272
  champlain_view_center_on (CHAMPLAIN_VIEW (view), 48.22, 16.8);
273
274
  clutter_actor_show_all (stage);
275
  clutter_main ();
276
277
  g_object_unref (session);
278
279
  return 0;
280
}