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/gi18n.h>
27 #include <glib/gstdio.h>
29 #include <sys/types.h>
31 #include <curl/curl.h>
36 #include "litehtml/litehtml.h"
39 #include "lh_widget.h"
40 #include "lh_widget_wrapped.h"
44 const gchar *prefs_common_get_uri_cmd(void);
51 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
53 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
55 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
57 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
59 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
61 static void open_link_cb(GtkMenuItem *item, gpointer user_data);
62 static void copy_link_cb(GtkMenuItem *item, gpointer user_data);
64 lh_widget::lh_widget()
69 m_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
70 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_scrolled_window),
71 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
72 g_signal_connect(m_scrolled_window, "size-allocate",
73 G_CALLBACK(size_allocate_cb), this);
76 GtkScrolledWindow *scw = GTK_SCROLLED_WINDOW(m_scrolled_window);
77 m_viewport = gtk_viewport_new(
78 gtk_scrolled_window_get_hadjustment(scw),
79 gtk_scrolled_window_get_vadjustment(scw));
80 gtk_container_add(GTK_CONTAINER(m_scrolled_window), m_viewport);
83 m_drawing_area = gtk_drawing_area_new();
84 gtk_container_add(GTK_CONTAINER(m_viewport), m_drawing_area);
85 g_signal_connect(m_drawing_area, "expose-event",
86 G_CALLBACK(expose_event_cb), this);
87 g_signal_connect(m_drawing_area, "motion_notify_event",
88 G_CALLBACK(motion_notify_event), this);
89 g_signal_connect(m_drawing_area, "button_press_event",
90 G_CALLBACK(button_press_event), this);
91 g_signal_connect(m_drawing_area, "button_release_event",
92 G_CALLBACK(button_release_event), this);
94 gtk_widget_show_all(m_scrolled_window);
97 m_context_menu = gtk_menu_new();
99 item = gtk_menu_item_new_with_label(_("Open Link"));
100 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_link_cb), this);
101 gtk_menu_shell_append(GTK_MENU_SHELL(m_context_menu), item);
103 item = gtk_menu_item_new_with_label(_("Copy Link Location"));
104 g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(copy_link_cb), this);
105 gtk_menu_shell_append(GTK_MENU_SHELL(m_context_menu), item);
108 m_rendered_width = 0;
109 m_context.load_master_stylesheet(master_css);
111 gtk_widget_set_events(m_drawing_area,
112 GDK_BUTTON_RELEASE_MASK
113 | GDK_BUTTON_PRESS_MASK
114 | GDK_POINTER_MOTION_MASK);
117 lh_widget::~lh_widget()
119 g_object_unref(m_drawing_area);
120 m_drawing_area = NULL;
121 g_object_unref(m_scrolled_window);
122 m_scrolled_window = NULL;
126 GtkWidget *lh_widget::get_widget() const
128 return m_scrolled_window;
131 void lh_widget::set_caption(const litehtml::tchar_t* caption)
133 debug_print("lh_widget set_caption\n");
137 void lh_widget::set_base_url(const litehtml::tchar_t* base_url)
139 debug_print("lh_widget set_base_url\n");
143 void lh_widget::on_anchor_click(const litehtml::tchar_t* url, const litehtml::element::ptr& el)
145 debug_print("lh_widget on_anchor_click. url -> %s\n", url);
151 void lh_widget::import_css(litehtml::tstring& text, const litehtml::tstring& url, litehtml::tstring& baseurl)
153 debug_print("lh_widget import_css\n");
154 baseurl = master_css;
157 void lh_widget::get_client_rect(litehtml::position& client) const
159 if (m_drawing_area == NULL)
162 client.width = m_rendered_width;
163 client.height = m_height;
167 // debug_print("lh_widget::get_client_rect: %dx%d\n",
168 // client.width, client.height);
171 GdkPixbuf *lh_widget::get_image(const litehtml::tchar_t* url, bool redraw_on_ready)
173 GError *error = NULL;
174 GdkPixbuf *pixbuf = NULL;
175 http* http_loader = NULL;
177 if (!lh_prefs_get()->enable_remote_content) {
178 debug_print("blocking download of image from '%s'\n", url);
182 debug_print("Loading... %s\n", url);
183 gchar *msg = g_strdup_printf("Loading %s ...", url);
184 lh_widget_statusbar_push(msg);
187 http_loader = new http();
188 GInputStream *image = http_loader->load_url(url, &error);
190 if (error || !image) {
192 g_warning("lh_widget::get_image: Could not create pixbuf %s", error->message);
193 g_clear_error(&error);
198 pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
200 g_warning("lh_widget::get_image: Could not create pixbuf %s", error->message);
202 g_clear_error(&error);
205 /* if (redraw_on_ready) {
210 lh_widget_statusbar_pop();
218 void lh_widget::open_html(const gchar *contents)
220 gint num = clear_images(lh_prefs_get()->image_cache_size * 1024 * 1000);
222 debug_print("LH: cleared %d images from image cache\n", num);
224 lh_widget_statusbar_push("Loading HTML part ...");
225 m_html = litehtml::document::createFromString(contents, this, &m_context);
226 m_rendered_width = 0;
227 if (m_html != NULL) {
228 debug_print("lh_widget::open_html created document\n");
231 lh_widget_statusbar_pop();
234 void lh_widget::draw(cairo_t *cr)
236 double x1, x2, y1, y2;
237 double width, height;
242 cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
247 litehtml::position pos;
248 pos.width = (int)width;
249 pos.height = (int)height;
253 m_html->draw((litehtml::uint_ptr)cr, 0, 0, &pos);
256 void lh_widget::redraw()
268 /* Get width of the viewport. */
269 gdkwin = gtk_viewport_get_view_window(GTK_VIEWPORT(m_viewport));
270 gdk_drawable_get_size(gdkwin, &width, NULL);
272 /* If the available width has changed, rerender the HTML content. */
273 if (m_rendered_width != width) {
274 debug_print("lh_widget::redraw: width changed: %d != %d\n",
275 m_rendered_width, width);
277 /* Update our internally stored width, mainly so that
278 * lh_widget::get_client_rect() gives correct width during the
280 m_rendered_width = width;
282 /* Re-render HTML for this width. */
283 m_html->media_changed();
284 m_html->render(m_rendered_width);
285 debug_print("render is %dx%d\n", m_html->width(), m_html->height());
287 /* Change drawing area's size to match what was rendered. */
288 gtk_widget_set_size_request(m_drawing_area,
289 m_html->width(), m_html->height());
292 /* Paint the rendered HTML. */
293 gdkwin = gtk_widget_get_window(m_drawing_area);
294 if (gdkwin == NULL) {
295 g_warning("lh_widget::redraw: No GdkWindow to draw on!");
298 cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
304 void lh_widget::paint_white()
306 GdkWindow *gdkwin = gtk_widget_get_window(m_drawing_area);
307 if (gdkwin == NULL) {
308 g_warning("lh_widget::clear: No GdkWindow to draw on!");
311 cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
313 /* Paint white background. */
315 gdk_drawable_get_size(gdkwin, &width, &height);
316 cairo_rectangle(cr, 0, 0, width, height);
317 cairo_set_source_rgb(cr, 255, 255, 255);
322 void lh_widget::clear()
326 m_rendered_width = 0;
329 void lh_widget::set_cursor(const litehtml::tchar_t* cursor)
332 if (m_cursor != cursor) {
339 void lh_widget::update_cursor()
342 const litehtml::tchar_t *href;
343 GdkWindow *w = gdk_display_get_window_at_pointer(gdk_display_get_default(),
345 GdkCursorType cursType = GDK_ARROW;
347 if (m_cursor == _t("pointer")) {
348 cursType = GDK_HAND2;
351 if (cursType == GDK_ARROW) {
352 gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), NULL);
354 gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), gdk_cursor_new(cursType));
357 if (w != gtk_widget_get_window(m_drawing_area))
360 /* If it's an anchor, show its "href" attribute in statusbar,
361 * otherwise clear statusbar. */
362 if ((href = get_href_at(x, y)) != NULL) {
363 lh_widget_statusbar_push(href);
365 lh_widget_statusbar_pop();
369 const litehtml::tchar_t *lh_widget::get_href_at(const gint x, const gint y) const
371 litehtml::element::ptr over_el, el;
376 over_el = m_html->root()->get_element_by_point(x, y, x, y);
380 /* If it's not an anchor, check if it has a parent anchor
381 * (e.g. it's an image within an anchor) and grab a pointer
383 if (strcmp(over_el->get_tagName(), "a") && over_el->parent()) {
384 el = over_el->parent();
385 while (el && el != m_html->root() && strcmp(el->get_tagName(), "a")) {
389 if (el && el != m_html->root())
395 /* At this point, over_el is pointing at an anchor tag, so let's
396 * grab its href attribute. */
397 return over_el->get_attr(_t("href"));
400 void lh_widget::print()
402 debug_print("lh_widget print\n");
403 gtk_widget_realize(GTK_WIDGET(m_drawing_area));
406 void lh_widget::popup_context_menu(const litehtml::tchar_t *url,
407 GdkEventButton *event)
409 cm_return_if_fail(url != NULL);
410 cm_return_if_fail(event != NULL);
412 debug_print("lh_widget showing context menu for '%s'\n", url);
415 gtk_widget_show_all(m_context_menu);
416 gtk_menu_popup(GTK_MENU(m_context_menu), NULL, NULL, NULL, NULL,
417 event->button, event->time);
420 static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
423 lh_widget *w = (lh_widget *)user_data;
428 static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
431 lh_widget *w = (lh_widget *)user_data;
433 debug_print("size_allocate_cb: %dx%d\n",
434 allocation->width, allocation->height);
436 w->setHeight(allocation->height);
440 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
443 litehtml::position::vector redraw_boxes;
444 lh_widget *w = (lh_widget *)user_data;
446 if (w->m_html == NULL)
449 debug_print("lh_widget on_button_press_event\n");
451 if (event->type == GDK_2BUTTON_PRESS ||
452 event->type == GDK_3BUTTON_PRESS)
456 if (event->button == 3) {
457 const litehtml::tchar_t *url = w->get_href_at((gint)event->x, (gint)event->y);
460 w->popup_context_menu(url, event);
465 if(w->m_html->on_lbutton_down((int) event->x, (int) event->y,
466 (int) event->x, (int) event->y, redraw_boxes)) {
467 for(auto& pos : redraw_boxes) {
468 debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
469 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
476 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
479 litehtml::position::vector redraw_boxes;
480 lh_widget *w = (lh_widget *)user_data;
482 //debug_print("lh_widget on_motion_notify_event\n");
486 //if(m_cursor == _t("pointer"))
487 if(w->m_html->on_mouse_over((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
489 for (auto& pos : redraw_boxes)
491 debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
492 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
500 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
503 litehtml::position::vector redraw_boxes;
504 lh_widget *w = (lh_widget *)user_data;
505 GError* error = NULL;
507 if (w->m_html == NULL)
510 debug_print("lh_widget on_button_release_event\n");
512 if (event->type == GDK_2BUTTON_PRESS ||
513 event->type == GDK_3BUTTON_PRESS)
517 if (event->button == 3)
520 w->m_clicked_url.clear();
522 if(w->m_html->on_lbutton_up((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
524 for (auto& pos : redraw_boxes)
526 debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
527 gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
531 if (!w->m_clicked_url.empty())
533 debug_print("Open in browser: %s\n", w->m_clicked_url.c_str());
534 open_uri(w->m_clicked_url.c_str(), prefs_common_get_uri_cmd());
540 static void open_link_cb(GtkMenuItem *item, gpointer user_data)
542 lh_widget_wrapped *w = (lh_widget_wrapped *)user_data;
544 open_uri(w->m_clicked_url.c_str(), prefs_common_get_uri_cmd());
547 static void copy_link_cb(GtkMenuItem *item, gpointer user_data)
549 lh_widget_wrapped *w = (lh_widget_wrapped *)user_data;
551 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
552 w->m_clicked_url.c_str(), -1);
553 gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
554 w->m_clicked_url.c_str(), -1);
557 ///////////////////////////////////////////////////////////
560 lh_widget_wrapped *lh_widget_new()
562 return new lh_widget;
565 GtkWidget *lh_widget_get_widget(lh_widget_wrapped *w)
567 return w->get_widget();
570 void lh_widget_open_html(lh_widget_wrapped *w, const gchar *path)
575 void lh_widget_clear(lh_widget_wrapped *w)
580 void lh_widget_destroy(lh_widget_wrapped *w)
585 void lh_widget_print(lh_widget_wrapped *w) {