fix CID 1596595: Resource leaks, and CID 1596594: (CHECKED_RETURN)
[claws.git] / src / plugins / litehtml_viewer / lh_widget.cpp
index 3fcb359c5138505d5a27aede8fc167a3006ca3a6..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
 #include "lh_prefs.h"
 #include "lh_widget.h"
 #include "lh_widget_wrapped.h"
-#include "http.h"
 
 extern "C" {
 const gchar *prefs_common_get_uri_cmd(void);
 }
 
-char master_css[] = {
-#include "css.inc"
-};
-
-static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
-               gpointer user_data);
-static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
+static gboolean draw_cb(GtkWidget *widget, cairo_t *cr,
                gpointer user_data);
 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
                gpointer user_data);
@@ -65,12 +56,13 @@ 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);
@@ -82,8 +74,8 @@ 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",
@@ -106,7 +98,15 @@ lh_widget::lh_widget()
 
        m_html = NULL;
        m_rendered_width = 0;
-       m_context.load_master_stylesheet(master_css);
+
+       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
@@ -121,6 +121,7 @@ lh_widget::~lh_widget()
        g_object_unref(m_scrolled_window);
        m_scrolled_window = NULL;
        m_html = NULL;
+       g_free(m_font_name);
 }
 
 GtkWidget *lh_widget::get_widget() const
@@ -128,30 +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)
 {
        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)
 {
-       debug_print("lh_widget set_base_url\n");
+       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();
+
        return;
 }
 
-void lh_widget::on_anchor_click(const litehtml::tchar_t* url, const litehtml::element::ptr& el)
+void lh_widget::on_anchor_click(const char *url, const litehtml::element::ptr& el)
 {
        debug_print("lh_widget on_anchor_click. url -> %s\n", url);
-       m_clicked_url = 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)
 {
-       debug_print("lh_widget import_css\n");
-       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
@@ -168,66 +174,37 @@ void lh_widget::get_client_rect(litehtml::position& client) const
 //                     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;
-       http* http_loader = NULL;
+       gint num = clear_images(lh_prefs_get()->image_cache_size * 1024 * 1000);
+       GtkAdjustment *adj;
 
-       if (!lh_prefs_get()->enable_remote_content) {
-               debug_print("blocking download of image from '%s'\n", url);
-               return NULL;
-       }
-
-       debug_print("Loading... %s\n", url);
-       gchar *msg = g_strdup_printf("Loading %s ...", url);
-        lh_widget_statusbar_push(msg);
-       g_free(msg);
-       
-       http_loader = new http();
-       GInputStream *image = http_loader->load_url(url, &error);
-    
-       if (error || !image) {
-           if (error) {
-               g_warning("lh_widget::get_image: Could not create pixbuf %s", error->message);
-               g_clear_error(&error);
-           }
-           goto statusbar_pop;
-       }
+       debug_print("LH: cleared %d images from image cache\n", num);
 
-       pixbuf = gdk_pixbuf_new_from_stream(image, NULL, &error);
-       if (error) {
-           g_warning("lh_widget::get_image: Could not create pixbuf %s", error->message);
-           pixbuf = NULL;
-           g_clear_error(&error);
-       }
-
-/*     if (redraw_on_ready) {
-               redraw();
-       }*/
+       update_font();
 
-statusbar_pop:
-       lh_widget_statusbar_pop();
-       if (http_loader) {
-               delete http_loader;
-       }
-       
-       return pixbuf;
-}
-
-void lh_widget::open_html(const gchar *contents)
-{
-       clear_images(lh_prefs_get()->image_cache_size * 1024 * 1000);
        lh_widget_statusbar_push("Loading HTML part ...");
-       m_html = litehtml::document::createFromString(contents, this, &m_context);
+       m_html = litehtml::document::createFromString(contents, this);
        m_rendered_width = 0;
        if (m_html != NULL) {
                debug_print("lh_widget::open_html created document\n");
-               redraw();
+               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;
        }
        lh_widget_statusbar_pop();
 }
 
