fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / plugins / litehtml_viewer / lh_widget.cpp
index 5c90005c7ab27dff4517524b967e4b24b61a41ea..19181804fc71bdb010b48f50e75090ec06f147e7 100644 (file)
@@ -1,9 +1,7 @@
 /*
- * Claws Mail -- A GTK+ based, lightweight, and fast e-mail client
- * Copyright(C) 1999-2015 the Claws Mail Team
- * == Fancy Plugin ==
- * This file Copyright (C) 2009-2015 Salvatore De Paolis
- * <iwkse@claws-mail.org> and the Claws Mail Team
+ * Claws Mail -- A GTK based, lightweight, and fast e-mail client
+ * Copyright(C) 2019 the Claws Mail Team
+ *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 3 of the License, or
 #endif
 
 #include <glib.h>
+#include <glib/gi18n.h>
 #include <glib/gstdio.h>
 #include <fcntl.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <curl/curl.h>
+#include <gdk/gdk.h>
+
+#include "utils.h"
+
+#include "litehtml/litehtml.h"
+
+#include "lh_prefs.h"
 #include "lh_widget.h"
 #include "lh_widget_wrapped.h"
 
-char master_css[] = {
-#include "css.inc"
-};
-
-/**
-  * curl callback
-  */
-static char* response_mime = NULL;     /* response content-type. ex: "text/html" */
-static char* response_data = NULL;     /* response data from server. */
-static size_t response_size = 0;       /* response size of data */
+extern "C" {
+const gchar *prefs_common_get_uri_cmd(void);
+}
 
-static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
+static gboolean draw_cb(GtkWidget *widget, cairo_t *cr,
                gpointer user_data);
-static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
+static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
                gpointer user_data);
-static size_t handle_returned_data(char* ptr, size_t size, size_t nmemb, void* stream);
-static size_t handle_returned_header(void* ptr, size_t size, size_t nmemb, void* stream);
+static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
+        gpointer user_data);
+static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
+        gpointer user_data);
+static void open_link_cb(GtkMenuItem *item, gpointer user_data);
+static void copy_link_cb(GtkMenuItem *item, gpointer user_data);
 
 lh_widget::lh_widget()
 {
+       GtkWidget *item;
+
+       m_force_render = false;
+       m_blank = false;
+
        /* scrolled window */
        m_scrolled_window = gtk_scrolled_window_new(NULL, NULL);
        gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(m_scrolled_window),
                        GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
-       g_signal_connect(m_scrolled_window, "size-allocate",
-                       G_CALLBACK(size_allocate_cb), this);
 
        /* viewport */
        GtkScrolledWindow *scw = GTK_SCROLLED_WINDOW(m_scrolled_window);
@@ -68,15 +74,44 @@ lh_widget::lh_widget()
        /* drawing area */
        m_drawing_area = gtk_drawing_area_new();
        gtk_container_add(GTK_CONTAINER(m_viewport), m_drawing_area);
-       g_signal_connect(m_drawing_area, "expose-event",
-                       G_CALLBACK(expose_event_cb), this);
+       g_signal_connect(m_drawing_area, "draw",
+                       G_CALLBACK(draw_cb), this);
+       g_signal_connect(m_drawing_area, "motion_notify_event",
+                       G_CALLBACK(motion_notify_event), this);
+       g_signal_connect(m_drawing_area, "button_press_event",
+                       G_CALLBACK(button_press_event), this);
+       g_signal_connect(m_drawing_area, "button_release_event",
+                       G_CALLBACK(button_release_event), this);
 
        gtk_widget_show_all(m_scrolled_window);
 
+       /* context menu */
+       m_context_menu = gtk_menu_new();
+
+       item = gtk_menu_item_new_with_label(_("Open Link"));
+       g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(open_link_cb), this);
+       gtk_menu_shell_append(GTK_MENU_SHELL(m_context_menu), item);
+
+       item = gtk_menu_item_new_with_label(_("Copy Link Location"));
+       g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(copy_link_cb), this);
+       gtk_menu_shell_append(GTK_MENU_SHELL(m_context_menu), item);
+
        m_html = NULL;
        m_rendered_width = 0;
-       m_context.load_master_stylesheet(master_css);
-       stream = NULL;
+
+       m_font_name = NULL;
+       m_font_size = 0;
+
+       m_partinfo = NULL;
+
+       m_showing_url = FALSE;
+
+       m_cairo_context = NULL;
+
+       gtk_widget_set_events(m_drawing_area,
+                               GDK_BUTTON_RELEASE_MASK
+                             | GDK_BUTTON_PRESS_MASK
+                             | GDK_POINTER_MOTION_MASK);
 }
 
 lh_widget::~lh_widget()
