Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


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