Implement image handling
[claws.git] / src / plugins / litehtml_viewer / lh_widget.cpp
1 /*
2  * Claws Mail -- A GTK+ based, lightweight, and fast e-mail client
3  * Copyright(C) 1999-2015 the Claws Mail Team
4  * == Fancy Plugin ==
5  * This file Copyright (C) 2009-2015 Salvatore De Paolis
6  * <iwkse@claws-mail.org> and the Claws Mail Team
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  * This program 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  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write tothe Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include "config.h"
22 #include "claws-features.h"
23 #endif
24
25 #include <glib.h>
26 #include <glib/gstdio.h>
27 #include <fcntl.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <curl/curl.h>
31 #include "lh_widget.h"
32 #include "lh_widget_wrapped.h"
33
34 char master_css[] = {
35 #include "css.inc"
36 };
37
38 /**
39   * curl callback
40   */
41 static char* response_mime = NULL;     /* response content-type. ex: "text/html" */
42 static char* response_data = NULL;     /* response data from server. */
43 static size_t response_size = 0;       /* response size of data */
44
45 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
46                 gpointer user_data);
47 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
48                 gpointer user_data);
49 static size_t handle_returned_data(char* ptr, size_t size, size_t nmemb, void* stream);
50 static size_t handle_returned_header(void* ptr, size_t size, size_t nmemb, void* stream);
51
52 lh_widget::lh_widget()
53 {
54         /* scrolled window */
55         m_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
56         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_scrolled_window),
57                         GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
58         g_signal_connect(m_scrolled_window, "size-allocate",
59                         G_CALLBACK(size_allocate_cb), this);
60
61         /* viewport */
62         GtkScrolledWindow *scw = GTK_SCROLLED_WINDOW(m_scrolled_window);
63         m_viewport = gtk_viewport_new(
64                         gtk_scrolled_window_get_hadjustment(scw),
65                         gtk_scrolled_window_get_vadjustment(scw));
66         gtk_container_add(GTK_CONTAINER(m_scrolled_window), m_viewport);
67
68         /* drawing area */
69         m_drawing_area = gtk_drawing_area_new();
70         gtk_container_add(GTK_CONTAINER(m_viewport), m_drawing_area);
71         g_signal_connect(m_drawing_area, "expose-event",
72                         G_CALLBACK(expose_event_cb), this);
73
74         gtk_widget_show_all(m_scrolled_window);
75
76         m_html = NULL;
77         m_rendered_width = 0;
78         m_context.load_master_stylesheet(master_css);
79         stream = NULL;
80 }
81
82 lh_widget::~lh_widget()
83 {
84         g_object_unref(m_drawing_area);
85         m_drawing_area = NULL;
86         g_object_unref(m_scrolled_window);
87         m_scrolled_window = NULL;
88         m_html = NULL;
89         if (stream) {
90             g_input_stream_close(stream, NULL, NULL);
91             stream = NULL;
92         }
93 }
94
95 GtkWidget *lh_widget::get_widget() const
96 {
97         return m_scrolled_window;
98 }
99
100 void lh_widget::set_caption(const litehtml::tchar_t* caption)
101 {
102         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_caption");
103         return;
104 }
105
106 void lh_widget::set_base_url(const litehtml::tchar_t* base_url)
107 {
108         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_base_url");
109         return;
110 }
111
112 void lh_widget::on_anchor_click(const litehtml::tchar_t* url, const litehtml::element::ptr& el)
113 {
114         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget on_anchor_click");
115         return;
116 }
117
118 void lh_widget::set_cursor(const litehtml::tchar_t* cursor)
119 {
120         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_cursor");
121         if (cursor == NULL)
122                 return;
123 }
124
125 void lh_widget::import_css(litehtml::tstring& text, const litehtml::tstring& url, litehtml::tstring& baseurl)
126 {
127         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget import_css");
128         baseurl = master_css;
129 }
130
131 void lh_widget::get_client_rect(litehtml::position& client) const
132 {
133         if (m_drawing_area == NULL)
134                 return;
135
136         client.width = m_rendered_width;
137         client.height = m_height;
138         client.x = 0;
139         client.y = 0;
140
141 //      g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget::get_client_rect: %dx%d",
142 //                      client.width, client.height);
143 }
144
145 GdkPixbuf *lh_widget::get_image(const litehtml::tchar_t* url, bool redraw_on_ready)
146 {
147         GError *error = NULL;
148         GdkPixbuf *pixbuf = NULL;
149
150         g_log(NULL, G_LOG_LEVEL_MESSAGE, "Loading... %s", url);
151
152         GInputStream *image = load_url(url, &error);
153         if (error) {
154                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "Error: %s", error->message);
155                 g_error_free(error);
156                 return NULL;
157         }
158
159         GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
160         if (gdk_pixbuf_loader_write(loader, (const guchar*)response_data, response_size, &error)) {
161                 pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
162         } else {
163                 g_log(NULL, G_LOG_LEVEL_ERROR, "lh_widget::get_image: Could not create pixbuf");
164         }
165         gdk_pixbuf_loader_close(loader, NULL);
166
167         /* cleanup callback data */
168         if (response_mime) g_free(response_mime);
169         if (response_data) g_free(response_data);
170         response_data = NULL;
171         response_mime = NULL;
172         response_size = 0;
173         
174         return pixbuf;
175 }
176
177 void lh_widget::open_html(const gchar *contents)
178 {
179         m_html = litehtml::document::createFromString(contents, this, &m_context);
180         m_rendered_width = 0;
181         if (m_html != NULL) {
182                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget::open_html created document");
183                 redraw();
184         }
185 }
186
187 void lh_widget::draw(cairo_t *cr)
188 {
189         double x1, x2, y1, y2;
190         double width, height;
191
192         if (m_html == NULL)
193                 return;
194
195         cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
196
197         width = x2 - x1;
198         height = y2 - y1;
199
200         litehtml::position pos;
201         pos.width = (int)width;
202         pos.height = (int)height;
203         pos.x = (int)x1;
204         pos.y = (int)y1;
205
206         m_html->draw((litehtml::uint_ptr)cr, 0, 0, &pos);
207 }
208
209 void lh_widget::redraw()
210 {
211         GtkAllocation rect;
212         gint width, height;
213         GdkWindow *gdkwin;
214         cairo_t *cr;
215
216         if (m_html == NULL) {
217                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::redraw: No document!");
218                 return;
219         }
220
221         /* Get width of the viewport. */
222         gdkwin = gtk_viewport_get_view_window(GTK_VIEWPORT(m_viewport));
223         gdk_drawable_get_size(gdkwin, &width, NULL);
224
225         /* If the available width has changed, rerender the HTML content. */
226         if (m_rendered_width != width) {
227                 g_log(NULL, G_LOG_LEVEL_MESSAGE,
228                                 "lh_widget::redraw: width changed: %d != %d",
229                                 m_rendered_width, width);
230
231                 /* Update our internally stored width, mainly so that
232                  * lh_widget::get_client_rect() gives correct width during the
233                  * render. */
234                 m_rendered_width = width;
235
236                 /* Re-render HTML for this width. */
237                 m_html->media_changed();
238                 m_html->render(m_rendered_width);
239                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "render is %dx%d",
240                                 m_html->width(), m_html->height());
241
242                 /* Change drawing area's size to match what was rendered. */
243                 gtk_widget_set_size_request(m_drawing_area,
244                                 m_html->width(), m_html->height());
245         }
246
247         paint_white();
248
249         /* Paint the rendered HTML. */
250         gdkwin = gtk_widget_get_window(m_drawing_area);
251         if (gdkwin == NULL) {
252                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::redraw: No GdkWindow to draw on!");
253                 return;
254         }
255         cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
256         draw(cr);
257
258         cairo_destroy(cr);
259 }
260
261 void lh_widget::paint_white()
262 {
263         GdkWindow *gdkwin = gtk_widget_get_window(m_drawing_area);
264         if (gdkwin == NULL) {
265                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::clear: No GdkWindow to draw on!");
266                 return;
267         }
268         cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
269
270         /* Paint white background. */
271         gint width, height;
272         gdk_drawable_get_size(gdkwin, &width, &height);
273         cairo_rectangle(cr, 0, 0, width, height);
274         cairo_set_source_rgb(cr, 255, 255, 255);
275         cairo_fill(cr);
276
277         cairo_destroy(cr);
278 }
279 void lh_widget::clear()
280 {
281         paint_white();
282         m_rendered_width = 0;
283 }
284
285 GInputStream *lh_widget::load_url(const gchar *url, GError **error)
286 {
287         GError* _error = NULL;
288         CURL* curl = NULL;
289         CURLcode res = CURLE_OK;
290         gsize len;
291         gchar* content;
292
293         /* initialize callback data */
294         response_mime = NULL;
295         response_data = NULL;
296         response_size = 0;
297         if (stream) {
298                 g_input_stream_close(stream, NULL, &_error);
299                 if (_error) {
300                         if (error) *error = _error;
301                         return NULL;
302                 }
303         }
304                 
305         stream = NULL;
306
307         if (!strncmp(url, "file:///", 8) || g_file_test(url, G_FILE_TEST_EXISTS)) {
308                 gchar* newurl = g_filename_from_uri(url, NULL, NULL);
309                 if (g_file_get_contents(newurl ? newurl : url, &content, &len, &_error)) {
310                         stream = g_memory_input_stream_new_from_data(content, len, g_free);
311                 } else {
312                         g_log(NULL, G_LOG_LEVEL_MESSAGE, "%s", _error->message);
313                 }
314                 g_free(newurl);
315         } else {
316                 curl = curl_easy_init();
317                 if (!curl) return NULL;
318                 curl_easy_setopt(curl, CURLOPT_URL, url);
319                 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
320                 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, handle_returned_header);
321                 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
322                 curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_GET_TIMEOUT);
323                 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
324                 res = curl_easy_perform(curl);
325                 curl_easy_cleanup(curl);
326                 if (res == CURLE_OK) {
327                         stream = g_memory_input_stream_new_from_data(content, response_size, g_free);
328                 } else
329                         _error = g_error_new_literal(G_FILE_ERROR, res, curl_easy_strerror(res));
330         }
331
332         if (error && _error) *error = _error;
333
334         return stream;
335 }
336
337 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
338                 gpointer user_data)
339 {
340         lh_widget *w = (lh_widget *)user_data;
341         w->redraw();
342         return FALSE;
343 }
344
345 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
346                 gpointer user_data)
347 {
348         lh_widget *w = (lh_widget *)user_data;
349
350         g_log(NULL, G_LOG_LEVEL_MESSAGE, "size_allocate_cb: %dx%d",
351                         allocation->width, allocation->height);
352
353         w->setHeight(allocation->height);
354         w->redraw();
355 }
356
357 static size_t handle_returned_data(char* ptr, size_t size, size_t nmemb, void* stream) {
358         if (!response_data)
359                 response_data = (char*)malloc(size*nmemb);
360         else
361                 response_data = (char*)realloc(response_data, response_size+size*nmemb);
362         if (response_data) {
363                 memcpy(response_data+response_size, ptr, size*nmemb);
364                 response_size += size*nmemb;
365         }
366         return size*nmemb;
367 }
368
369 static size_t handle_returned_header(void* ptr, size_t size, size_t nmemb, void* stream) {
370         char* header = NULL;
371
372         header = (char*) malloc(size*nmemb + 1);
373         memcpy(header, ptr, size*nmemb);
374         header[size*nmemb] = 0;
375         if (strncmp(header, "Content-Type: ", 14) == 0) {
376                 char* stop = header + 14;
377                 stop = strpbrk(header + 14, "\r\n;");
378                 if (stop) *stop = 0;
379                 response_mime = strdup(header + 14);
380         }
381         free(header);
382         return size*nmemb;
383 }
384
385 ///////////////////////////////////////////////////////////
386 extern "C" {
387
388 lh_widget_wrapped *lh_widget_new()
389 {
390         return new lh_widget;
391 }
392
393 GtkWidget *lh_widget_get_widget(lh_widget_wrapped *w)
394 {
395         return w->get_widget();
396 }
397
398 void lh_widget_open_html(lh_widget_wrapped *w, const gchar *path)
399 {
400         w->open_html(path);
401 }
402
403 void lh_widget_clear(lh_widget_wrapped *w)
404 {
405         w->clear();
406 }
407
408 void lh_widget_destroy(lh_widget_wrapped *w)
409 {
410         delete w;
411 }
412
413 } /* extern "C" */