Contents of /trunk/src/html.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 11 - (show annotations)
Fri Jun 26 12:24:24 2009 UTC (14 years, 10 months ago) by harbaum
File MIME type: text/plain
File size: 13557 byte(s)
Stackable window control flow fixes
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 #ifndef NO_COPY_N_PASTE
251 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 #endif
289
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 #ifndef NO_COPY_N_PASTE
318 on_destroy_textview(widget, context->appdata);
319 #endif
320
321 /* destroy context */
322 free(data);
323 }
324
325 #ifndef NO_COPY_N_PASTE
326 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 #endif
352
353 /* panning a gtkhtml view currently doesn't work well */
354 #define 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 #ifndef USE_HILDON_TEXT_VIEW
423 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 #ifndef NO_COPY_N_PASTE
434 g_signal_connect(G_OBJECT(view), "destroy",
435 G_CALLBACK(on_destroy_textview), appdata);
436 #endif
437 }
438
439 #ifndef NO_COPY_N_PASTE
440 g_signal_connect(G_OBJECT(view), "focus-in-event",
441 G_CALLBACK(focus_in), appdata);
442 #endif
443
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