@@ -86,10 +121,7 @@ lh_widget::~lh_widget()
        g_object_unref(m_scrolled_window);
        m_scrolled_window = NULL;
        m_html = NULL;
-       if (stream) {
-           g_input_stream_close(stream, NULL, NULL);
-           stream = NULL;
-       }
+       g_free(m_font_name);
 }
 
 GtkWidget *lh_widget::get_widget() const
@@ -97,35 +129,35 @@ GtkWidget *lh_widget::get_widget() const
        return m_scrolled_window;
 }
 
-void lh_widget::set_caption(const litehtml::tchar_t* caption)
+void lh_widget::set_caption(const char *caption)
 {
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_caption");
+       debug_print("lh_widget set_caption\n");
        return;
 }
 
-void lh_widget::set_base_url(const litehtml::tchar_t* base_url)
+void lh_widget::set_base_url(const char *base_url)
 {
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_base_url");
-       return;
-}
+       debug_print("lh_widget set_base_url '%s'\n",
+                       (base_url ? base_url : "(null)"));
+       if (base_url)
+               m_base_url = base_url;
+       else
+               m_base_url.clear();
 
-void lh_widget::on_anchor_click(const litehtml::tchar_t* url, const litehtml::element::ptr& el)
-{
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget on_anchor_click");
        return;
 }
 
-void lh_widget::set_cursor(const litehtml::tchar_t* cursor)
+void lh_widget::on_anchor_click(const char *url, const litehtml::element::ptr& el)
 {
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget set_cursor");
-       if (cursor == NULL)
-               return;
+       debug_print("lh_widget on_anchor_click. url -> %s\n", url);
+
+       m_clicked_url = fullurl(url);
+       return;
 }
 
-void lh_widget::import_css(litehtml::tstring& text, const litehtml::tstring& url, litehtml::tstring& baseurl)
+void lh_widget::import_css(litehtml::string& text, const litehtml::string& url, litehtml::string& baseurl)
 {
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget import_css");
-       baseurl = master_css;
+       debug_print("lh_widget import_css. url=\"%s\" baseurl=\"%s\"\n", url.c_str(), baseurl.c_str());
 }
 
 void lh_widget::get_client_rect(litehtml::position& client) const
@@ -138,50 +170,39 @@ void lh_widget::get_client_rect(litehtml::position& client) const
        client.x = 0;
        client.y = 0;
 
-//     g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget::get_client_rect: %dx%d",
+//     debug_print("lh_widget::get_client_rect: %dx%d\n",
 //                     client.width, client.height);
 }
 
-GdkPixbuf *lh_widget::get_image(const litehtml::tchar_t* url, bool redraw_on_ready)
+void lh_widget::open_html(const gchar *contents)
 {
-       GError *error = NULL;
-       GdkPixbuf *pixbuf = NULL;
+       gint num = clear_images(lh_prefs_get()->image_cache_size * 1024 * 1000);
+       GtkAdjustment *adj;
 
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "Loading... %s", url);
+       debug_print("LH: cleared %d images from image cache\n", num);
 
-       GInputStream *image = load_url(url, &error);
-       if (error) {
-               g_log(NULL, G_LOG_LEVEL_MESSAGE, "Error: %s", error->message);
-               g_error_free(error);
-               return NULL;
-       }
+       update_font();
 
-       GdkPixbufLoader* loader = gdk_pixbuf_loader_new();
-       if (gdk_pixbuf_loader_write(loader, (const guchar*)response_data, response_size, &error)) {
-               pixbuf = gdk_pixbuf_loader_get_pixbuf(loader);
-       } else {
-               g_log(NULL, G_LOG_LEVEL_ERROR, "lh_widget::get_image: Could not create pixbuf");
+       lh_widget_statusbar_push("Loading HTML part ...");
+       m_html = litehtml::document::createFromString(contents, this);
+       m_rendered_width = 0;
+       if (m_html != NULL) {
+               debug_print("lh_widget::open_html created document\n");
+               adj = gtk_scrolled_window_get_hadjustment(
+                               GTK_SCROLLED_WINDOW(m_scrolled_window));
+               gtk_adjustment_set_value(adj, 0.0);
+               adj = gtk_scrolled_window_get_vadjustment(
+                               GTK_SCROLLED_WINDOW(m_scrolled_window));
+               gtk_adjustment_set_value(adj, 0.0);
+               m_blank = false;
        }
-       gdk_pixbuf_loader_close(loader, NULL);
-
-        /* cleanup callback data */
-        if (response_mime) g_free(response_mime);
-        if (response_data) g_free(response_data);
-        response_data = NULL;
-        response_mime = NULL;
-        response_size = 0;
-       
-       return pixbuf;
+       lh_widget_statusbar_pop();
 }
 
