Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 8 - (hide annotations)
Thu Jun 25 19:08:48 2009 UTC (14 years, 10 months ago) by harbaum
File MIME type: text/plain
File size: 13556 byte(s)
Liblocation support finalized
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 harbaum 2 #ifndef NO_COPY_N_PASTE
251 harbaum 1 static void on_destroy_textview(GtkWidget *widget, gpointer data) {
252     appdata_t *appdata = (appdata_t*)data;
253     int destroy_active = FALSE;
254    
255     /* only do this if main windows hasn't already been destroyed */
256     if(!appdata->window) {
257     printf("detroy_textview: main window is gone\n");
258     return;
259     }
260    
261     if(!appdata->active_buffer)
262     printf("Destroy, but there was no active buffer!\n");
263     else {
264     if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) {
265     printf("destroying textview\n");
266    
267     if(appdata->active_buffer ==
268     gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget))) {
269     printf("This was the active buffer\n");
270     destroy_active = TRUE;
271     }
272     } else {
273     if(widget == (GtkWidget*)appdata->active_buffer) {
274     printf("This was the active html view\n");
275     destroy_active = TRUE;
276     }
277     }
278     }
279    
280     if(destroy_active) {
281     appdata->active_buffer = NULL;
282    
283     gtk_widget_set_sensitive(appdata->menu_cut, FALSE);
284     gtk_widget_set_sensitive(appdata->menu_copy, FALSE);
285     gtk_widget_set_sensitive(appdata->menu_paste, FALSE);
286     }
287     }
288 harbaum 2 #endif
289 harbaum 1
290     static void on_destroy_htmlview(GtkWidget *widget, gpointer data) {
291     http_context_t *context = (http_context_t*)data;
292    
293     printf("destroying html context\n");
294     // printf("associated view = %p\n", context->view);
295    
296     struct html_view **h = &(context->appdata->html_view);
297    
298     while(*h && (*h)->view != context->view)
299     h = &((*h)->next);
300    
301     g_assert(h);
302    
303     /* remove entry from chain */
304     void *tmp = *h;
305     *h = (*h)->next;
306     free(tmp);
307    
308     load_context_t *load_context = context->load_context;
309     while(load_context) {
310     printf("found an associated thread\n");
311    
312     load_context_t *tmp_context = load_context->next;
313     release_load_context(NULL, load_context);
314     load_context = tmp_context;
315     }
316    
317 harbaum 2 #ifndef NO_COPY_N_PASTE
318 harbaum 1 on_destroy_textview(widget, context->appdata);
319 harbaum 2 #endif
320 harbaum 1
321     /* destroy context */
322     free(data);
323     }
324    
325 harbaum 2 #ifndef NO_COPY_N_PASTE
326 harbaum 1 static gboolean focus_in(GtkWidget *widget, GdkEventFocus *event,
327     gpointer data) {
328     appdata_t *appdata = (appdata_t*)data;
329    
330     printf("focus in!\n");
331    
332     /* these buffers are read-only, thus only "copy" is enabled */
333     gtk_widget_set_sensitive(appdata->menu_cut, FALSE);
334     gtk_widget_set_sensitive(appdata->menu_copy, TRUE);
335     gtk_widget_set_sensitive(appdata->menu_paste, FALSE);
336    
337     if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) {
338     appdata->active_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
339     } else if(GTK_WIDGET_TYPE(widget) == gtk_html_get_type()) {
340     appdata->active_buffer = (GtkTextBuffer*)widget;
341     } else {
342     printf("not a text nor a html view\n");
343     }
344    
345     return FALSE;
346     }
347    
348     void html_copy_to_clipboard(appdata_t *appdata) {
349     gtk_html_copy(GTK_HTML(appdata->active_buffer));
350     }
351 harbaum 2 #endif
352 harbaum 1
353     /* panning a gtkhtml view currently doesn't work well */
354     #undef PANNABLE_HTML
355    
356     #ifdef PANNABLE_HTML
357     /* eat the button events */
358     static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
359     gpointer user_data) {
360     return TRUE;
361     }
362     #endif
363    
364     /* the cache descriptions are not valid html and need some surrounding stuff */
365     static const char *html_start = "<html><head>"
366     "<meta http-equiv=content-type content=\"text/html; charset=UTF-8\">"
367     "</head></body>";
368     static const char *html_end = "</body></html>";
369    
370     GtkWidget *html_view(appdata_t *appdata, char *text,
371     gboolean is_html, gboolean scrollwin,
372     cache_t *cache, char *anchor) {
373     GtkWidget *view;
374    
375     if(is_html) {
376     http_context_t *context = g_new0(http_context_t, 1);
377     context->appdata = appdata;
378     context->cache = cache;
379    
380     context->view = view = gtk_html_new();
381    
382     /* create a callback to load images only if a cache has been given */
383     /* so that images can be cached/stored appropriately */
384     g_signal_connect(G_OBJECT(view), "url_requested",
385     G_CALLBACK(on_request_url), context);
386    
387     GtkHTMLStream *stream = gtk_html_begin(GTK_HTML(view));
388    
389     gtk_html_write(GTK_HTML(view), stream, html_start, strlen(html_start));
390     gtk_html_write(GTK_HTML(view), stream, text, strlen(text));
391     gtk_html_write(GTK_HTML(view), stream, html_end, strlen(html_end));
392     gtk_html_end(GTK_HTML(view), stream, GTK_HTML_STREAM_OK);
393    
394     if(anchor) {
395     while(gtk_events_pending())
396     gtk_main_iteration ();
397    
398     gtk_html_jump_to_anchor(GTK_HTML(context->view), anchor);
399     }
400    
401     /* register this html view */
402     struct html_view **h = &(appdata->html_view);
403     while(*h) h = &((*h)->next);
404     *h = g_new0(struct html_view, 1);
405     (*h)->view = view;
406    
407     #ifdef PANNABLE_HTML
408     /* this causes finger scrolling to work nicely but also prevents */
409     /* copy'n paste from working correctly */
410     gtk_widget_set_sensitive(GTK_WIDGET(view), FALSE);
411    
412     g_signal_connect(G_OBJECT(view), "button-press-event",
413     G_CALLBACK(on_button_press), NULL);
414     #endif
415    
416     g_signal_connect(G_OBJECT(view), "destroy",
417     G_CALLBACK(on_destroy_htmlview), context);
418     } else {
419     GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
420     gtk_text_buffer_set_text(buffer, text, strlen(text));
421    
422 harbaum 8 #ifndef USE_HILDON_TEXT_VIEW
423 harbaum 1 view = gtk_text_view_new_with_buffer(buffer);
424     #else
425     view = hildon_text_view_new();
426     hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer);
427     #endif
428    
429     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
430     gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
431     gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
432    
433 harbaum 2 #ifndef NO_COPY_N_PASTE
434 harbaum 1 g_signal_connect(G_OBJECT(view), "destroy",
435     G_CALLBACK(on_destroy_textview), appdata);
436 harbaum 2 #endif
437 harbaum 1 }
438    
439 harbaum 2 #ifndef NO_COPY_N_PASTE
440 harbaum 1 g_signal_connect(G_OBJECT(view), "focus-in-event",
441     G_CALLBACK(focus_in), appdata);
442 harbaum 2 #endif
443 harbaum 1
444     if(scrollwin) {
445     #ifndef USE_PANNABLE_AREA
446     GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
447     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
448     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
449     gtk_container_add(GTK_CONTAINER(scrolled_window), view);
450    
451     #if 1
452     return scrolled_window;
453     #else
454     GtkWidget *fixed = gtk_fixed_new();
455     gtk_fixed_put(GTK_FIXED(fixed), scrolled_window, 0, 0);
456     GtkWidget *tbutton = gtk_toggle_button_new_with_label("sel");
457     gtk_fixed_put(GTK_FIXED(fixed), tbutton, 0, 0);
458     return fixed;
459     #endif
460     #else
461     #ifndef PANNABLE_HTML
462     if(is_html) {
463     GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
464     gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
465     GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
466     gtk_container_add(GTK_CONTAINER(scrolled_window), view);
467     return scrolled_window;
468     } else
469     #endif
470     {
471     GtkWidget *pannable_area = hildon_pannable_area_new();
472     gtk_container_add(GTK_CONTAINER(pannable_area), view);
473     return pannable_area;
474     }
475     #endif
476     }
477    
478     return view;
479     }
480