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