2 * Claws Mail -- A GTK+ based, lightweight, and fast e-mail client
3 * Copyright(C) 1999-2015 the Claws Mail Team
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.
22 #include "claws-features.h"
26 #include <glib/gstdio.h>
28 #include <sys/types.h>
30 #include <curl/curl.h>
35 #include "litehtml/litehtml.h"
37 #include "lh_widget.h"
38 #include "lh_widget_wrapped.h"
45 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
47 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
49 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
51 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
53 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
56 lh_widget::lh_widget()
59 m_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
60 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_scrolled_window),
61 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
62 g_signal_connect(m_scrolled_window, "size-allocate",
63 G_CALLBACK(size_allocate_cb), this);
66 GtkScrolledWindow *scw = GTK_SCROLLED_WINDOW(m_scrolled_window);
67 m_viewport = gtk_viewport_new(
68 gtk_scrolled_window_get_hadjustment(scw),
69 gtk_scrolled_window_get_vadjustment(scw));
70 gtk_container_add(GTK_CONTAINER(m_scrolled_window), m_viewport);
73 m_drawing_area = gtk_drawing_area_new();
74 gtk_container_add(GTK_CONTAINER(m_viewport), m_drawing_area);
75 g_signal_connect(m_drawing_area, "expose-event",
76 G_CALLBACK(expose_event_cb), this);
77 g_signal_connect(m_drawing_area, "motion_notify_event",
78 G_CALLBACK(motion_notify_event), this);
79 g_signal_connect(m_drawing_area, "button_press_event",
80 G_CALLBACK(button_press_event), this);
81 g_signal_connect(m_drawing_area, "button_release_event",
82 G_CALLBACK(button_release_event), this);
84 gtk_widget_show_all(m_scrolled_window);
88 m_context.load_master_stylesheet(master_css);
90 gtk_widget_set_events(m_drawing_area,
91 GDK_BUTTON_RELEASE_MASK
92 | GDK_BUTTON_PRESS_MASK
93 | GDK_POINTER_MOTION_MASK);
97 lh_widget::~lh_widget()
99 g_object_unref(m_drawing_area);
100 m_drawing_area = NULL;
101 g_object_unref(m_scrolled_window);
102 m_scrolled_window = NULL;
106 GtkWidget *lh_widget::get_widget() const
108 return m_scrolled_window;
111 void lh_widget::set_caption(const litehtml::tchar_t* caption)
113 debug_print("lh_widget set_caption\n");
117 void lh_widget::set_base_url(const litehtml::tchar_t* base_url)
119 debug_print("lh_widget set_base_url\n");
123 void lh_widget::on_anchor_click(const litehtml::tchar_t* url, const litehtml::element::ptr& el)
125 debug_print("lh_widget on_anchor_click. url -> %s\n", url);
131 void lh_widget::import_css(litehtml::tstring& text, const litehtml::tstring& url, litehtml::tstring& baseurl)
133 debug_print("lh_widget import_css\n");
134 baseurl = master_css;
137 void lh_widget::get_client_rect(litehtml::position& client) const
139 if (m_drawing_area == NULL)
142 client.width = m_rendered_width;
143 client.height = m_height;
147 // debug_print("lh_widget::get_client_rect: %dx%d\n",
148 // client.width, client.height);
151 GdkPixbuf *lh_widget::get_image(const litehtml::tchar_t* url, bool redraw_on_ready)
153 GError *error = NULL;
154 GdkPixbuf *pixbuf = NULL;
156 debug_print("Loading... %s\n", url);
157 gchar *msg = g_strdup_printf("Loading %s ...", url);
158 lh_widget_statusbar_push(msg);
162 GInputStream *image = http_loader.load_url(url, &error);
164 if (error || !image) {
166 g_warning("lh_widget::get_image: Could not create pixbuf %s", error->message);
167 g_clear_error(&error);
172 pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
174 g_warning("lh_widget::get_image: Could not create pixbuf %s", error->message);
175 //g_object_unref(pixbuf);
177 g_clear_error(&error);
179 g_input_stream_close(image, NULL, NULL);
181 /* if (redraw_on_ready) {
186 lh_widget_statusbar_pop();
191 void lh_widget::open_html(const gchar *contents)
193 lh_widget_statusbar_push("Loading HTML part ...");
194 m_html = litehtml::document::createFromString(contents, this, &m_context);
195 m_rendered_width = 0;
196 if (m_html != NULL) {
197 debug_print("lh_widget::open_html created document\n");
200 lh_widget_statusbar_pop();
203 void lh_widget::draw(cairo_t *cr)
205 double x1, x2, y1, y2;
206 double width, height;
211 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
216 litehtml::position pos;
217 pos.width = (int)width;
218 pos.height = (int)height;
222 m_html->draw((litehtml::uint_ptr)cr, 0, 0, &pos);
225 void lh_widget::redraw()
232 if (m_html == NULL) {
233 g_warning("lh_widget::redraw: No document!");
237 /* Get width of the viewport. */
238 gdkwin = gtk_viewport_get_view_window(GTK_VIEWPORT(m_viewport));
239 gdk_drawable_get_size(gdkwin, &width, NULL);
241 /* If the available width has changed, rerender the HTML content. */
242 if (m_rendered_width != width) {
243 debug_print("lh_widget::redraw: width changed: %d != %d\n",
244 m_rendered_width, width);
246 /* Update our internally stored width, mainly so that
247 * lh_widget::get_client_rect() gives correct width during the
249 m_rendered_width = width;
251 /* Re-render HTML for this width. */
252 m_html->media_changed();
253 m_html->render(m_rendered_width);
254 debug_print("render is %dx%d\n", m_html->width(), m_html->height());
256 /* Change drawing area's size to match what was rendered. */
257 gtk_widget_set_size_request(m_drawing_area,
258 m_html->width(), m_html->height());
263 /* Paint the rendered HTML. */
264 gdkwin = gtk_widget_get_window(m_drawing_area);
265 if (gdkwin == NULL) {
266 g_warning("lh_widget::redraw: No GdkWindow to draw on!");
269 cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
275 void lh_widget::paint_white()
277 GdkWindow *gdkwin = gtk_widget_get_window(m_drawing_area);
278 if (gdkwin == NULL) {
279 g_warning("lh_widget::clear: No GdkWindow to draw on!");
282 cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
284 /* Paint white background. */
286 gdk_drawable_get_size(gdkwin, &width, &height);
287 cairo_rectangle(cr, 0, 0, width, height);
288 cairo_set_source_rgb(cr, 255, 255, 255);
293 void lh_widget::clear()
296 m_rendered_width = 0;
299 void lh_widget::set_cursor(const litehtml::tchar_t* cursor)
302 if (m_cursor != cursor) {
309 void lh_widget::update_cursor()
312 litehtml::element::ptr root_el, over_el, el;
313 GdkWindow *w = gdk_display_get_window_at_pointer(gdk_display_get_default(),
315 GdkCursorType cursType = GDK_ARROW;
317 if (m_cursor == _t("pointer")) {
318 cursType = GDK_HAND2;
321 if (cursType == GDK_ARROW) {
322 gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), NULL);
324 gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), gdk_cursor_new(cursType));
327 if (w != gtk_widget_get_window(m_drawing_area))
330 /* Find the element we are hovering over */
331 root_el = m_html->root();
332 g_return_if_fail(root_el != NULL);
333 over_el = root_el->get_element_by_point(x, y, x, y);
334 g_return_if_fail(over_el != NULL);
336 /* If it's not an anchor, check if it has a parent anchor
337 * (e.g. it's an image within an anchor) and grab a pointer
339 if (strcmp(over_el->get_tagName(), "a") && over_el->parent()) {
340 el = over_el->parent();
341 while (el && el != root_el && strcmp(el->get_tagName(), "a")) {
345 if (el && el != root_el)
349 /* If it's an anchor, show its "href" attribute in statusbar,
350 * otherwise clear statusbar. */
351 if (!strcmp(over_el->get_tagName(), "a")) {
352 lh_widget_statusbar_push(over_el->get_attr(_t("href")));
354 lh_widget_statusbar_pop();
358 void lh_widget::print()
360 debug_print("lh_widget print\n");
361 gtk_widget_realize(GTK_WIDGET(m_drawing_area));
364 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
367 lh_widget *w = (lh_widget *)user_data;
372 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
375 lh_widget *w = (lh_widget *)user_data;
377 debug_print("size_allocate_cb: %dx%d\n",
378 allocation->width, allocation->height);
380 w->setHeight(allocation->height);
384 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
387 litehtml::position::vector redraw_boxes;
388 lh_widget *w = (lh_widget *)user_data;
390 debug_print("lh_widget on_button_press_event\n");
394 if(w->m_html->on_lbutton_down((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
396 for(auto& pos : redraw_boxes)
398 debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
399 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
407 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
410 litehtml::position::vector redraw_boxes;
411 lh_widget *w = (lh_widget *)user_data;
413 //debug_print("lh_widget on_motion_notify_event\n");
417 //if(m_cursor == _t("pointer"))
418 if(w->m_html->on_mouse_over((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
420 for (auto& pos : redraw_boxes)
422 debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
423 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
431 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
434 litehtml::position::vector redraw_boxes;
435 lh_widget *w = (lh_widget *)user_data;
436 GError* error = NULL;
438 debug_print("lh_widget on_button_release_event\n");
442 w->m_clicked_url.clear();
443 if(w->m_html->on_lbutton_up((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
445 for (auto& pos : redraw_boxes)
447 debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
448 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
452 if (!w->m_clicked_url.empty())
454 debug_print("Open in browser: %s\n", w->m_clicked_url.c_str());
455 gtk_show_uri(gdk_screen_get_default(),
456 w->m_clicked_url.c_str(),
457 GDK_CURRENT_TIME, &error);
459 g_warning("Failed opening url(%s): %s", w->m_clicked_url, error->message);
460 g_clear_error(&error);
468 ///////////////////////////////////////////////////////////
471 lh_widget_wrapped *lh_widget_new()
473 return new lh_widget;
476 GtkWidget *lh_widget_get_widget(lh_widget_wrapped *w)
478 return w->get_widget();
481 void lh_widget_open_html(lh_widget_wrapped *w, const gchar *path)
486 void lh_widget_clear(lh_widget_wrapped *w)
491 void lh_widget_destroy(lh_widget_wrapped *w)
496 void lh_widget_print(lh_widget_wrapped *w) {