-void lh_widget::open_html(const gchar *contents)
+void lh_widget::rerender()
 {
-       m_html = litehtml::document::createFromString(contents, this, &m_context);
-       m_rendered_width = 0;
-       if (m_html != NULL) {
-               g_log(NULL, G_LOG_LEVEL_MESSAGE, "lh_widget::open_html created document");
-               redraw();
-       }
+       m_force_render = true;
+       gtk_widget_queue_draw(m_drawing_area);
 }
 
 void lh_widget::draw(cairo_t *cr)
@@ -209,23 +230,24 @@ void lh_widget::draw(cairo_t *cr)
 void lh_widget::redraw()
 {
        GtkAllocation rect;
-       gint width, height;
+       gint width;
        GdkWindow *gdkwin;
        cairo_t *cr;
+       cairo_region_t *creg;
+       GdkDrawingContext *gdkctx;
+       gboolean destroy = FALSE;
 
-       if (m_html == NULL) {
-               g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::redraw: No document!");
+       if (m_html == NULL)
                return;
-       }
 
        /* Get width of the viewport. */
-       gdkwin = gtk_viewport_get_view_window(GTK_VIEWPORT(m_viewport));
-       gdk_drawable_get_size(gdkwin, &width, NULL);
+       gtk_widget_get_allocation(GTK_WIDGET(m_viewport), &rect);
+       width = rect.width;
+       m_height = rect.height;
 
        /* If the available width has changed, rerender the HTML content. */
-       if (m_rendered_width != width) {
-               g_log(NULL, G_LOG_LEVEL_MESSAGE,
-                               "lh_widget::redraw: width changed: %d != %d",
+       if (m_rendered_width != width || std::atomic_exchange(&m_force_render, false)) {
+               debug_print("lh_widget::redraw: width changed: %d != %d\n",
                                m_rendered_width, width);
 
                /* Update our internally stored width, mainly so that
@@ -236,150 +258,342 @@ void lh_widget::redraw()
                /* Re-render HTML for this width. */
                m_html->media_changed();
                m_html->render(m_rendered_width);
-               g_log(NULL, G_LOG_LEVEL_MESSAGE, "render is %dx%d",
-                               m_html->width(), m_html->height());
+               debug_print("render is %dx%d\n", m_html->width(), m_html->height());
 
                /* Change drawing area's size to match what was rendered. */
                gtk_widget_set_size_request(m_drawing_area,
                                m_html->width(), m_html->height());
        }
 
-       paint_white();
+       /* Use provided cairo context, if any. Otherwise create our own. */
+       if (m_cairo_context != NULL) {
+               cr = m_cairo_context;
+       } else {
+               gdkwin = gtk_widget_get_window(m_drawing_area);
+               if (gdkwin == NULL) {
+                       g_warning("lh_widget::redraw: No GdkWindow to draw on!");
+                       return;
+               }
+               creg = cairo_region_create_rectangle(&rect);
+               gdkctx = gdk_window_begin_draw_frame(gdkwin, creg);
+               cr = gdk_drawing_context_get_cairo_context(gdkctx);
+               destroy = TRUE;
+       }
 
-       /* Paint the rendered HTML. */
-       gdkwin = gtk_widget_get_window(m_drawing_area);
-       if (gdkwin == NULL) {
-               g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::redraw: No GdkWindow to draw on!");
-               return;
+       if(!std::atomic_exchange(&m_blank, false)) {
+               draw(cr);
+       } else {
+               cairo_rectangle(cr, rect.x, rect.y, rect.width, rect.height);
+               cairo_set_source_rgb(cr, 255, 255, 255);
+               cairo_fill(cr);
+       }
+
+       /* Only destroy the used cairo context if we created it earlier. */
+       if (destroy) {
+               gdk_window_end_draw_frame(gdkwin, gdkctx);
+               cairo_region_destroy(creg);
        }
-       cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
-       draw(cr);
+}
 
-       cairo_destroy(cr);
+void lh_widget::clear()
+{
+       m_html = nullptr;
+       m_blank = true;
+       m_rendered_width = 0;
+       m_base_url.clear();
+       m_clicked_url.clear();
 }
 
-void lh_widget::paint_white()
+void lh_widget::set_cursor(const char *cursor)
 {
-       GdkWindow *gdkwin = gtk_widget_get_window(m_drawing_area);
-       if (gdkwin == NULL) {
-               g_log(NULL, G_LOG_LEVEL_WARNING, "lh_widget::clear: No GdkWindow to draw on!");
-               return;
-       }
-       cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
+       litehtml::element::const_ptr over_el = m_html->get_over_element();
 
-       /* Paint white background. */
-       gint width, height;
-       gdk_drawable_get_size(gdkwin, &width, &height);
-       cairo_rectangle(cr, 0, 0, width, height);
-       cairo_set_source_rgb(cr, 255, 255, 255);
-       cairo_fill(cr);
+       if (m_showing_url &&
+                       (over_el == NULL || over_el != m_over_element)) {
+               lh_widget_statusbar_pop();
+               m_showing_url = FALSE;
+       }
 
-       cairo_destroy(cr);
+       if (over_el != m_over_element) {
+               m_over_element = over_el;
+               update_cursor(cursor);
+       }
 }
-void lh_widget::clear()
+
+void lh_widget::update_cursor(const char *cursor)
 {
-       paint_white();
-       m_rendered_width = 0;
+       GdkCursorType cursType = GDK_ARROW;
+       const char *href = get_href_at(m_over_element);
+
+       /* If there is a href, and litehtml is okay with showing a pointer
+        * cursor ("pointer" or "auto"), set it, otherwise keep the
+        * default arrow cursor */
+       if ((!strcmp(cursor, "pointer") || !strcmp(cursor, "auto")) &&
+                       href != NULL) {
+               cursType = GDK_HAND2;
+       }
+
+       if (cursType == GDK_ARROW) {
+               gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area), NULL);
+       } else {
+               gdk_window_set_cursor(gtk_widget_get_window(m_drawing_area),
+                               gdk_cursor_new_for_display(gtk_widget_get_display(m_drawing_area),
+                                       cursType));
+       }
+
+       /* If there is a href, show it in statusbar */
+       if (href != NULL) {
+               lh_widget_statusbar_push(fullurl(href).c_str());
+               m_showing_url = TRUE;
+       }
 }
 
-GInputStream *lh_widget::load_url(const gchar *url, GError **error)
+const char *lh_widget::get_href_at(litehtml::element::const_ptr element)
 {
-       GError* _error = NULL;
-       CURL* curl = NULL;
-       CURLcode res = CURLE_OK;
-       gsize len;
-       gchar* content;
+       litehtml::element::const_ptr el;
 
-       /* initialize callback data */
-       response_mime = NULL;
-       response_data = NULL;
-       response_size = 0;
-       if (stream) {
-               g_input_stream_close(stream, NULL, &_error);
-               if (_error) {
-                       if (error) *error = _error;
-                       return NULL;
+       if (element == NULL)
+               return NULL;
+
+       /* If it's not an anchor, check if it has a parent anchor
+        * (e.g. it's an image within an anchor) and grab a pointer
+        * to that. */
+       if (strcmp(element->get_tagName(), "a") && element->parent()) {
+               el = element->parent();
+               while (el && el != m_html->root() && strcmp(el->get_tagName(), "a")) {
+                       el = el->parent();
                }
+
+               if (!el || el == m_html->root())
+                       return NULL;
+       } else {
+               el = element;
        }
-               
-       stream = NULL;
-
-       if (!strncmp(url, "file:///", 8) || g_file_test(url, G_FILE_TEST_EXISTS)) {
-               gchar* newurl = g_filename_from_uri(url, NULL, NULL);
-               if (g_file_get_contents(newurl ? newurl : url, &content, &len, &_error)) {
-                       stream = g_memory_input_stream_new_from_data(content, len, g_free);
-               } else {
-                       g_log(NULL, G_LOG_LEVEL_MESSAGE, "%s", _error->message);
+
+       /* At this point, over_el is pointing at an anchor tag, so let's
+        * grab its href attribute. */
+       return el->get_attr("href");
+}
+
+void lh_widget::print()
+{
+    debug_print("lh_widget print\n");
+    gtk_widget_realize(GTK_WIDGET(m_drawing_area));
+}
+
+void lh_widget::popup_context_menu(const char *url,
+               GdkEventButton *event)
+{
+       cm_return_if_fail(url != NULL);
+       cm_return_if_fail(event != NULL);
+
+       debug_print("lh_widget showing context menu for '%s'\n", url);
+
+       m_clicked_url = url;
+       gtk_widget_show_all(m_context_menu);
+       gtk_menu_popup_at_pointer(GTK_MENU(m_context_menu), (GdkEvent *)event);
+}
+
+void lh_widget::update_font()
+{
+       PangoFontDescription *pd =
+               pango_font_description_from_string(lh_prefs_get()->default_font);
+       gboolean absolute = pango_font_description_get_size_is_absolute(pd);
+
+       g_free(m_font_name);
+       m_font_name = g_strdup(pango_font_description_get_family(pd));
+       m_font_size = pango_font_description_get_size(pd);
+
+       pango_font_description_free(pd);
+
+       if (!absolute)
+               m_font_size /= PANGO_SCALE;
+
+       debug_print("Font set to '%s', size %d\n", m_font_name, m_font_size);
+}
+
+const litehtml::string lh_widget::fullurl(const char *url) const
+{
+       if (*url == '#' && !m_base_url.empty())
+               return m_base_url + url;
+
+       return url;
+}
+
+void lh_widget::set_partinfo(MimeInfo *partinfo)
+{
+       m_partinfo = partinfo;
+}
+
+GdkPixbuf *lh_widget::get_local_image(const litehtml::string url) const
+{
+       GdkPixbuf *pixbuf;
+       const gchar *name;
+       MimeInfo *p = m_partinfo;
+
+       if (strncmp(url.c_str(), "cid:", 4) != 0) {
+               debug_print("lh_widget::get_local_image: '%s' is not a local URI, ignoring\n", url.c_str());
+               return NULL;
+       }
+
+       name = url.c_str() + 4;
+       debug_print("getting message part '%s'\n", name);
+
+       while ((p = procmime_mimeinfo_next(p)) != NULL) {
+               size_t len = strlen(name);
+
+               /* p->id is in format "<partname>" */
+               if (p->id != NULL &&
+                               strlen(p->id) >= len + 2 &&
+                               !strncasecmp(name, p->id + 1, len) &&
+                               *(p->id + len + 1) == '>') {
+                       GError *error = NULL;
+
+                       pixbuf = procmime_get_part_as_pixbuf(p, &error);
+                       if (error != NULL) {
+                               g_warning("couldn't load image: %s", error->message);
+                               g_error_free(error);
+                               return NULL;
+                       }
+
+                       return pixbuf;
                }
-               g_free(newurl);
-       } else {
-               curl = curl_easy_init();
-               if (!curl) return NULL;
-               curl_easy_setopt(curl, CURLOPT_URL, url);
-               curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, handle_returned_data);
-               curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, handle_returned_header);
-               curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
-               curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_GET_TIMEOUT);
-               curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
-               res = curl_easy_perform(curl);
-               curl_easy_cleanup(curl);
-               if (res == CURLE_OK) {
-                       stream = g_memory_input_stream_new_from_data(content, response_size, g_free);
-               } else
-                       _error = g_error_new_literal(G_FILE_ERROR, res, curl_easy_strerror(res));
        }
 
-       if (error && _error) *error = _error;
+       /* MIME part with requested name was not found */
+       return NULL;
+}
 
-       return stream;
+void lh_widget::set_cairo_context(cairo_t *cr)
+{
+       m_cairo_context = cr;
 }
 
-static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
+
+////////////////////////////////////////////////
+static gboolean draw_cb(GtkWidget *widget, cairo_t *cr,
                gpointer user_data)
 {
        lh_widget *w = (lh_widget *)user_data;
+       w->set_cairo_context(cr);
        w->redraw();
+       w->set_cairo_context(NULL);
        return FALSE;
 }
 
-static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
+static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
                gpointer user_data)
 {
+       litehtml::position::vector redraw_boxes;
        lh_widget *w = (lh_widget *)user_data;
 
-       g_log(NULL, G_LOG_LEVEL_MESSAGE, "size_allocate_cb: %dx%d",
-                       allocation->width, allocation->height);
+       if (w->m_html == NULL)
+               return FALSE;
 
-       w->setHeight(allocation->height);
-       w->redraw();
+       //debug_print("lh_widget on_button_press_event\n");
+
+       if (event->type == GDK_2BUTTON_PRESS ||
+                       event->type == GDK_3BUTTON_PRESS)
+               return TRUE;
+
+       /* Right-click */
+       if (event->button == 3) {
+               const char *url = w->get_href_at(w->m_html->get_over_element());
+
+               if (url != NULL)
+                       w->popup_context_menu(url, event);
+
+               return TRUE;
+       }
+
+       if(w->m_html->on_lbutton_down((int) event->x, (int) event->y,
+                               (int) event->x, (int) event->y, redraw_boxes)) {
+               for(auto& pos : redraw_boxes) {
+                       debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
+                       gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
+               }
+       }
+       
+       return TRUE;
 }
 
-static size_t handle_returned_data(char* ptr, size_t size, size_t nmemb, void* stream) {
-       if (!response_data)
-               response_data = (char*)malloc(size*nmemb);
-       else
-               response_data = (char*)realloc(response_data, response_size+size*nmemb);
-       if (response_data) {
-               memcpy(response_data+response_size, ptr, size*nmemb);
-               response_size += size*nmemb;
+static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
+        gpointer user_data)
+{
+    litehtml::position::vector redraw_boxes;
+    lh_widget *w = (lh_widget *)user_data;
+    
+    //debug_print("lh_widget on_motion_notify_event\n");
+
+    if(w->m_html)
+    {    
+        if(w->m_html->on_mouse_over((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
+        {
+            for (auto& pos : redraw_boxes)
+            {
+               debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
+                gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
+            }
+        }
        }
-       return size*nmemb;
+       
+       return TRUE;
 }
 
-static size_t handle_returned_header(void* ptr, size_t size, size_t nmemb, void* stream) {
-       char* header = NULL;
+static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
+        gpointer user_data)
+{
+    litehtml::position::vector redraw_boxes;
+    lh_widget *w = (lh_widget *)user_data;
 
-       header = (char*) malloc(size*nmemb + 1);
-       memcpy(header, ptr, size*nmemb);
-       header[size*nmemb] = 0;
-       if (strncmp(header, "Content-Type: ", 14) == 0) {
-               char* stop = header + 14;
-               stop = strpbrk(header + 14, "\r\n;");
-               if (stop) *stop = 0;
-               response_mime = strdup(header + 14);
-       }
-       free(header);
-       return size*nmemb;
+       if (w->m_html == NULL)
+               return FALSE;
+
+       //debug_print("lh_widget on_button_release_event\n");
+
+       if (event->type == GDK_2BUTTON_PRESS ||
+                       event->type == GDK_3BUTTON_PRESS)
+               return TRUE;
+
+       /* Right-click */
+       if (event->button == 3)
+               return TRUE;
+
+       w->m_clicked_url.clear();
+
+    if(w->m_html->on_lbutton_up((int) event->x, (int) event->y, (int) event->x, (int) event->y, redraw_boxes))
+    {
+        for (auto& pos : redraw_boxes)
+        {
+            debug_print("x: %d y:%d w: %d h: %d\n", pos.x, pos.y, pos.width, pos.height);
+            gtk_widget_queue_draw_area(widget, pos.x, pos.y, pos.width, pos.height);
+        }
+    }
+
+    if (!w->m_clicked_url.empty())
+    {
+            debug_print("Open in browser: %s\n", w->m_clicked_url.c_str());
+            open_uri(w->m_clicked_url.c_str(), prefs_common_get_uri_cmd());
+    }
+
+       return TRUE;
+}
+
+static void open_link_cb(GtkMenuItem *item, gpointer user_data)
+{
+       lh_widget_wrapped *w = (lh_widget_wrapped *)user_data;
+
+       open_uri(w->m_clicked_url.c_str(), prefs_common_get_uri_cmd());
+}
+
+static void copy_link_cb(GtkMenuItem *item, gpointer user_data)
+{
+       lh_widget_wrapped *w = (lh_widget_wrapped *)user_data;
+
+       gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+                       w->m_clicked_url.c_str(), -1);
+       gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
+                       w->m_clicked_url.c_str(), -1);
 }
 
 ///////////////////////////////////////////////////////////
@@ -410,4 +624,13 @@ void lh_widget_destroy(lh_widget_wrapped *w)
        delete w;
 }
 
+void lh_widget_print(lh_widget_wrapped *w) {
+       w->print();
+}
+
+void lh_widget_set_partinfo(lh_widget_wrapped *w, MimeInfo *partinfo)
+{
+       w->set_partinfo(partinfo);
+}
+
 } /* extern "C" */