Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


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