Check for NULLs params within the functions
[claws.git] / src / stock_pixmap.c
index 70ff84e1e7454fd8d15699435cf8b1295cdf5ae2..807d862a51de4d96af0d604de41d534e8a597033 100644 (file)
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include "defs.h"
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#include "claws-features.h"
+#endif
+
 #include <glib.h>
 #include <gtk/gtk.h>
 #include <string.h>
 #include <dirent.h>
+#ifdef HAVE_SVG
+#include <librsvg/rsvg.h>
+#include <math.h>
+#endif
 
-#include "config.h"
+#include "defs.h"
 #include "stock_pixmap.h"
 #include "gtkutils.h"
 #include "utils.h"
 #include "pixmaps/down_arrow.xpm"
 #include "pixmaps/up_arrow.xpm"
 #include "pixmaps/exec.xpm"
-#include "pixmaps/mail.xpm"
+#include "pixmaps/mail_draft.xpm"
 #include "pixmaps/mail_attach.xpm"
 #include "pixmaps/mail_compose.xpm"
 #include "pixmaps/mail_forward.xpm"
+#include "pixmaps/mail_privacy_encrypted.xpm"
+#include "pixmaps/mail_privacy_signed.xpm"
 #include "pixmaps/mail_receive.xpm"
 #include "pixmaps/mail_receive_all.xpm"
 #include "pixmaps/mail_reply.xpm"
 #include "pixmaps/mime_calendar.xpm"
 #include "pixmaps/mime_pgpsig.xpm"
 #include "pixmaps/printer.xpm"
+#include "pixmaps/printer_btn.xpm"
 #include "pixmaps/privacy_signed.xpm"
 #include "pixmaps/privacy_passed.xpm"
 #include "pixmaps/privacy_failed.xpm"
@@ -313,10 +324,12 @@ static StockPixmapData pixmaps[] =
     {linewrapcurrent_xpm              , NULL, NULL, "linewrapcurrent", NULL, NULL},
     {linewrap_xpm                     , NULL, NULL, "linewrap", NULL, NULL},
     {locked_xpm                       , NULL, NULL, "locked", NULL, NULL},
-    {mail_xpm                         , NULL, NULL, "mail", NULL, NULL},
+    {mail_draft_xpm                   , NULL, NULL, "mail_draft", NULL, NULL},
     {mail_attach_xpm                  , NULL, NULL, "mail_attach", NULL, NULL},
     {mail_compose_xpm                 , NULL, NULL, "mail_compose", NULL, NULL},
     {mail_forward_xpm                 , NULL, NULL, "mail_forward", NULL, NULL},
+    {mail_privacy_encrypted_xpm       , NULL, NULL, "mail_privacy_encrypted", NULL, NULL},
+    {mail_privacy_signed_xpm          , NULL, NULL, "mail_privacy_signed", NULL, NULL},
     {mail_receive_xpm                 , NULL, NULL, "mail_receive", NULL, NULL},
     {mail_receive_all_xpm             , NULL, NULL, "mail_receive_all", NULL, NULL},
     {mail_reply_xpm                   , NULL, NULL, "mail_reply", NULL, NULL},
@@ -385,6 +398,7 @@ static StockPixmapData pixmaps[] =
     {mime_ps_xpm                      , NULL, NULL, "mime_ps", NULL, NULL},
     {mime_calendar_xpm                , NULL, NULL, "mime_calendar", NULL, NULL},
     {mime_pgpsig_xpm                  , NULL, NULL, "mime_pgpsig", NULL, NULL},
+    {printer_btn_xpm                  , NULL, NULL, "printer_btn", NULL, NULL},
     {printer_xpm                      , NULL, NULL, "printer", NULL, NULL},
     {privacy_signed_xpm               , NULL, NULL, "privacy_signed", NULL, NULL},
     {privacy_passed_xpm               , NULL, NULL, "privacy_passed", NULL, NULL},
@@ -464,6 +478,9 @@ static StockPixmapData pixmaps[] =
 static const char *extension[] = {
        ".png",
        ".xpm",
+#ifdef HAVE_SVG
+       ".svg",
+#endif
        NULL
 };
 
@@ -486,6 +503,180 @@ GtkWidget *stock_pixmap_widget(StockPixmap icon)
        return NULL;
 }
 
