Parent Directory | Revision Log
Textview color handling
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 | #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 | html_mode_t mode, gboolean scrollwin, |
366 | cache_t *cache, char *anchor) { |
367 | GtkWidget *view; |
368 | |
369 | if(mode == HTML_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_HILDON_TEXT_VIEW |
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 | // #if MAEMO_VERSION_MAJOR >= 5 |
428 | #if 1 |
429 | /* make this look nicer in fremantle and not just black-on-white */ |
430 | |
431 | #if 0 |
432 | /* this causes a black background */ |
433 | gtk_widget_modify_base(view, GTK_STATE_NORMAL, |
434 | >K_WIDGET(appdata->window)->style->bg[GTK_STATE_NORMAL]); |
435 | /* and this causes white letters */ |
436 | gtk_widget_modify_text(view, GTK_STATE_NORMAL, |
437 | >K_WIDGET(appdata->window)->style->text[GTK_STATE_NORMAL]); |
438 | #else |
439 | /* just use the default style */ |
440 | #if 1 |
441 | gtk_widget_set_style(view, GTK_WIDGET(appdata->window)->style); |
442 | #else |
443 | GtkStyle *style = gtk_rc_get_style(GTK_WIDGET(appdata->window)); |
444 | gtk_widget_set_style(view, style); |
445 | #endif |
446 | #endif |
447 | |
448 | #endif |
449 | |
450 | #ifndef NO_COPY_N_PASTE |
451 | g_signal_connect(G_OBJECT(view), "destroy", |
452 | G_CALLBACK(on_destroy_textview), appdata); |
453 | #endif |
454 | } |
455 | |
456 | #ifndef NO_COPY_N_PASTE |
457 | g_signal_connect(G_OBJECT(view), "focus-in-event", |
458 | G_CALLBACK(focus_in), appdata); |
459 | #endif |
460 | |
461 | if(scrollwin) { |
462 | #ifndef USE_PANNABLE_AREA |
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 | |
468 | #if 1 |
469 | return scrolled_window; |
470 | #else |
471 | GtkWidget *fixed = gtk_fixed_new(); |
472 | gtk_fixed_put(GTK_FIXED(fixed), scrolled_window, 0, 0); |
473 | GtkWidget *tbutton = gtk_toggle_button_new_with_label("sel"); |
474 | gtk_fixed_put(GTK_FIXED(fixed), tbutton, 0, 0); |
475 | return fixed; |
476 | #endif |
477 | #else |
478 | #ifndef PANNABLE_HTML |
479 | if(mode == HTML_HTML) { |
480 | GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL); |
481 | gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window), |
482 | GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
483 | gtk_container_add(GTK_CONTAINER(scrolled_window), view); |
484 | return scrolled_window; |
485 | } else |
486 | #endif |
487 | { |
488 | GtkWidget *pannable_area = hildon_pannable_area_new(); |
489 | gtk_container_add(GTK_CONTAINER(pannable_area), view); |
490 | return pannable_area; |
491 | } |
492 | #endif |
493 | } |
494 | |
495 | return view; |
496 | } |
497 |