Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 12 - (show annotations)
Fri Jun 26 20:07:33 2009 UTC (14 years, 10 months ago) by harbaum
File MIME type: text/plain
File size: 13560 byte(s)
Ubuntu compile fixes, removed image loading option
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 printf("image file doesn't exist, starting extra thread!\n");
182
183 checkdir(path);
184
185 /* walk to end of list */
186 load_context_t **load_context = &(context->load_context);
187 while(*load_context)
188 load_context = &(*load_context)->next;
189
190 *load_context = g_new0(load_context_t, 1);
191
192 (*load_context)->url = strdup(url);
193 (*load_context)->path = strdup(path);
194 (*load_context)->view = context->view;
195 (*load_context)->stream = stream;
196 (*load_context)->next = NULL;
197 (*load_context)->active = TRUE;
198 (*load_context)->mutex = g_mutex_new();
199
200 g_thread_create(loader_thread, *load_context, TRUE, NULL);
201 return;
202 }
203 } else {
204 /* not a cache, maybe help, so load images from icon directory */
205
206 /* try to build local path */
207 char *path = malloc(strlen(ICONPATH)+strlen(url)+1);
208
209 strcpy(path, ICONPATH);
210 strcat(path, url);
211 if(!g_file_test(path, G_FILE_TEST_EXISTS)) {
212 strcpy(path, "./icons/");
213 strcat(path, url);
214 }
215
216 if(g_file_test(path, G_FILE_TEST_EXISTS)) {
217 FILE *f = fopen(path, "rb");
218
219 while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) != 0) {
220 gtk_html_write(GTK_HTML(context->view),
221 stream, buffer, bytes_read);
222
223 while (gtk_events_pending ())
224 gtk_main_iteration ();
225 }
226 fclose(f);
227 }
228 }
229
230 gtk_html_end(GTK_HTML(context->view), stream, GTK_HTML_STREAM_OK);
231 }
232
233 /* notify all open html views of zoom event */
234 void html_zoom(appdata_t *appdata, gboolean in) {
235 struct html_view *html_view = appdata->html_view;
236
237 while(html_view) {
238 printf("zoom notify view %p\n", html_view->view);
239
240 if(in) gtk_html_zoom_in(GTK_HTML(html_view->view));
241 else gtk_html_zoom_out(GTK_HTML(html_view->view));
242
243 html_view = html_view->next;
244 }
245 }
246
247 #ifndef NO_COPY_N_PASTE
248 static void on_destroy_textview(GtkWidget *widget, gpointer data) {
249 appdata_t *appdata = (appdata_t*)data;
250 int destroy_active = FALSE;
251
252 /* only do this if main windows hasn't already been destroyed */
253 if(!appdata->window) {
254 printf("detroy_textview: main window is gone\n");
255 return;
256 }
257
258 if(!appdata->active_buffer)
259 printf("Destroy, but there was no active buffer!\n");
260 else {
261 if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) {
262 printf("destroying textview\n");
263
264 if(appdata->active_buffer ==
265 gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget))) {
266 printf("This was the active buffer\n");
267 destroy_active = TRUE;
268 }
269 } else {
270 if(widget == (GtkWidget*)appdata->active_buffer) {
271 printf("This was the active html view\n");
272 destroy_active = TRUE;
273 }
274 }
275 }
276
277 if(destroy_active) {
278 appdata->active_buffer = NULL;
279
280 gtk_widget_set_sensitive(appdata->menu_cut, FALSE);
281 gtk_widget_set_sensitive(appdata->menu_copy, FALSE);
282 gtk_widget_set_sensitive(appdata->menu_paste, FALSE);
283 }
284 }
285 #endif
286
287 static void on_destroy_htmlview(GtkWidget *widget, gpointer data) {
288 http_context_t *context = (http_context_t*)data;
289
290 printf("destroying html context\n");
291 // printf("associated view = %p\n", context->view);
292
293 struct html_view **h = &(context->appdata->html_view);
294
295 while(*h && (*h)->view != context->view)
296 h = &((*h)->next);
297
298 g_assert(h);
299
300 /* remove entry from chain */
301 void *tmp = *h;
302 *h = (*h)->next;
303 free(tmp);
304
305 load_context_t *load_context = context->load_context;
306 while(load_context) {
307 printf("found an associated thread\n");
308
309 load_context_t *tmp_context = load_context->next;
310 release_load_context(NULL, load_context);
311 load_context = tmp_context;
312 }
313
314 #ifndef NO_COPY_N_PASTE
315 on_destroy_textview(widget, context->appdata);
316 #endif
317
318 /* destroy context */
319 free(data);
320 }
321
322 #ifndef NO_COPY_N_PASTE
323 static gboolean focus_in(GtkWidget *widget, GdkEventFocus *event,
324 gpointer data) {
325 appdata_t *appdata = (appdata_t*)data;
326
327 printf("focus in!\n");
328
329 /* these buffers are read-only, thus only "copy" is enabled */
330 gtk_widget_set_sensitive(appdata->menu_cut, FALSE);
331 gtk_widget_set_sensitive(appdata->menu_copy, TRUE);
332 gtk_widget_set_sensitive(appdata->menu_paste, FALSE);
333
334 if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) {
335 appdata->active_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
336 } else if(GTK_WIDGET_TYPE(widget) == gtk_html_get_type()) {
337 appdata->active_buffer = (GtkTextBuffer*)widget;
338 } else {
339 printf("not a text nor a html view\n");
340 }
341
342 return FALSE;
343 }
344
345 void html_copy_to_clipboard(appdata_t *appdata) {
346 gtk_html_copy(GTK_HTML(appdata->active_buffer));
347 }
348 #endif
349
350 /* panning a gtkhtml view currently doesn't work well */
351 #define PANNABLE_HTML
352
353 #ifdef PANNABLE_HTML
354 /* eat the button events */
355 static gboolean on_button_press(GtkWidget *widget, GdkEventButton *event,
356 gpointer user_data) {
357 return TRUE;
358 }
359 #endif
360
361 /* the cache descriptions are not valid html and need some surrounding stuff */
362 static const char *html_start = "<html><head>"
363 "<meta http-equiv=content-type content=\"text/html; charset=UTF-8\">"
364 "</head></body>";
365 static const char *html_end = "</body></html>";
366
367 GtkWidget *html_view(appdata_t *appdata, char *text,
368 gboolean is_html, gboolean scrollwin,
369 cache_t *cache, char *anchor) {
370 GtkWidget *view;
371
372 if(is_html) {
373 http_context_t *context = g_new0(http_context_t, 1);
374 context->appdata = appdata;
375 context->cache = cache;
376
377 context->view = view = gtk_html_new();
378
379 /* create a callback to load images only if a cache has been given */
380 /* so that images can be cached/stored appropriately */
381 g_signal_connect(G_OBJECT(view), "url_requested",
382 G_CALLBACK(on_request_url), context);
383
384 GtkHTMLStream *stream = gtk_html_begin(GTK_HTML(view));
385
386 gtk_html_write(GTK_HTML(view), stream, html_start, strlen(html_start));
387 gtk_html_write(GTK_HTML(view), stream, text, strlen(text));
388 gtk_html_write(GTK_HTML(view), stream, html_end, strlen(html_end));
389 gtk_html_end(GTK_HTML(view), stream, GTK_HTML_STREAM_OK);
390
391 if(anchor) {
392 while(gtk_events_pending())
393 gtk_main_iteration ();
394
395 gtk_html_jump_to_anchor(GTK_HTML(context->view), anchor);
396 }
397
398 /* register this html view */
399 struct html_view **h = &(appdata->html_view);
400 while(*h) h = &((*h)->next);
401 *h = g_new0(struct html_view, 1);
402 (*h)->view = view;
403
404 #ifdef PANNABLE_HTML
405 /* this causes finger scrolling to work nicely but also prevents */
406 /* copy'n paste from working correctly */
407 gtk_widget_set_sensitive(GTK_WIDGET(view), FALSE);
408
409 g_signal_connect(G_OBJECT(view), "button-press-event",
410 G_CALLBACK(on_button_press), NULL);
411 #endif
412
413 g_signal_connect(G_OBJECT(view), "destroy",
414 G_CALLBACK(on_destroy_htmlview), context);
415 } else {
416 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
417 gtk_text_buffer_set_text(buffer, text, strlen(text));
418
419 #ifndef USE_HILDON_TEXT_VIEW
420 view = gtk_text_view_new_with_buffer(buffer);
421 #else
422 view = hildon_text_view_new();
423 hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer);
424 #endif
425
426 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
427 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
428 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
429
430 #ifndef NO_COPY_N_PASTE
431 g_signal_connect(G_OBJECT(view), "destroy",
432 G_CALLBACK(on_destroy_textview), appdata);
433 #endif
434 }
435
436 #ifndef NO_COPY_N_PASTE
437 g_signal_connect(G_OBJECT(view), "focus-in-event",
438 G_CALLBACK(focus_in), appdata);
439 #endif
440
441 if(scrollwin) {
442 #ifndef USE_PANNABLE_AREA
443 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
444 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
445 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
446 gtk_container_add(GTK_CONTAINER(scrolled_window), view);
447
448 #if 1
449 return scrolled_window;
450 #else
451 GtkWidget *fixed = gtk_fixed_new();
452 gtk_fixed_put(GTK_FIXED(fixed), scrolled_window, 0, 0);
453 GtkWidget *tbutton = gtk_toggle_button_new_with_label("sel");
454 gtk_fixed_put(GTK_FIXED(fixed), tbutton, 0, 0);
455 return fixed;
456 #endif
457 #else
458 #ifndef PANNABLE_HTML
459 if(is_html) {
460 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
461 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
462 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
463 gtk_container_add(GTK_CONTAINER(scrolled_window), view);
464 return scrolled_window;
465 } else
466 #endif
467 {
468 GtkWidget *pannable_area = hildon_pannable_area_new();
469 gtk_container_add(GTK_CONTAINER(pannable_area), view);
470 return pannable_area;
471 }
472 #endif
473 }
474
475 return view;
476 }
477