Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 294 - (show annotations)
Wed Aug 18 18:24:19 2010 UTC (13 years, 8 months ago) by harbaum
File MIME type: text/plain
File size: 16137 byte(s)
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