Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 229 - (show annotations)
Fri Dec 4 19:58:26 2009 UTC (14 years, 5 months ago) by harbaum
File MIME type: text/plain
File size: 14215 byte(s)
New about dialog
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 #include <hildon/hildon-banner.h>
22
23 typedef struct load_context {
24 int active;
25 GMutex *mutex;
26 GtkWidget *view;
27 char *url, *path;
28 GtkHTMLStream *stream;
29 struct load_context *next;
30 } load_context_t;
31
32 typedef struct {
33 appdata_t *appdata;
34 cache_t *cache;
35 GtkWidget *view;
36 load_context_t *load_context;
37 } http_context_t;
38
39 static unsigned long name_hash(const char *name) {
40 unsigned long val = 0;
41
42 while(*name) {
43 val = (val<<8) ^ (val >> 24) ^ (*name & 0xff);
44 name++;
45 }
46
47 return val;
48 }
49
50 void release_load_context(GThread *self, load_context_t *context) {
51 /* this must be atomar, so acquire a lock */
52
53 printf("%p: freeing context at %p\n", self, context);
54
55 g_mutex_lock(context->mutex);
56
57 /* don't do much if the other thread still uses this */
58 if(context->active) {
59 printf(" still active -> just close link\n");
60
61 /* close link to view as the thread is either done or */
62 /* main wants to close the view */
63 gtk_html_end(GTK_HTML(context->view), context->stream,
64 GTK_HTML_STREAM_OK);
65
66 context->active = FALSE;
67 g_mutex_unlock(context->mutex);
68 return;
69 }
70
71 /* ok, other thread is also gone -> we can destroy everything */
72 printf(" not active -> destroy\n");
73
74 /* just free everything */
75 g_mutex_unlock(context->mutex);
76 free(context->url);
77 free(context->path);
78 free(context);
79 }
80
81 gpointer loader_thread(gpointer data) {
82 GThread *self = g_thread_self();
83
84 GnomeVFSResult result;
85 GnomeVFSHandle *handle;
86 char buffer[4096];
87 GnomeVFSFileSize bytes_read;
88
89 load_context_t *context = (load_context_t*)data;
90
91 printf("%p: loader thread for %s running\n", self, context->url);
92
93 result = gnome_vfs_open(&handle, context->url, GNOME_VFS_OPEN_READ);
94 if(result != GNOME_VFS_OK) {
95 g_print("%p: open error: %s\n", self, gnome_vfs_result_to_string(result));
96
97 release_load_context(self, context);
98 return NULL;
99 }
100
101 /* try to open file for writing */
102 FILE *f = fopen(context->path, "wb");
103 int running = TRUE;
104
105 do {
106 result = gnome_vfs_read(handle, buffer, sizeof(buffer)-1, &bytes_read);
107 if((result == GNOME_VFS_OK) && (bytes_read > 0)) {
108
109 /* update local "running" variable from shared variable */
110 if(running) {
111 g_mutex_lock(context->mutex);
112 running = context->active;
113 g_mutex_unlock(context->mutex);
114 }
115
116 if(running)
117 gtk_html_write(GTK_HTML(context->view),
118 context->stream, buffer, bytes_read);
119
120 /* and also write local file */
121 if(f) fwrite(buffer, 1l, bytes_read, f);
122 }
123 } while((result == GNOME_VFS_OK) && (bytes_read > 0) && running);
124
125 if(f) fclose(f);
126
127 gnome_vfs_close(handle);
128
129 /* this file is likely incomplete, remove it */
130 if(!running) {
131 printf("%p: thread killed, removing imcomplete cached image\n", self);
132 remove(context->path);
133 release_load_context(self, context);
134 return NULL;
135 }
136
137 printf("%p: loader thread successfully finished\n", self);
138 release_load_context(self, context);
139 return NULL;
140 }
141
142 static void on_request_url(GtkHTML *html, const gchar *url,
143 GtkHTMLStream *stream, gpointer data) {
144 char buffer[4096];
145 GnomeVFSFileSize bytes_read;
146
147 http_context_t *context = (http_context_t*)data;
148
149 if(context->cache) {
150 /* try to build local path */
151 char *path = malloc(strlen(context->appdata->image_path)+
152 strlen(context->cache->id)+
153 14); /* strlen("/xxxxxxxx.xxx\0") == 14 */
154
155 strcpy(path, context->appdata->image_path);
156 strcat(path, context->cache->id);
157 sprintf(path+strlen(path), "/%lx", name_hash(url));
158
159 /* only append extension if there's a useful one up to three chars ... */
160 char *dot = strrchr(url, '.');
161 if(dot)
162 if(strlen(dot) <= 4)
163 strcat(path, dot);
164
165 printf("cache name = %s\n", path);
166 if(g_file_test(path, G_FILE_TEST_EXISTS)) {
167 printf("image file exists!\n");
168
169 FILE *f = fopen(path, "rb");
170
171 while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) != 0) {
172
173 gtk_html_write(GTK_HTML(context->view),
174 stream, buffer, bytes_read);
175
176 while (gtk_events_pending ())
177 gtk_main_iteration ();
178 }
179 fclose(f);
180
181 } else {
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 }
204 } else {
205 /* not a cache, maybe help, so load images from icon directory */
206
207 /* try to build local path */
208 char *path = malloc(strlen(ICONPATH)+strlen(url)+1);
209
210 strcpy(path, ICONPATH);
211 strcat(path, url);
212 if(!g_file_test(path, G_FILE_TEST_EXISTS)) {
213 strcpy(path, "./icons/");
214 strcat(path, url);
215 }
216
217 if(g_file_test(path, G_FILE_TEST_EXISTS)) {
218 FILE *f = fopen(path, "rb");
219
220 while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) != 0) {
221 gtk_html_write(GTK_HTML(context->view),
222 stream, buffer, bytes_read);
223
224 while (gtk_events_pending ())
225 gtk_main_iteration ();
226 }
227 fclose(f);
228 }
229 }
230
231 gtk_html_end(GTK_HTML(context->view), stream, GTK_HTML_STREAM_OK);
232 }
233
234 /* notify all open html views of zoom event */
235 void html_zoom(appdata_t *appdata, gboolean in) {
236 struct html_view *html_view = appdata->html_view;
237
238 while(html_view) {
239 printf("zoom notify view %p\n", html_view->view);
240
241 if(in) gtk_html_zoom_in(GTK_HTML(html_view->view));
242 else gtk_html_zoom_out(GTK_HTML(html_view->view));
243
244 html_view = html_view->next;
245 }
246 }
247
248 #ifndef NO_COPY_N_PASTE
249 static void on_destroy_textview(GtkWidget *widget, gpointer data) {
250 appdata_t *appdata = (appdata_t*)data;
251 int destroy_active = FALSE;
252
253 /* only do this if main windows hasn't already been destroyed */
254 if(!appdata->window) {
255 printf("detroy_textview: main window is gone\n");
256 return;
257 }
258
259 if(!appdata->active_buffer)
260 printf("Destroy, but there was no active buffer!\n");
261 else {
262 if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) {
263 printf("destroying textview\n");
264
265 if(appdata->active_buffer ==
266 gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget))) {
267 printf("This was the active buffer\n");
268 destroy_active = TRUE;
269 }
270 } else {
271 if(widget == (GtkWidget*)appdata->active_buffer) {
272 printf("This was the active html view\n");
273 destroy_active = TRUE;
274 }
275 }
276 }
277
278 if(destroy_active) {
279 appdata->active_buffer = NULL;
280
281 gtk_widget_set_sensitive(appdata->menu_cut, FALSE);
282 gtk_widget_set_sensitive(appdata->menu_copy, FALSE);
283 gtk_widget_set_sensitive(appdata->menu_paste, FALSE);
284 }
285 }
286 #endif
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 #ifndef NO_COPY_N_PASTE
316 on_destroy_textview(widget, context->appdata);
317 #endif
318
319 /* destroy context */
320 free(data);
321 }
322
323 #ifndef NO_COPY_N_PASTE
324 static gboolean focus_in(GtkWidget *widget, GdkEventFocus *event,
325 gpointer data) {
326 appdata_t *appdata = (appdata_t*)data;
327
328 printf("focus in!\n");
329
330 /* these buffers are read-only, thus only "copy" is enabled */
331 gtk_widget_set_sensitive(appdata->menu_cut, FALSE);
332 gtk_widget_set_sensitive(appdata->menu_copy, TRUE);
333 gtk_widget_set_sensitive(appdata->menu_paste, FALSE);
334
335 if(GTK_WIDGET_TYPE(widget) == GTK_TYPE_TEXT_VIEW) {
336 appdata->active_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
337 } else if(GTK_WIDGET_TYPE(widget) == gtk_html_get_type()) {
338 appdata->active_buffer = (GtkTextBuffer*)widget;
339 } else {
340 printf("not a text nor a html view\n");
341 }
342
343 return FALSE;
344 }
345
346 void html_copy_to_clipboard(appdata_t *appdata) {
347 gtk_html_copy(GTK_HTML(appdata->active_buffer));
348 }
349 #endif
350
351 #ifdef PANNABLE_HTML
352 static void
353 tap_and_hold_cb (GtkWidget *widget, gpointer user_data) {
354 appdata_t *appdata = (appdata_t*)user_data;
355
356 printf("Tap n hold\n");
357 hildon_banner_show_information(GTK_WIDGET(appdata->window), NULL, "Tap n hold");
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 html_mode_t mode, gboolean scrollwin,
369 cache_t *cache, char *anchor) {
370 GtkWidget *view;
371
372 if(mode == HTML_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 gtk_widget_tap_and_hold_setup(GTK_WIDGET(view), NULL, NULL, 0);
406 g_signal_connect(G_OBJECT(view), "tap-and-hold", G_CALLBACK(tap_and_hold_cb), appdata);
407 #endif
408
409 g_signal_connect(G_OBJECT(view), "destroy",
410 G_CALLBACK(on_destroy_htmlview), context);
411 } else {
412 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
413 gtk_text_buffer_set_text(buffer, text, strlen(text));
414
415 #ifndef USE_HILDON_TEXT_VIEW
416 view = gtk_text_view_new_with_buffer(buffer);
417 #else
418 view = hildon_text_view_new();
419 hildon_text_view_set_buffer(HILDON_TEXT_VIEW(view), buffer);
420
421 gtk_widget_tap_and_hold_setup(GTK_WIDGET(view), NULL, NULL, 0);
422 g_signal_connect(G_OBJECT(view), "tap-and-hold", G_CALLBACK(tap_and_hold_cb), appdata);
423 #endif
424
425 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
426 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
427 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(view), FALSE);
428
429 /* make this look nicer in fremantle and not just black-on-white */
430 /* just use the default style */
431 #ifdef USE_STACKABLE_WINDOW
432 /* in fremantle this is really tricky and we need to inherit the */
433 /* style from the topmost window in the stack */
434 HildonWindowStack *stack = hildon_window_stack_get_default();
435 GList *list = hildon_window_stack_get_windows(stack);
436 gtk_widget_set_style(view, GTK_WIDGET(list->data)->style);
437 g_list_free(list);
438 #else
439 gtk_widget_set_style(view, GTK_WIDGET(appdata->window)->style);
440 #endif
441
442 #ifndef NO_COPY_N_PASTE
443 g_signal_connect(G_OBJECT(view), "destroy",
444 G_CALLBACK(on_destroy_textview), appdata);
445 #endif
446 }
447
448 #ifndef NO_COPY_N_PASTE
449 g_signal_connect(G_OBJECT(view), "focus-in-event",
450 G_CALLBACK(focus_in), appdata);
451 #endif
452
453 if(scrollwin) {
454 #ifndef USE_PANNABLE_AREA
455 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
456 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
457 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
458 gtk_container_add(GTK_CONTAINER(scrolled_window), view);
459
460 #if 1
461 return scrolled_window;
462 #else
463 GtkWidget *fixed = gtk_fixed_new();
464 gtk_fixed_put(GTK_FIXED(fixed), scrolled_window, 0, 0);
465 GtkWidget *tbutton = gtk_toggle_button_new_with_label("sel");
466 gtk_fixed_put(GTK_FIXED(fixed), tbutton, 0, 0);
467 return fixed;
468 #endif
469 #else
470 #ifndef PANNABLE_HTML
471 if(mode == HTML_HTML) {
472 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
473 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
474 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
475 gtk_container_add(GTK_CONTAINER(scrolled_window), view);
476 return scrolled_window;
477 } else
478 #endif
479 {
480 GtkWidget *pannable_area = hildon_pannable_area_new();
481 gtk_container_add(GTK_CONTAINER(pannable_area), view);
482 return pannable_area;
483 }
484 #endif
485 }
486
487 return view;
488 }
489