Parent Directory | Revision Log
Liblocation support finalized
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of GPXView. |
5 | * |
6 | * GPXView is free software: you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation, either version 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * GPXView is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with GPXView. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | #include "gpxview.h" |
21 | |
22 | typedef struct load_context { |
23 | int active; |
24 | GMutex *mutex; |
25 | GtkWidget *view; |
26 | char *url, *path; |
27 | GtkHTMLStream *stream; |
28 | struct load_context *next; |
29 | } load_context_t; |
30 | |
31 | typedef struct { |
32 | appdata_t *appdata; |
33 | cache_t *cache; |
34 | GtkWidget *view; |
35 | load_context_t *load_context; |
36 | } http_context_t; |
37 | |
38 | static unsigned long name_hash(const char *name) { |
39 | unsigned long val = 0; |
40 | |
41 | while(*name) { |
42 | val = (val<<8) ^ (val >> 24) ^ (*name & 0xff); |
43 | name++; |
44 | } |
45 | |
46 | return val; |
47 | } |
48 | |
49 | void release_load_context(GThread *self, load_context_t *context) { |
50 | /* this must be atomar, so acquire a lock */ |
51 | |
52 | printf("%p: freeing context at %p\n", self, context); |
53 | |
54 | g_mutex_lock(context->mutex); |
55 | |
56 | /* don't do much if the other thread still uses this */ |
57 | if(context->active) { |
58 | printf(" still active -> just close link\n"); |
59 | |
60 | /* close link to view as the thread is either done or */ |
61 | /* main wants to close the view */ |
62 | gtk_html_end(GTK_HTML(context->view), context->stream, |
63 | GTK_HTML_STREAM_OK); |
64 | |
65 | context->active = FALSE; |
66 | g_mutex_unlock(context->mutex); |
67 | return; |
68 | } |
69 | |
70 | /* ok, other thread is also gone -> we can destroy everything */ |
71 | printf(" not active -> destroy\n"); |
72 | |
73 | /* just free everything */ |
74 | g_mutex_unlock(context->mutex); |
75 | free(context->url); |
76 | free(context->path); |
77 | free(context); |
78 | } |
79 | |
80 | gpointer loader_thread(gpointer data) { |
81 | GThread *self = g_thread_self(); |
82 | |
83 | GnomeVFSResult result; |
84 | GnomeVFSHandle *handle; |
85 | char buffer[4096]; |
86 | GnomeVFSFileSize bytes_read; |
87 | |
88 | load_context_t *context = (load_context_t*)data; |
89 | |
90 | printf("%p: loader thread for %s running\n", self, context->url); |
91 | |
92 | result = gnome_vfs_open(&handle, context->url, GNOME_VFS_OPEN_READ); |
93 | if(result != GNOME_VFS_OK) { |
94 | g_print("%p: open error: %s\n", self, gnome_vfs_result_to_string(result)); |
95 | |
96 | release_load_context(self, context); |
97 | return NULL; |
98 | } |
99 | |
100 | /* try to open file for writing */ |
101 | FILE *f = fopen(context->path, "wb"); |
102 | int running = TRUE; |
103 | |
104 | do { |
105 | result = gnome_vfs_read(handle, buffer, sizeof(buffer)-1, &bytes_read); |
106 | if((result == GNOME_VFS_OK) && (bytes_read > 0)) { |
107 | |
108 | /* update local "running" variable from shared variable */ |
109 | if(running) { |
110 | g_mutex_lock(context->mutex); |
111 | running = context->active; |
112 | g_mutex_unlock(context->mutex); |
113 | } |
114 | |
115 | if(running) |
116 | gtk_html_write(GTK_HTML(context->view), |
117 | context->stream, buffer, bytes_read); |
118 | |
119 | /* and also write local file */ |
120 | if(f) fwrite(buffer, 1l, bytes_read, f); |
121 | } |
122 | } while((result == GNOME_VFS_OK) && (bytes_read > 0) && running); |
123 | |
124 | if(f) fclose(f); |
125 | |
126 | gnome_vfs_close(handle); |
127 | |
128 | /* this file is likely incomplete, remove it */ |
129 | if(!running) { |
130 | printf("%p: thread killed, removing imcomplete cached image\n", self); |
131 | remove(context->path); |
132 | release_load_context(self, context); |
133 | return NULL; |
134 | } |
135 | |
136 | printf("%p: loader thread successfully finished\n", self); |
137 | release_load_context(self, context); |
138 | return NULL; |
139 | } |
140 | |
141 | static void on_request_url(GtkHTML *html, const gchar *url, |
142 | GtkHTMLStream *stream, gpointer data) { |
143 | char buffer[4096]; |
144 | GnomeVFSFileSize bytes_read; |
145 | |
146 | http_context_t *context = (http_context_t*)data; |
147 | |
148 | if(context->cache) { |
149 | /* try to build local path */ |
150 | char *path = malloc(strlen(context->appdata->image_path)+ |
151 | strlen(context->cache->id)+ |
152 | 14); /* strlen("/xxxxxxxx.xxx\0") == 14 */ |
153 | |
154 | strcpy(path, context->appdata->image_path); |
155 | strcat(path, context->cache->id); |
156 | sprintf(path+strlen(path), "/%lx", name_hash(url)); |
157 | |
158 | /* only append extension if there's a useful one up to three chars ... */ |
159 | char *dot = strrchr(url, '.'); |
160 | if(dot) |
161 | if(strlen(dot) <= 4) |
162 | strcat(path, dot); |
163 | |
164 | printf("cache name = %s\n", path); |
165 | if(g_file_test(path, G_FILE_TEST_EXISTS)) { |
166 | printf("image file exists!\n"); |
167 | |
168 | FILE *f = fopen(path, "rb"); |
169 | |
170 | while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) != 0) { |
171 | |
172 | gtk_html_write(GTK_HTML(context->view), |
173 | stream, buffer, bytes_read); |
174 | |
175 | while (gtk_events_pending ()) |
176 | gtk_main_iteration (); |
177 | } |
178 | fclose(f); |
179 | |
180 | } else { |
181 | if(context->appdata->load_images) { |
182 | printf("image file doesn't exist, starting extra thread!\n"); |
183 | |
184 | checkdir(path); |
185 | |
186 | /* walk to end of list */ |
187 | load_context_t **load_context = &(context->load_context); |
188 | while(*load_context) |
189 | load_context = &(*load_context)->next; |
190 | |
191 | *load_context = g_new0(load_context_t, 1); |
192 | |
193 | (*load_context)->url = strdup(url); |
194 | (*load_context)->path = strdup(path); |
195 | (*load_context)->view = context->view; |
196 | (*load_context)->stream = stream; |
197 | (*load_context)->next = NULL; |
198 | (*load_context)->active = TRUE; |
199 | (*load_context)->mutex = g_mutex_new(); |
200 | |
201 | g_thread_create(loader_thread, *load_context, TRUE, NULL); |
202 | return; |
203 | } else |
204 | g_print("Image loading disabled\n"); |
205 | } |
206 | } else { |
207 | /* not a cache, maybe help, so load images from icon directory */ |
208 | |
209 | /* try to build local path */ |
210 | char *path = malloc(strlen(ICONPATH)+strlen(url)+1); |
211 | |
212 | strcpy(path, ICONPATH); |
213 | strcat(path, url); |
214 | if(!g_file_test(path, G_FILE_TEST_EXISTS)) { |
215 | strcpy(path, "./icons/"); |
216 | strcat(path, url); |
217 | } |
218 | |
219 | if(g_file_test(path, G_FILE_TEST_EXISTS)) { |
220 | FILE *f = fopen(path, "rb"); |
221 | |
222 | while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) != 0) { |
223 | gtk_html_write(GTK_HTML(context->view), |
224 | stream, buffer, bytes_read); |
225 | |
226 | while (gtk_events_pending ()) |
227 | gtk_main_iteration (); |
228 | } |
229 | fclose(f); |
230 | } |
231 | } |
232 | |
233 | gtk_html_end(GTK_HTML(context->view), stream, GTK_HTML_STREAM_OK); |
234 | } |
235 | |
236 | /* notify all open html views of zoom event */ |
237 | void html_zoom(appdata_t *appdata, gboolean in) { |
238 | struct html_view *html_view = appdata->html_view; |
239 | |
240 | while(html_view) { |
241 | printf("zoom notify view %p\n", html_view->view); |
242 | |
243 | if(in) gtk_html_zoom_in(GTK_HTML(html_view->view)); |
244 | else gtk_html_zoom_out(GTK_HTML(html_view->view)); |
245 | |
246 | html_view = html_view->next; |
247 | } |
248 | } |
249 | |
250 | #ifndef NO_COPY_N_PASTE |
251 | static void on_destroy_textview(GtkWidget *widget, gpointer data) { |
252 | appdata_t *appdata = (appdata_t*)data; |
253 | int destroy_active = FALSE; |
254 | |
255 | /* only do this if main windows hasn't already been destroyed */ |
256 | if(!appdata->window) { |
257 | printf("detroy_textview: main window is gone\n"); |
258 | return; |
259 | } |
260 | |
261 | if(!appdata->active_buffer) |
262 | printf("Destroy, but there was no active buffer!\n"); |
263 | else { |
264 | if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) { |
265 | printf("destroying textview\n"); |
266 | |
267 | if(appdata->active_buffer == |
268 | gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget))) { |
269 | printf("This was the active buffer\n"); |
270 | destroy_active = TRUE; |
271 | } |
272 | } else { |
273 | if(widget == (GtkWidget*)appdata->active_buffer) { |
274 | printf("This was the active html view\n"); |
275 | destroy_active = TRUE; |
276 | } |
277 | } |
278 | } |
279 | |
280 | if(destroy_active) { |
281 | appdata->active_buffer = NULL; |
282 | |
283 | gtk_widget_set_sensitive(appdata->menu_cut, FALSE); |
284 | gtk_widget_set_sensitive(appdata->menu_copy, FALSE); |
285 | gtk_widget_set_sensitive(appdata->menu_paste, FALSE); |
286 | } |
287 | } |
288 | #endif |
289 | |
290 | static void on_destroy_htmlview(GtkWidget *widget, gpointer data) { |
291 | http_context_t *context = (http_context_t*)data; |
292 | |
293 | printf("destroying html context\n"); |
294 | // printf("associated view = %p\n", context->view); |
295 | |
296 | struct html_view **h = &(context->appdata->html_view); |
297 | |
298 | while(*h && (*h)->view != context->view) |
299 | h = &((*h)->next); |
300 | |
301 | g_assert(h); |
302 | |
303 | /* remove entry from chain */ |
304 | void *tmp = *h; |
305 | *h = (*h)->next; |
306 | free(tmp); |
307 | |
308 | load_context_t *load_context = context->load_context; |
309 | while(load_context) { |
310 | printf("found an associated thread\n"); |
311 | |
312 | load_context_t *tmp_context = load_context->next; |
313 | release_load_context(NULL, load_context); |
314 | load_context = tmp_context; |
315 | } |
316 | |
317 | #ifndef NO_COPY_N_PASTE |
318 | on_destroy_textview(widget, context->appdata); |
319 | #endif |
320 | |
321 | /* destroy context */ |
322 | free(data); |
323 | } |
324 | |
325 | #ifndef NO_COPY_N_PASTE |
326 | static gboolean focus_in(GtkWidget *widget, GdkEventFocus *event, |
327 | gpointer data) { |
328 | appdata_t *appdata = (appdata_t*)data; |
329 | |
330 | printf("focus in!\n"); |
331 | |
332 | /* these buffers are read-only, thus only "copy" is enabled */ |
333 | gtk_widget_set_sensitive(appdata->menu_cut, FALSE); |
334 | gtk_widget_set_sensitive(appdata->menu_copy, TRUE); |
335 | gtk_widget_set_sensitive(appdata->menu_paste, FALSE); |
336 | |
337 | if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) { |
338 | appdata->active_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget)); |
339 | } else if(GTK_WIDGET_TYPE(widget) == gtk_html_get_type()) { |
340 | appdata->active_buffer = (GtkTextBuffer*)widget; |
341 | } else { |
342 | printf("not a text nor a html view\n"); |
343 | } |
344 | |
345 | return FALSE; |
346 | } |
347 | |
348 | void html_copy_to_clipboard(appdata_t *appdata) { |
349 | gtk_html_copy(GTK_HTML(appdata->active_buffer)); |
350 | } |
351 | #endif |
352 | |
353 | /* panning a gtkhtml view currently doesn't work well */ |
354 | #undef PANNABLE_HTML |
355 | |
356 | #ifdef PANNABLE_HTML |
357 | /* eat the button events */ |
358 | static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event, |
359 | gpointer user_data) { |
360 | return TRUE; |
361 | } |
362 | #endif |
363 | |
364 | /* the cache descriptions are not valid html and need some surrounding stuff */ |
365 | static const char *html_start = "<html><head>" |
366 | "<meta http-equiv=content-type content=\"text/html; charset=UTF-8\">" |
367 | "</head></body>"; |
368 | static const char *html_end = "</body></html>"; |
369 | |
370 | GtkWidget *html_view(appdata_t *appdata, char *text, |
371 | gboolean is_html, gboolean scrollwin, |
372 | cache_t *cache, char *anchor) { |
373 | GtkWidget *view; |
374 | |
375 | if(is_html) { |
376 | http_context_t *context = g_new0(http_context_t, 1); |
377 | context->appdata = appdata; |
378 | context->cache = cache; |
379 | |
380 | context->view = view = gtk_html_new(); |
381 | |
382 | /* create a callback to load images only if a cache has been given */ |
383 | /* so that images can be cached/stored appropriately */ |
384 | g_signal_connect(G_OBJECT(view), "url_requested", |
385 | G_CALLBACK(on_request_url), context); |
386 | |
387 | GtkHTMLStream *stream = gtk_html_begin(GTK_HTML(view)); |
388 | |
389 | gtk_html_write(GTK_HTML(view), stream, html_start, strlen(html_start)); |
390 | gtk_html_write(GTK_HTML(view), stream, text, strlen(text)); |
391 | gtk_html_write(GTK_HTML(view), stream, html_end, strlen(html_end)); |
392 | gtk_html_end(GTK_HTML(view), stream, GTK_HTML_STREAM_OK); |
393 | |
394 | if(anchor) { |
395 | while(gtk_events_pending()) |
396 | gtk_main_iteration (); |
397 | |
398 | gtk_html_jump_to_anchor(GTK_HTML(context->view), anchor); |
399 | } |
400 | |
401 | /* register this html view */ |
402 | struct html_view **h = &(appdata->html_view); |
403 | while(*h) h = &((*h)->next); |
404 | *h = g_new0(struct html_view, 1); |
405 | (*h)->view = view; |
406 | |
407 | #ifdef PANNABLE_HTML |
408 | /* this causes finger scrolling to work nicely but also prevents */ |
409 | /* copy'n paste from working correctly */ |
410 | gtk_widget_set_sensitive(GTK_WIDGET(view), FALSE); |
411 | |
412 | g_signal_connect(G_OBJECT(view), "button-press-event", |
413 | G_CALLBACK(on_button_press), NULL); |
414 | #endif |
415 | |
416 | g_signal_connect(G_OBJECT(view), "destroy", |
417 | G_CALLBACK(on_destroy_htmlview), context); |
418 | } else { |
419 | GtkTextBuffer *buffer = gtk_text_buffer_new(NULL); |
420 | gtk_text_buffer_set_text(buffer, text, strlen(text)); |
421 | |
422 | #ifndef USE_HILDON_TEXT_VIEW |
423 | view = gtk_text_view_new_with_buffer(buffer); |
424 | #else |
425 | view = hildon_text_view_new(); |
426 | hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer); |
427 | #endif |
428 | |
429 | gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD); |
430 | gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE); |
431 | gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE); |
432 | |
433 | #ifndef NO_COPY_N_PASTE |
434 | g_signal_connect(G_OBJECT(view), "destroy", |
435 | G_CALLBACK(on_destroy_textview), appdata); |
436 | #endif |
437 | } |
438 | |
439 | #ifndef NO_COPY_N_PASTE |
440 | g_signal_connect(G_OBJECT(view), "focus-in-event", |
441 | G_CALLBACK(focus_in), appdata); |
442 | #endif |
443 | |
444 | if(scrollwin) { |
445 | #ifndef USE_PANNABLE_AREA |
446 | GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); |
447 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), |
448 | GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
449 | gtk_container_add(GTK_CONTAINER(scrolled_window), view); |
450 | |
451 | #if 1 |
452 | return scrolled_window; |
453 | #else |
454 | GtkWidget *fixed = gtk_fixed_new(); |
455 | gtk_fixed_put(GTK_FIXED(fixed), scrolled_window, 0, 0); |
456 | GtkWidget *tbutton = gtk_toggle_button_new_with_label("sel"); |
457 | gtk_fixed_put(GTK_FIXED(fixed), tbutton, 0, 0); |
458 | return fixed; |
459 | #endif |
460 | #else |
461 | #ifndef PANNABLE_HTML |
462 | if(is_html) { |
463 | GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); |
464 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), |
465 | GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
466 | gtk_container_add(GTK_CONTAINER(scrolled_window), view); |
467 | return scrolled_window; |
468 | } else |
469 | #endif |
470 | { |
471 | GtkWidget *pannable_area = hildon_pannable_area_new(); |
472 | gtk_container_add(GTK_CONTAINER(pannable_area), view); |
473 | return pannable_area; |
474 | } |
475 | #endif |
476 | } |
477 | |
478 | return view; |
479 | } |
480 |