+void lh_widget::rerender()
+{
+       m_force_render = true;
+       gtk_widget_queue_draw(m_drawing_area);
+}
+
 void lh_widget::draw(cairo_t *cr)
 {
        double x1, x2, y1, y2;
@@ -253,21 +230,23 @@ void lh_widget::draw(cairo_t *cr)
 void lh_widget::redraw()
 {
        GtkAllocation rect;
-       gint width, height;
+       gint width;
        GdkWindow *gdkwin;
        cairo_t *cr;
-
-       paint_white();
+       cairo_region_t *creg;
+       GdkDrawingContext *gdkctx;
+       gboolean destroy = FALSE;
 
        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) {
+       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);
 
@@ -286,112 +265,114 @@ void lh_widget::redraw()
                                m_html->width(), m_html->height());
        }
 
-       /* Paint the rendered HTML. */
-       gdkwin = gtk_widget_get_window(m_drawing_area);
-       if (gdkwin == NULL) {
-               g_warning("lh_widget::redraw: No GdkWindow to draw on!");
-               return;
+       /* 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;
        }
-       cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
-       draw(cr);
-
-       cairo_destroy(cr);
-}
 
-void lh_widget::paint_white()
-{
-       GdkWindow *gdkwin = gtk_widget_get_window(m_drawing_area);
-       if (gdkwin == NULL) {
-               g_warning("lh_widget::clear: 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);
        }
-       cairo_t *cr = gdk_cairo_create(GDK_DRAWABLE(gdkwin));
 
-       /* 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);
-
-       cairo_destroy(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);
+       }
 }
+
 void lh_widget::clear()
 {
        m_html = nullptr;
-       paint_white();
+       m_blank = true;
        m_rendered_width = 0;
+       m_base_url.clear();
+       m_clicked_url.clear();
 }
 
-void lh_widget::set_cursor(const litehtml::tchar_t* cursor)
+void lh_widget::set_cursor(const char *cursor)
 {
-       if (cursor) {
-               if (m_cursor != cursor) {
-                       m_cursor = cursor;
-                       update_cursor();
-               }
+       litehtml::element::const_ptr over_el = m_html->get_over_element();
+
+       if (m_showing_url &&
+                       (over_el == NULL || over_el != m_over_element)) {
+               lh_widget_statusbar_pop();
+               m_showing_url = FALSE;
+       }
+
+       if (over_el != m_over_element) {
+               m_over_element = over_el;
+               update_cursor(cursor);
        }
 }
 
-void lh_widget::update_cursor()
+void lh_widget::update_cursor(const char *cursor)
 {
-       gint x, y;
-       const litehtml::tchar_t *href;
-       GdkWindow *w = gdk_display_get_window_at_pointer(gdk_display_get_default(),
-                       &x, &y);
        GdkCursorType cursType = GDK_ARROW;
+       const char *href = get_href_at(m_over_element);
 
-       if (m_cursor == _t("pointer")) {
+       /* 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(cursType));
+               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 (w != gtk_widget_get_window(m_drawing_area))
-               return;
-
-       /* If it's an anchor, show its "href" attribute in statusbar,
-        * otherwise clear statusbar. */
-       if ((href = get_href_at(x, y)) != NULL) {
-               lh_widget_statusbar_push(href);
-       } else {
-               lh_widget_statusbar_pop();
+       /* If there is a href, show it in statusbar */
+       if (href != NULL) {
+               lh_widget_statusbar_push(fullurl(href).c_str());
+               m_showing_url = TRUE;
        }
 }
 
-const litehtml::tchar_t *lh_widget::get_href_at(const gint x, const gint y) const
+const char *lh_widget::get_href_at(litehtml::element::const_ptr element)
 {
-       litehtml::element::ptr over_el, el;
-
-       if (m_html == NULL)
-               return NULL;
+       litehtml::element::const_ptr el;
 
-       over_el = m_html->root()->get_element_by_point(x, y, x, y);
-       if (over_el == 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(over_el->get_tagName(), "a") && over_el->parent()) {
-               el = over_el->parent();
+       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())
-                       over_el = el;
-               else
+               if (!el || el == m_html->root())
                        return NULL;
+       } else {
+               el = element;
        }
 
        /* At this point, over_el is pointing at an anchor tag, so let's
         * grab its href attribute. */
-       return over_el->get_attr(_t("href"));
+       return el->get_attr("href");
 }
 
 void lh_widget::print()
@@ -400,7 +381,7 @@ void lh_widget::print()
     gtk_widget_realize(GTK_WIDGET(m_drawing_area));
 }
 
