Parent Directory | Revision Log
Liblocation support finalized
1 | harbaum | 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 | harbaum | 2 | #ifndef NO_COPY_N_PASTE |
251 | harbaum | 1 | 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 | harbaum | 2 | #endif |
289 | harbaum | 1 | |
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 | harbaum | 2 | #ifndef NO_COPY_N_PASTE |
318 | harbaum | 1 | on_destroy_textview(widget, context->appdata); |
319 | harbaum | 2 | #endif |
320 | harbaum | 1 | |
321 | /* destroy context */ | ||
322 | free(data); | ||
323 | } | ||
324 | |||
325 | harbaum | 2 | #ifndef NO_COPY_N_PASTE |
326 | harbaum | 1 | 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 | harbaum | 2 | #endif |
352 | harbaum | 1 | |
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 | harbaum | 8 | #ifndef USE_HILDON_TEXT_VIEW |
423 | harbaum | 1 | 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 | harbaum | 2 | #ifndef NO_COPY_N_PASTE |
434 | harbaum | 1 | g_signal_connect(G_OBJECT(view), "destroy", |
435 | G_CALLBACK(on_destroy_textview), appdata); | ||
436 | harbaum | 2 | #endif |
437 | harbaum | 1 | } |
438 | |||
439 | harbaum | 2 | #ifndef NO_COPY_N_PASTE |
440 | harbaum | 1 | g_signal_connect(G_OBJECT(view), "focus-in-event", |
441 | G_CALLBACK(focus_in), appdata); | ||
442 | harbaum | 2 | #endif |
443 | harbaum | 1 | |
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 |