+#ifdef HAVE_SVG
+/*
+ * Renders a SVG into a Cairo context at the given dimensions keeping
+ * the aspect ratio.
+ *
+ * Adapted from https://developer.gnome.org/rsvg/2.40/RsvgHandle.html
+ * #rsvg-handle-set-size-callback
+ */
+void render_scaled_proportionally(RsvgHandle *handle, cairo_t *cr, int width, int height)
+{
+       RsvgDimensionData dimensions;
+       double x_factor, y_factor;
+       double scale_factor;
+
+       rsvg_handle_get_dimensions(handle, &dimensions);
+
+       x_factor = (double) width / dimensions.width;
+       y_factor = (double) height / dimensions.height;
+
+       scale_factor = MIN(x_factor, y_factor);
+
+       cairo_scale(cr, scale_factor, scale_factor);
+
+       rsvg_handle_render_cairo(handle, cr);
+}
+
+/*
+ * Generates a new Pixbuf from a Cairo context of the given dimensions.
+ *
+ * Adapted from https://gist.github.com/bert/985903
+ */
+GdkPixbuf *pixbuf_from_cairo(cairo_t *cr, gboolean alpha, int width, int height)
+{
+       gint p_stride, /* Pixbuf stride value */
+            p_n_channels, /* RGB -> 3, RGBA -> 4 */
+            s_stride; /* Surface stride value */
+       guchar *p_pixels, /* Pixbuf's pixel data */
+              *s_pixels; /* Surface's pixel data */
+       cairo_surface_t *surface; /* Temporary image surface */
+       GdkPixbuf *pixbuf; /* Returned pixbuf */
+
+       /* Create pixbuf */
+       pixbuf = gdk_pixbuf_new
+                       (GDK_COLORSPACE_RGB, alpha, 8, width, height);
+       if (pixbuf == NULL) {
+               g_warning("failed to create a new %d x %d pixbuf", width, height);
+               return NULL;
+       }
+       /* Obtain surface from where pixel values will be copied */
+       surface = cairo_get_target(cr);
+       if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+               g_warning("invalid cairo surface for copying");
+               return NULL;
+       }
+       /* Inspect pixbuf */
+       g_object_get(G_OBJECT(pixbuf),
+               "rowstride", &p_stride,
+               "n-channels", &p_n_channels,
+               "pixels", &p_pixels,
+               NULL);
+       /* and surface */
+       s_stride = cairo_image_surface_get_stride(surface);
+       s_pixels = cairo_image_surface_get_data(surface);
+
+       /* Copy pixel data from surface to pixbuf */
+       while (height--) {
+               gint i;
+               guchar *p_iter = p_pixels, *s_iter = s_pixels;
+               for (i = 0; i < width; i++) {
+#if G_BYTE_ORDER == G_LITTLE_ENDIAN
+                       /* Pixbuf: RGB(A) - Surface: BGRA */
+                       gdouble alpha_factor = (gdouble)0xff / s_iter[3];
+                       p_iter[0] = (guchar)( s_iter[2] * alpha_factor + .5 );
+                       p_iter[1] = (guchar)( s_iter[1] * alpha_factor + .5 );
+                       p_iter[2] = (guchar)( s_iter[0] * alpha_factor + .5 );
+                       if (p_n_channels == 4)
+                                p_iter[3] = s_iter[3];
+#elif G_BYTE_ORDER == G_BIG_ENDIAN
+                       /* Pixbuf: RGB(A) - Surface: ARGB */
+                       gdouble alpha_factor = (gdouble)0xff / s_iter[0];
+                       p_iter[0] = (guchar)( s_iter[1] * alpha_factor + .5 );
+                       p_iter[1] = (guchar)( s_iter[2] * alpha_factor + .5 );
+                       p_iter[2] = (guchar)( s_iter[3] * alpha_factor + .5 );
+                       if (p_n_channels == 4)
+                               p_iter[3] = s_iter[0];
+#else /* PDP endianness */
+                       /* Pixbuf: RGB(A) - Surface: RABG */
+                       gdouble alpha_factor = (gdouble)0xff / s_iter[1];
+                       p_iter[0] = (guchar)( s_iter[0] * alpha_factor + .5 );
+                       p_iter[1] = (guchar)( s_iter[3] * alpha_factor + .5 );
+                       p_iter[2] = (guchar)( s_iter[2] * alpha_factor + .5 );
+                       if (p_n_channels == 4)
+                               p_iter[3] = s_iter[1];
+#endif
+                       s_iter += 4;
+                       p_iter += p_n_channels;
+               }
+               s_pixels += s_stride;
+               p_pixels += p_stride;
+       }
+       /* Destroy context */
+       cairo_destroy(cr);
+
+       return pixbuf;
+}
+
+/*
+ * Renders a SVG file into a pixbuf with the dimensions of the
+ * given pixmap data (optionally with alpha channel).
+ */
+GdkPixbuf *pixbuf_from_svg_like_icon(char *filename, GError **error, StockPixmapData *icondata, gboolean alpha)
+{
+       int width, height;
+       cairo_surface_t *surface;
+       cairo_t *context;
+       RsvgHandle *handle;
+
+       cm_return_val_if_fail(filename != NULL, NULL);
+       cm_return_val_if_fail(icondata != NULL, NULL);
+
+       /* load SVG file */
+       handle = rsvg_handle_new_from_file(filename, error);
+       if (handle == NULL) {
+               g_warning("failed loading SVG '%s': %s (%d)", filename,
+                               (*error)->message, (*error)->code);
+               return NULL;
+       }
+
+       /* scale dimensions */
+       if (prefs_common.enable_pixmap_scaling) {
+               /* default is pixmap icon size */
+               if (sscanf((icondata->data)[0], "%d %d ", &width, &height) != 2) {
+                       g_warning("failed reading icondata width and height");
+                       return NULL;
+               }
+               /* which can be modified by some factor */
+               if (prefs_common.pixmap_scaling_ppi > 0) {
+                       gdouble factor = (gdouble) prefs_common.pixmap_scaling_ppi / MIN_PPI;
+                       width = (int) floor(factor * width);
+                       height = (int) floor(factor * height);
+               }
+       } else { /* render using SVG size */
+               RsvgDimensionData dimension;
+
+               rsvg_handle_get_dimensions (handle, &dimension);
+               width = dimension.width;
+               height = dimension.height;
+       }
+
+       /* create drawing context */
+       surface = cairo_image_surface_create(
+                       alpha? CAIRO_FORMAT_ARGB32: CAIRO_FORMAT_RGB24,
+                       width, height);
+       if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
+               g_warning("failed to create a cairo surface: %s",
+                               cairo_status_to_string(cairo_surface_status(surface)));
+               g_object_unref(handle);
+               return NULL;
+       }
+       context = cairo_create(surface);
+       cairo_surface_destroy(surface);
+       if (cairo_status(context) != CAIRO_STATUS_SUCCESS) {
+               g_warning("failed to create a cairo context: %s",
+                               cairo_status_to_string(cairo_status(context)));
+               cairo_destroy(context);
+               return NULL;
+       }
+       /* render SVG */
+       render_scaled_proportionally(handle, context, width, height);
+       /* build result and destroy context */
+       return pixbuf_from_cairo(context, alpha, width, height);
+}
+#endif
+
 /*!
  *\brief
  */
