Update current version of litehtml. Fix a crash when document contains no fonts
[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 <gdk/gdk.h>
32 #include "lh_widget.h"
33 #include "lh_widget_wrapped.h"
34 #include "http.h"
35
36 char master_css[] = {
37 #include "css.inc"
38 };
39
40 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
41                 gpointer user_data);
42 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
43                 gpointer user_data);
44 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
45                 gpointer user_data);
46 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
47         gpointer user_data);
48 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
49         gpointer user_data);
50
51 lh_widget::lh_widget()
52 {
53         /* scrolled window */
54         m_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
55         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_scrolled_window),
56                         GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
57         g_signal_connect(m_scrolled_window, "size-allocate",
58                         G_CALLBACK(size_allocate_cb), this);
59
60         /* viewport */
61         GtkScrolledWindow *scw = GTK_SCROLLED_WINDOW(m_scrolled_window);
62         m_viewport = gtk_viewport_new(
63                         gtk_scrolled_window_get_hadjustment(scw),
64                         gtk_scrolled_window_get_vadjustment(scw));
65         gtk_container_add(GTK_CONTAINER(m_scrolled_window), m_viewport);
66
67         /* drawing area */
68         m_drawing_area = gtk_drawing_area_new();
69         gtk_container_add(GTK_CONTAINER(m_viewport), m_drawing_area);
70         g_signal_connect(m_drawing_area, "expose-event",
71                         G_CALLBACK(expose_event_cb), this);
72         g_signal_connect(m_drawing_area, "motion_notify_event",
73                         G_CALLBACK(motion_notify_event), this);
74         g_signal_connect(m_drawing_area, "button_press_event",
75                         G_CALLBACK(button_press_event), this);
76         g_signal_connect(m_drawing_area, "button_release_event",
77                         G_CALLBACK(button_release_event), this);
78
79         gtk_widget_show_all(m_scrolled_window);
80
81         m_html = NULL;
82         m_rendered_width = 0;
83         m_context.load_master_stylesheet(master_css);
84
85         gtk_widget_set_events(m_drawing_area,
86                                 GDK_BUTTON_RELEASE_MASK
87                               | GDK_BUTTON_PRESS_MASK
88                               | GDK_POINTER_MOTION_MASK);
89
90 }
91
92 lh_widget::~lh_widget()
93 {
94         g_object_unref(m_drawing_area);
95         m_drawing_area = NULL;
96         g_object_unref(m_scrolled_window);
97         m_scrolled_window = NULL;
98         m_html = NULL;
99 }
100
101 GtkWidget *lh_widget::get_widget() const
102 {
103         return m_scrolled_window;
104 }
105
106 void lh_widget::set_caption(const litehtml::tchar_t* caption)
107 {
108         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_caption");
109         return;
110 }
111
112 void lh_widget::set_base_url(const litehtml::tchar_t* base_url)
113 {
114         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_base_url");
115         return;
116 }
117
118 void lh_widget::on_anchor_click(const litehtml::tchar_t* url, const litehtml::element::ptr& el)
119 {
120         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget on_anchor_click. url -> %s", url);
121         m_clicked_url = url;
122         
123         return;
124 }
125
126 void lh_widget::import_css(litehtml::tstring& text, const litehtml::tstring& url, litehtml::tstring& baseurl)
127 {
128         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget import_css");
129         baseurl = master_css;
130 }
131
132 void lh_widget::get_client_rect(litehtml::position& client) const
133 {
134         if (m_drawing_area == NULL)
135                 return;
136
137         client.width = m_rendered_width;
138         client.height = m_height;
139         client.x = 0;
140         client.y = 0;
141
142 //      g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget::get_client_rect: %dx%d",
143 //                      client.width, client.height);
144 }
145
146 GdkPixbuf *lh_widget::get_image(const litehtml::tchar_t* url, bool redraw_on_ready)
147 {
148         GError *error = NULL;
149         GdkPixbuf *pixbuf = NULL;
150
151         g_log(NULL, G_LOG_LEVEL_MESSAGE, "Loading... %s", url);
152         gchar *msg = g_strdup_printf("Loading %s ...", url);
153         lh_widget_statusbar_push(msg);
154         g_free(msg);
155         
156         http http_loader;
157         GInputStream *image = http_loader.load_url(url, &error);
158     
159         if (error || !image) {
160             if (error) {
161                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::get_image: Could not create pixbuf %s", error->message);
162                 g_clear_error(&error);
163             }
164             goto statusbar_pop;
165         }
166
167         pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
168         if (error) {
169             g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::get_image: Could not create pixbuf %s", error->message);
170             //g_object_unref(pixbuf);
171             pixbuf = NULL;
172             g_clear_error(&error);
173         }
174         g_input_stream_close(image, NULL, NULL);
175
176 /*      if (redraw_on_ready) {
177                 redraw();
178         }*/
179
180 statusbar_pop:
181         lh_widget_statusbar_pop();
182         
183         return pixbuf;
184 }
185
186 void lh_widget::open_html(const gchar *contents)
187 {
188         lh_widget_statusbar_push("Loading HTML part ...");
189         m_html = litehtml::document::createFromString(contents, this, &m_context);
190         m_rendered_width = 0;
191         if (m_html != NULL) {
192                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget::open_html created document");
193                 redraw();
194         }
195         lh_widget_statusbar_pop();
196 }
197
198 void lh_widget::draw(cairo_t *cr)
199 {
200         double x1, x2, y1, y2;
201         double width, height;
202
203         if (m_html == NULL)
204                 return;
205
206         cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
207
208         width = x2 - x1;
209         height = y2 - y1;
210
211         litehtml::position pos;
212         pos.width = (int)width;
213         pos.height = (int)height;
214         pos.x = (int)x1;
215         pos.y = (int)y1;
216
217         m_html->draw((litehtml::uint_ptr)cr, 0, 0, &pos);
218 }
219
220 void lh_widget::redraw()
221 {
222         GtkAllocation rect;
223         gint width, height;
224         GdkWindow *gdkwin;
225         cairo_t *cr;
226
227         if (m_html == NULL) {
228                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::redraw: No document!");
229                 return;
230         }
231
232         /* Get width of the viewport. */
233         gdkwin = gtk_viewport_get_view_window(GTK_VIEWPORT(m_viewport));
234         gdk_drawable_get_size(gdkwin, &width, NULL);
235
236         /* If the available width has changed, rerender the HTML content. */
237         if (m_rendered_width != width) {
238                 g_log(NULL, G_LOG_LEVEL_MESSAGE,
239                                 "lh_widget::redraw: width changed: %d != %d",
240                                 m_rendered_width, width);
241
242                 /* Update our internally stored width, mainly so that
243                  * lh_widget::get_client_rect() gives correct width during the
244                  * render. */
245                 m_rendered_width = width;
246
247                 /* Re-render HTML for this width. */
248                 m_html->media_changed();
249                 m_html->render(m_rendered_width);
250                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "render is %dx%d",
251                                 m_html->width(), m_html->height());
252
253                 /* Change drawing area's size to match what was rendered. */
254                 gtk_widget_set_size_request(m_drawing_area,
255                                 m_html->width(), m_html->height());
256         }
257
258         paint_white();
259
260         /* Paint the rendered HTML. */
261         gdkwin = gtk_widget_get_window(m_drawing_area);
262         if (gdkwin == NULL) {
263                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::redraw: No GdkWindow to draw on!");
264                 return;
265         }
266         cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
267         draw(cr);
268
269         cairo_destroy(cr);
270 }
271
272 void lh_widget::paint_white()
273 {
274         GdkWindow *gdkwin = gtk_widget_get_window(m_drawing_area);
275         if (gdkwin == NULL) {
276                 g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::clear: No GdkWindow to draw on!");
277                 return;
278         }
279         cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
280
281         /* Paint white background. */
282         gint width, height;
283         gdk_drawable_get_size(gdkwin, &width, &height);
284         cairo_rectangle(cr, 0, 0, width, height);
285         cairo_set_source_rgb(cr, 255, 255, 255);
286         cairo_fill(cr);
287
288         cairo_destroy(cr);
289 }
290 void lh_widget::clear()
291 {
292         paint_white();
293         m_rendered_width = 0;
294 }
295
296 void lh_widget::set_cursor(const litehtml::tchar_t* cursor)
297 {
298     //g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_cursor %s:%s", m_cursor, cursor);
299     if (cursor)
300     {
301         if (m_cursor != cursor)
302         {
303             m_cursor = cursor;
304             update_cursor();
305         }
306     }
307 }
308
309 void lh_widget::update_cursor()
310 {
311     //g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget update_cursor %s", m_cursor);
312     GdkCursorType cursType = GDK_ARROW;
313     if(m_cursor == _t("pointer"))
314     {
315         cursType = GDK_HAND2;
316     }
317     if(cursType == GDK_ARROW)
318     {
319         lh_widget_statusbar_pop();
320         gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), NULL);
321     } else
322     {
323         if (!m_clicked_url.empty()) {
324             lh_widget_statusbar_push(m_clicked_url.c_str());
325         }
326         gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), gdk_cursor_new(cursType));
327     }
328 }
329
330 void lh_widget::print()
331 {
332     g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget print");
333     gtk_widget_realize(GTK_WIDGET(m_drawing_area));
334 }
335
336 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
337                 gpointer user_data)
338 {
339         lh_widget *w = (lh_widget *)user_data;
340         w->redraw();
341         return FALSE;
342 }
343
344 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
345                 gpointer user_data)
346 {
347         lh_widget *w = (lh_widget *)user_data;
348
349         g_log(NULL, G_LOG_LEVEL_MESSAGE, "size_allocate_cb: %dx%d",
350                         allocation->width, allocation->height);
351
352         w->setHeight(allocation->height);
353         w->redraw();
354 }
355
356 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
357                 gpointer user_data)
358 {
359     litehtml::position::vector redraw_boxes;
360     lh_widget *w = (lh_widget *)user_data;
361     
362     g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget on_button_press_event");
363
364     if(w->m_html)
365     {    
366         if(w->m_html->on_lbutton_down((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
367         {
368             for(auto& pos : redraw_boxes)
369             {
370                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "x: %d y:%d w: %d h: %d", pos.x, pos.y, pos.width, pos.height);
371                 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
372             }
373         }
374         }
375         
376         return true;
377 }
378
379 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
380         gpointer user_data)
381 {
382     litehtml::position::vector redraw_boxes;
383     lh_widget *w = (lh_widget *)user_data;
384     
385     //g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget on_motion_notify_event");
386
387     if(w->m_html)
388     {    
389         //if(m_cursor == _t("pointer"))
390         if(w->m_html->on_mouse_over((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
391         {
392             for (auto& pos : redraw_boxes)
393             {
394                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "x: %d y:%d w: %d h: %d", pos.x, pos.y, pos.width, pos.height);
395                 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
396             }
397         }
398         }
399         
400         return true;
401 }
402
403 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
404         gpointer user_data)
405 {
406     litehtml::position::vector redraw_boxes;
407     lh_widget *w = (lh_widget *)user_data;
408     GError* error = NULL;
409
410         g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget on_button_release_event");
411         
412         if(w->m_html)
413         {
414             w->m_clicked_url.clear();
415             if(w->m_html->on_lbutton_up((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
416         {
417             for (auto& pos : redraw_boxes)
418             {
419                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "x: %d y:%d w: %d h: %d", pos.x, pos.y, pos.width, pos.height);
420                 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
421             }
422         }
423         
424         if (!w->m_clicked_url.empty())
425         {
426                 g_log(NULL, G_LOG_LEVEL_MESSAGE, "Open in browser: %s", w->m_clicked_url.c_str());
427                 gtk_show_uri(gdk_screen_get_default(),
428                              w->m_clicked_url.c_str(),
429                              GDK_CURRENT_TIME, &error);
430                 if (error) {
431                     g_log(NULL, G_LOG_LEVEL_WARNING, "Failed opening url(%s): %s", w->m_clicked_url, error->message);
432                     g_clear_error(&error);
433                 }
434         }
435     }
436
437         return true;
438 }
439
440 ///////////////////////////////////////////////////////////
441 extern "C" {
442
443 lh_widget_wrapped *lh_widget_new()
444 {
445         return new lh_widget;
446 }
447
448 GtkWidget *lh_widget_get_widget(lh_widget_wrapped *w)
449 {
450         return w->get_widget();
451 }
452
453 void lh_widget_open_html(lh_widget_wrapped *w, const gchar *path)
454 {
455         w->open_html(path);
456 }
457
458 void lh_widget_clear(lh_widget_wrapped *w)
459 {
460         w->clear();
461 }
462
463 void lh_widget_destroy(lh_widget_wrapped *w)
464 {
465         delete w;
466 }
467
468 void lh_widget_print(lh_widget_wrapped *w) {
469         w->print();
470 }
471
472 } /* extern "C" */