-void lh_widget::popup_context_menu(const litehtml::tchar_t *url,
+void lh_widget::popup_context_menu(const char *url,
                GdkEventButton *event)
 {
        cm_return_if_fail(url != NULL);
@@ -410,28 +391,94 @@ void lh_widget::popup_context_menu(const litehtml::tchar_t *url,
 
        m_clicked_url = url;
        gtk_widget_show_all(m_context_menu);
-       gtk_menu_popup(GTK_MENU(m_context_menu), NULL, NULL, NULL, NULL,
-                       event->button, event->time);
+       gtk_menu_popup_at_pointer(GTK_MENU(m_context_menu), (GdkEvent *)event);
 }
 
-static gboolean expose_event_cb(GtkWidget *widget, GdkEvent *event,
-               gpointer user_data)
+void lh_widget::update_font()
 {
-       lh_widget *w = (lh_widget *)user_data;
-       w->redraw();
-       return FALSE;
+       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);
 }
 
-static void size_allocate_cb(GtkWidget *widget, GdkRectangle *allocation,
-               gpointer user_data)
+const litehtml::string lh_widget::fullurl(const char *url) const
 {
-       lh_widget *w = (lh_widget *)user_data;
+       if (*url == '#' && !m_base_url.empty())
+               return m_base_url + url;
 
-       debug_print("size_allocate_cb: %dx%d\n",
-                       allocation->width, allocation->height);
+       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;
 
-       w->setHeight(allocation->height);
+       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;
+               }
+       }
+
+       /* MIME part with requested name was not found */
+       return NULL;
+}
+
+void lh_widget::set_cairo_context(cairo_t *cr)
+{
+       m_cairo_context = cr;
+}
+
+
+////////////////////////////////////////////////
+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 gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
@@ -441,22 +488,22 @@ static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
        lh_widget *w = (lh_widget *)user_data;
 
        if (w->m_html == NULL)
-               return false;
+               return FALSE;
 
-       debug_print("lh_widget on_button_press_event\n");
+       //debug_print("lh_widget on_button_press_event\n");
 
        if (event->type == GDK_2BUTTON_PRESS ||
                        event->type == GDK_3BUTTON_PRESS)
-               return true;
+               return TRUE;
 
        /* Right-click */
        if (event->button == 3) {
-               const litehtml::tchar_t *url = w->get_href_at((gint)event->x, (gint)event->y);
+               const char *url = w->get_href_at(w->m_html->get_over_element());
 
                if (url != NULL)
                        w->popup_context_menu(url, event);
 
-               return true;
+               return TRUE;
        }
 
        if(w->m_html->on_lbutton_down((int) event->x, (int) event->y,
@@ -467,7 +514,7 @@ static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event,
                }
        }
        
-       return true;
+       return TRUE;
 }
 
 static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
@@ -480,7 +527,6 @@ static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
 
     if(w->m_html)
     {    
-       //if(m_cursor == _t("pointer"))
         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)
@@ -491,7 +537,7 @@ static gboolean motion_notify_event(GtkWidget *widget, GdkEventButton *event,
         }
        }
        
-       return true;
+       return TRUE;
 }
 
 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
@@ -499,20 +545,19 @@ static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
 {
     litehtml::position::vector redraw_boxes;
     lh_widget *w = (lh_widget *)user_data;
-    GError* error = NULL;
 
        if (w->m_html == NULL)
-               return false;
+               return FALSE;
 
-       debug_print("lh_widget on_button_release_event\n");
+       //debug_print("lh_widget on_button_release_event\n");
 
        if (event->type == GDK_2BUTTON_PRESS ||
                        event->type == GDK_3BUTTON_PRESS)
-               return true;
+               return TRUE;
 
        /* Right-click */
        if (event->button == 3)
-               return true;
+               return TRUE;
 
        w->m_clicked_url.clear();
 
@@ -531,7 +576,7 @@ static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event,
             open_uri(w->m_clicked_url.c_str(), prefs_common_get_uri_cmd());
     }
 
-       return true;
+       return TRUE;
 }
 
 static void open_link_cb(GtkMenuItem *item, gpointer user_data)
@@ -583,4 +628,9 @@ 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" */