@@ -522,7 +713,16 @@ try_next_extension:
                                                             NULL);
                                if (is_file_exist(icon_file_name)) {
                                        GError *err = NULL;
+#ifdef HAVE_SVG
+                                       if (!strncmp(extension[i], ".svg", 4)) {
+                                               pix = pixbuf_from_svg_like_icon(icon_file_name, &err, pix_d,
+                                                               prefs_common.enable_alpha_svg);
+                                       } else {
+                                               pix = gdk_pixbuf_new_from_file(icon_file_name, &err);
+                                       }
+#else
                                        pix = gdk_pixbuf_new_from_file(icon_file_name, &err);
+#endif
                                        if (err) g_error_free(err);
                                }
                                if (pix) {
@@ -640,11 +840,32 @@ void stock_pixmap_themes_list_free(GList *list)
 {
        GList *ptr;
 
+       cm_return_if_fail(list != NULL);
+
        for (ptr = g_list_first(list); ptr != NULL; ptr = g_list_next(ptr))
                g_free(ptr->data);
        g_list_free(list);
 }
 
+void stock_pixmap_invalidate_all_icons(void)
+{
+       StockPixmapData *pix_d;
+       int i = 0;
+
+       while (i < N_STOCK_PIXMAPS) {
+               pix_d = &pixmaps[i];
+               if (pix_d->pixbuf) {
+                       g_object_unref(G_OBJECT(pix_d->pixbuf));
+                       pix_d->pixbuf = NULL;
+               }
+               if (pix_d->pixmap) {
+                       g_object_unref(G_OBJECT(pix_d->pixmap));
+                       pix_d->pixmap = NULL;
+               }
+               i++;
+       }
+}
+
 gchar *stock_pixmap_get_name (StockPixmap icon)
 {
        if (icon >= N_STOCK_PIXMAPS)