Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 230 - (hide annotations)
Sun Dec 6 19:36:35 2009 UTC (14 years, 5 months ago) by harbaum
File MIME type: text/plain
File size: 16381 byte(s)
Tests with fremantle copy'n paste
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 harbaum 230
22     // #undef PANNABLE_HTML
23    
24     #ifdef FREMANTLE
25 harbaum 229 #include <hildon/hildon-banner.h>
26 harbaum 230 #include <hildon/hildon-note.h>
27     #endif
28 harbaum 1
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 harbaum 230 #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 harbaum 1 static void on_request_url(GtkHTML *html, const gchar *url,
176     GtkHTMLStream *stream, gpointer data) {
177     char buffer[4096];
178     GnomeVFSFileSize bytes_read;
179 harbaum 230
180 harbaum 1 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 harbaum 12 printf("image file doesn't exist, starting extra thread!\n");
216 harbaum 1
217 harbaum 12 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 harbaum 1 }
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 harbaum 2 #ifndef NO_COPY_N_PASTE
282 harbaum 1 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 harbaum 2 #endif
320 harbaum 1
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 harbaum 2 #ifndef NO_COPY_N_PASTE
349 harbaum 1 on_destroy_textview(widget, context->appdata);
350 harbaum 2 #endif
351 harbaum 1
352     /* destroy context */
353     free(data);
354     }
355    
356 harbaum 2 #ifndef NO_COPY_N_PASTE
357 harbaum 1 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 harbaum 2 #endif
383 harbaum 1
384 harbaum 230 #ifdef FREMANTLE
385 harbaum 229 static void
386     tap_and_hold_cb (GtkWidget *widget, gpointer user_data) {
387     appdata_t *appdata = (appdata_t*)user_data;
388    
389 harbaum 230 /* 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    
395     hildon_banner_show_information(GTK_WIDGET(appdata->window),
396     NULL, state?"Cursor disabled":"Cursor enabled");
397    
398     return;
399     }
400    
401     GtkWidget *parent = widget->parent;
402    
403     /* check if the parent actually is a hildonpannablearea */
404     if(GTK_WIDGET_TYPE(parent) != HILDON_TYPE_PANNABLE_AREA) {
405     printf("parent is not pannable area, ignoring event\n");
406     return;
407     }
408    
409     /* lets assume parent is a pannable area ... */
410    
411     hildon_banner_show_information(GTK_WIDGET(appdata->window),
412     NULL, "Copy'n paste is not working in maemo5");
413    
414     #if 0
415     gboolean enabled = FALSE;
416     g_object_get(parent, "enabled", &enabled, NULL);
417     enabled = !enabled;
418     g_object_set(parent, "enabled", enabled, NULL);
419    
420     hildon_banner_show_information(GTK_WIDGET(appdata->window),
421     NULL, enabled?"enabled":"disabled");
422     #endif
423 harbaum 1 }
424     #endif
425    
426     /* the cache descriptions are not valid html and need some surrounding stuff */
427     static const char *html_start = "<html><head>"
428     "<meta http-equiv=content-type content=\"text/html; charset=UTF-8\">"
429     "</head></body>";
430     static const char *html_end = "</body></html>";
431    
432     GtkWidget *html_view(appdata_t *appdata, char *text,
433 harbaum 140 html_mode_t mode, gboolean scrollwin,
434 harbaum 1 cache_t *cache, char *anchor) {
435     GtkWidget *view;
436    
437 harbaum 140 if(mode == HTML_HTML) {
438 harbaum 1 http_context_t *context = g_new0(http_context_t, 1);
439     context->appdata = appdata;
440     context->cache = cache;
441    
442     context->view = view = gtk_html_new();
443    
444 harbaum 230 #ifndef PANNABLE_HTML
445     // gtk_html_set_auto_panning(GTK_HTML(view), TRUE);
446     #else
447     gtk_html_set_auto_panning(GTK_HTML(view), FALSE);
448     #endif
449    
450     #ifdef FREMANTLE
451     /* allow selection does not allow anything, but disables auto panning ... */
452     /* even worse: frequently causes the device to reboot */
453     // gtk_html_allow_selection(GTK_HTML(view), TRUE);
454     #endif
455    
456 harbaum 1 /* create a callback to load images only if a cache has been given */
457     /* so that images can be cached/stored appropriately */
458     g_signal_connect(G_OBJECT(view), "url_requested",
459     G_CALLBACK(on_request_url), context);
460    
461 harbaum 230 #ifdef ENABLE_BROWSER_INTERFACE
462     g_signal_connect(G_OBJECT(view), "link_clicked",
463     G_CALLBACK(on_link_clicked), context->appdata);
464     #endif
465    
466 harbaum 1 GtkHTMLStream *stream = gtk_html_begin(GTK_HTML(view));
467    
468     gtk_html_write(GTK_HTML(view), stream, html_start, strlen(html_start));
469     gtk_html_write(GTK_HTML(view), stream, text, strlen(text));
470     gtk_html_write(GTK_HTML(view), stream, html_end, strlen(html_end));
471     gtk_html_end(GTK_HTML(view), stream, GTK_HTML_STREAM_OK);
472    
473     if(anchor) {
474     while(gtk_events_pending())
475     gtk_main_iteration ();
476    
477     gtk_html_jump_to_anchor(GTK_HTML(context->view), anchor);
478     }
479    
480     /* register this html view */
481     struct html_view **h = &(appdata->html_view);
482     while(*h) h = &((*h)->next);
483     *h = g_new0(struct html_view, 1);
484     (*h)->view = view;
485    
486 harbaum 230 #ifdef FREMANTLE
487 harbaum 229 gtk_widget_tap_and_hold_setup(GTK_WIDGET(view), NULL, NULL, 0);
488 harbaum 230 g_signal_connect(G_OBJECT(view), "tap-and-hold",
489     G_CALLBACK(tap_and_hold_cb), appdata);
490 harbaum 1 #endif
491    
492     g_signal_connect(G_OBJECT(view), "destroy",
493     G_CALLBACK(on_destroy_htmlview), context);
494     } else {
495     GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
496     gtk_text_buffer_set_text(buffer, text, strlen(text));
497    
498 harbaum 8 #ifndef USE_HILDON_TEXT_VIEW
499 harbaum 1 view = gtk_text_view_new_with_buffer(buffer);
500     #else
501     view = hildon_text_view_new();
502     hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer);
503 harbaum 229
504     gtk_widget_tap_and_hold_setup(GTK_WIDGET(view), NULL, NULL, 0);
505 harbaum 230 g_signal_connect(G_OBJECT(view), "tap-and-hold",
506     G_CALLBACK(tap_and_hold_cb), appdata);
507 harbaum 1 #endif
508    
509     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
510     gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
511     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
512    
513 harbaum 163 /* make this look nicer in fremantle and not just black-on-white */
514     /* just use the default style */
515 harbaum 226 #ifdef USE_STACKABLE_WINDOW
516     /* in fremantle this is really tricky and we need to inherit the */
517     /* style from the topmost window in the stack */
518     HildonWindowStack *stack = hildon_window_stack_get_default();
519     GList *list = hildon_window_stack_get_windows(stack);
520     gtk_widget_set_style(view, GTK_WIDGET(list->data)->style);
521     g_list_free(list);
522 harbaum 163 #else
523 harbaum 226 gtk_widget_set_style(view, GTK_WIDGET(appdata->window)->style);
524 harbaum 163 #endif
525    
526 harbaum 2 #ifndef NO_COPY_N_PASTE
527 harbaum 1 g_signal_connect(G_OBJECT(view), "destroy",
528     G_CALLBACK(on_destroy_textview), appdata);
529 harbaum 2 #endif
530 harbaum 1 }
531    
532 harbaum 2 #ifndef NO_COPY_N_PASTE
533 harbaum 1 g_signal_connect(G_OBJECT(view), "focus-in-event",
534     G_CALLBACK(focus_in), appdata);
535 harbaum 2 #endif
536 harbaum 1
537     if(scrollwin) {
538     #ifndef USE_PANNABLE_AREA
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    
544     return scrolled_window;
545     #else
546     #ifndef PANNABLE_HTML
547 harbaum 140 if(mode == HTML_HTML) {
548 harbaum 1 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
549     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
550     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
551     gtk_container_add(GTK_CONTAINER(scrolled_window), view);
552     return scrolled_window;
553     } else
554     #endif
555     {
556     GtkWidget *pannable_area = hildon_pannable_area_new();
557     gtk_container_add(GTK_CONTAINER(pannable_area), view);
558     return pannable_area;
559     }
560     #endif
561     }
562    
563     return view;
564     }
565