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