Update Spanish translation
[gnumeric.git] / src / sheet-object-image.c
blobe23fefe7bbefc39ff63fd3eb893538c1199277cc
2 /*
3 * sheet-object-image.c: a wrapper for gdkpixbuf to display images.
5 * Author:
6 * Jody Goldberg (jody@gnome.org)
7 */
8 #include <gnumeric-config.h>
9 #include <gnm-i18n.h>
10 #include <gnumeric.h>
11 #include <sheet-object-image.h>
12 #include <sheet.h>
14 #include <gnm-pane.h>
15 #include <wbc-gtk.h>
16 #include <sheet-object-impl.h>
17 #include <sheet-control-gui.h>
18 #include <gui-file.h>
19 #include <application.h>
20 #include <gutils.h>
21 #include <xml-sax.h>
23 #include <goffice/goffice.h>
24 #include <gsf/gsf-impl-utils.h>
25 #include <gsf/gsf-output-stdio.h>
26 #include <gsf/gsf-utils.h>
28 #include <math.h>
29 #include <string.h>
30 #define DISABLE_DEBUG
31 #ifndef DISABLE_DEBUG
32 #define d(code) do { code; } while (0)
33 #else
34 #define d(code)
35 #endif
37 #define CC2XML(s) ((xmlChar const *)(s))
38 #define CXML2C(s) ((char const *)(s))
40 static inline gboolean
41 attr_eq (const xmlChar *a, const char *s)
43 return !strcmp (CXML2C (a), s);
46 static void
47 so_image_view_set_bounds (SheetObjectView *sov, double const *coords, gboolean visible)
49 GocItem *view = GOC_ITEM (GOC_GROUP (sov)->children->data);
50 double scale = goc_canvas_get_pixels_per_unit (view->canvas);
52 if (visible) {
53 double x, y, width, height;
54 double old_x1, old_y1, old_x2, old_y2, old_width, old_height;
55 GdkPixbuf *placeholder = g_object_get_data (G_OBJECT (view), "tile");
57 x = MIN (coords [0], coords [2]) / scale;
58 y = MIN (coords [1], coords [3]) / scale;
59 width = fabs (coords [2] - coords [0]) / scale;
60 height = fabs (coords [3] - coords [1]) / scale;
62 goc_item_get_bounds (view, &old_x1, &old_y1, &old_x2, &old_y2);
63 goc_item_set (view,
64 "x", x, "y", y,
65 "width", width, "height", height,
66 NULL);
68 /* regenerate the image if necessary */
69 old_width = fabs (old_x1 - old_x2);
70 old_height = fabs (old_y1 - old_y2);
71 if (placeholder != NULL &&
72 (fabs (width - old_width) > 0.5 || fabs (height - old_height) > 0.5)) {
73 GdkPixbuf *newimage = go_gdk_pixbuf_tile (placeholder,
74 (guint)width, (guint)height);
75 goc_item_set (view, "pixbuf", newimage, NULL);
76 g_object_unref (newimage);
79 goc_item_show (view);
80 } else
81 goc_item_hide (view);
84 static void
85 so_image_goc_view_class_init (SheetObjectViewClass *sov_klass)
87 sov_klass->set_bounds = so_image_view_set_bounds;
89 typedef SheetObjectView SOImageGocView;
90 typedef SheetObjectViewClass SOImageGocViewClass;
91 static GSF_CLASS (SOImageGocView, so_image_goc_view,
92 so_image_goc_view_class_init, NULL,
93 GNM_SO_VIEW_TYPE)
95 /****************************************************************************/
96 struct _SheetObjectImage {
97 SheetObject sheet_object;
99 GOImage *image;
100 char *type;
101 char *name;
103 double crop_top;
104 double crop_bottom;
105 double crop_left;
106 double crop_right;
109 typedef struct {
110 SheetObjectClass parent_class;
111 } SheetObjectImageClass;
113 static SheetObjectClass *gnm_soi_parent_class;
115 enum {
116 PROP_0,
117 PROP_IMAGE_TYPE,
118 PROP_IMAGE,
119 PROP_PIXBUF
123 * sheet_object_image_set_image:
124 * @soi: #SheetObjectImage
125 * @type:
126 * @data:
127 * @data_len
129 * Assign raw data and type to @soi.
130 * yet.
132 void
133 sheet_object_image_set_image (SheetObjectImage *soi,
134 char const *type,
135 gconstpointer data,
136 unsigned data_len)
138 g_return_if_fail (GNM_IS_SO_IMAGE (soi));
140 g_free (soi->type);
141 soi->type = (type && *type) ? g_strdup (type) : NULL;
142 if (soi->image)
143 g_object_unref (soi->image);
144 soi->image = go_image_new_from_data (soi->type, data, data_len,
145 ((soi->type == NULL)? &soi->type: NULL), NULL);
147 if (soi->sheet_object.sheet != NULL) {
148 /* Share within document. */
149 GOImage *image = go_doc_add_image (GO_DOC (soi->sheet_object.sheet->workbook), NULL, soi->image);
150 if (image != soi->image) {
151 g_object_unref (soi->image);
152 soi->image = g_object_ref (image);
157 void
158 sheet_object_image_set_crop (SheetObjectImage *soi,
159 double crop_left, double crop_top,
160 double crop_right, double crop_bottom)
162 g_return_if_fail (GNM_IS_SO_IMAGE (soi));
164 soi->crop_left = crop_left;
165 soi->crop_top = crop_top;
166 soi->crop_right = crop_right;
167 soi->crop_bottom = crop_bottom;
170 static void
171 gnm_soi_finalize (GObject *object)
173 SheetObjectImage *soi;
175 soi = GNM_SO_IMAGE (object);
176 g_free (soi->type);
177 g_free (soi->name);
178 if (soi->image)
179 g_object_unref (soi->image);
181 G_OBJECT_CLASS (gnm_soi_parent_class)->finalize (object);
184 static SheetObjectView *
185 gnm_soi_new_view (SheetObject *so, SheetObjectViewContainer *container)
187 SheetObjectImage *soi = GNM_SO_IMAGE (so);
188 GocItem *item = NULL;
190 item = goc_item_new (
191 gnm_pane_object_group (GNM_PANE (container)),
192 so_image_goc_view_get_type (),
193 NULL);
194 if (soi->image) {
195 goc_item_hide (goc_item_new (GOC_GROUP (item),
196 GOC_TYPE_IMAGE,
197 "image", soi->image,
198 "crop-bottom", soi->crop_bottom,
199 "crop-left", soi->crop_left,
200 "crop-right", soi->crop_right,
201 "crop-top", soi->crop_top,
202 NULL));
204 } else {
205 GdkPixbuf *placeholder =
206 gdk_pixbuf_new_from_resource ("/org/gnumeric/gnumeric/images/unknown_image.png",
207 NULL);
208 GdkPixbuf *pixbuf = gdk_pixbuf_copy (placeholder);
210 goc_item_hide (goc_item_new (GOC_GROUP (item),
211 GOC_TYPE_PIXBUF,
212 "pixbuf", pixbuf,
213 NULL));
214 g_object_unref (pixbuf);
215 g_object_set_data (G_OBJECT (item), "tile", placeholder);
218 return gnm_pane_object_register (so, item, TRUE);
221 static GtkTargetList *
222 gnm_soi_get_target_list (SheetObject const *so)
224 SheetObjectImage *soi = GNM_SO_IMAGE (so);
225 GtkTargetList *tl = gtk_target_list_new (NULL, 0);
226 char *mime_str;
227 GSList *mimes, *ptr;
228 GdkPixbuf *pixbuf = NULL;
230 if (soi->type == NULL && soi->image != NULL)
231 pixbuf = go_image_get_pixbuf (soi->image);
232 mime_str = go_image_format_to_mime (soi->type);
233 if (mime_str) {
234 mimes = go_strsplit_to_slist (mime_str, ',');
235 for (ptr = mimes; ptr != NULL; ptr = ptr->next) {
236 const char *mime = ptr->data;
238 if (mime != NULL && *mime != '\0')
239 gtk_target_list_add (tl, gdk_atom_intern (mime, FALSE),
240 0, 0);
242 g_free (mime_str);
243 g_slist_free_full (mimes, g_free);
245 /* No need to eliminate duplicates. */
246 if (soi->image != NULL || pixbuf != NULL) {
247 gtk_target_list_add_image_targets (tl, 0, TRUE);
248 if (pixbuf != NULL)
249 g_object_unref (pixbuf);
252 return tl;
255 static void
256 gnm_soi_write_image (SheetObject const *so, char const *format,
257 G_GNUC_UNUSED double resolution,
258 GsfOutput *output, GError **err)
260 SheetObjectImage *soi = GNM_SO_IMAGE (so);
261 gboolean res;
262 gsize length;
263 guint8 const *data;
264 GOImage *image = NULL;
265 GOImageFormatInfo const *src_info;
266 GOImageFormatInfo const *dst_info;
268 g_return_if_fail (soi->image != NULL);
270 src_info = go_image_get_info (soi->image);
271 dst_info = format
272 ? go_image_get_format_info (go_image_get_format_from_name (format))
273 : src_info;
275 if (src_info != dst_info) {
276 GdkPixbuf *pixbuf = go_image_get_pixbuf (soi->image);
277 image = go_pixbuf_new_from_pixbuf (pixbuf);
278 g_object_set (image, "image-type", format, NULL);
279 g_object_unref (pixbuf);
282 data = go_image_get_data (image ? image : soi->image, &length);
283 res = gsf_output_write (output, length, data);
285 if (!res && err && *err == NULL)
286 *err = g_error_new (gsf_output_error_id (), 0,
287 _("Unknown failure while saving image"));
289 if (image) g_object_unref (image);
292 static void
293 soi_cb_save_as (SheetObject *so, SheetControl *sc)
295 WBCGtk *wbcg;
296 char *uri;
297 GsfOutput *output;
298 GSList *l = NULL;
299 GOImageFormat sel_fmt;
300 GOImageFormatInfo const *format_info;
301 GdkPixbuf *pixbuf = NULL;
302 GError *err = NULL;
303 SheetObjectImage *soi = GNM_SO_IMAGE (so);
305 g_return_if_fail (soi != NULL);
307 sel_fmt = go_image_get_format_from_name (soi->type);
308 if ((pixbuf = go_image_get_pixbuf (soi->image)) != NULL)
309 l = go_image_get_formats_with_pixbuf_saver ();
310 /* Move original format first in menu */
311 if (sel_fmt != GO_IMAGE_FORMAT_UNKNOWN) {
312 l = g_slist_remove (l, GUINT_TO_POINTER (sel_fmt));
313 l = g_slist_prepend (l, GUINT_TO_POINTER (sel_fmt));
316 wbcg = scg_wbcg (GNM_SCG (sc));
318 uri = go_gui_get_image_save_info (wbcg_toplevel (wbcg), l, &sel_fmt, NULL);
319 if (!uri)
320 goto out;
322 output = go_file_create (uri, &err);
323 if (!output)
324 goto out;
325 format_info = go_image_get_format_info (sel_fmt);
326 sheet_object_write_image (so, (format_info? format_info->name: NULL), -1.0, output, &err);
327 gsf_output_close (output);
328 g_object_unref (output);
330 if (err != NULL)
331 go_cmd_context_error (GO_CMD_CONTEXT (wbcg), err);
333 out:
334 if (pixbuf)
335 g_object_unref (pixbuf);
336 g_free (uri);
337 g_slist_free (l);
340 static void
341 gnm_soi_populate_menu (SheetObject *so, GPtrArray *actions)
343 static SheetObjectAction const soi_action =
344 { "document-save-as", N_("_Save As Image"), NULL, 0, soi_cb_save_as };
345 gnm_soi_parent_class->populate_menu (so, actions);
346 g_ptr_array_insert (actions, 1, (gpointer) &soi_action);
349 static void
350 content_start (GsfXMLIn *xin, xmlChar const **attrs)
352 SheetObject *so = gnm_xml_in_cur_obj (xin);
353 SheetObjectImage *soi = GNM_SO_IMAGE (so);
354 char const *image_type = NULL, *image_name = NULL;
356 for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2)
357 if (attr_eq (attrs[0], "image-type"))
358 image_type = CXML2C (attrs[1]);
359 else if (attr_eq (attrs[0], "name"))
360 image_name = CXML2C (attrs[1]);
362 g_free (soi->type);
363 soi->type = g_strdup (image_type);
364 if (image_name)
365 soi->name = g_strdup (image_name);
367 static void
368 content_end (GsfXMLIn *xin, G_GNUC_UNUSED GsfXMLBlob *unknown)
370 SheetObject *so = gnm_xml_in_cur_obj (xin);
371 SheetObjectImage *soi = GNM_SO_IMAGE (so);
372 GString *data = xin->content;
374 if (data->len >= 4) {
375 size_t len = gsf_base64_decode_simple (data->str, data->len);
376 if (soi->image)
377 g_object_unref (soi->image);
378 soi->image = go_image_new_from_data (soi->type, data->str, len,
379 NULL, NULL);
383 static void
384 gnm_soi_prep_sax_parser (SheetObject *so, GsfXMLIn *xin,
385 xmlChar const **attrs,
386 G_GNUC_UNUSED GnmConventions const *convs)
388 static GsfXMLInNode const dtd[] = {
389 GSF_XML_IN_NODE (CONTENT, CONTENT, -1, "Content", GSF_XML_CONTENT, &content_start, &content_end),
390 GSF_XML_IN_NODE_END
392 static GsfXMLInDoc *doc = NULL;
393 SheetObjectImage *soi = GNM_SO_IMAGE (so);
395 if (NULL == doc) {
396 doc = gsf_xml_in_doc_new (dtd, NULL);
397 gnm_xml_in_doc_dispose_on_exit (&doc);
399 gsf_xml_in_push_state (xin, doc, NULL, NULL, attrs);
401 for (; attrs != NULL && attrs[0] && attrs[1] ; attrs += 2) {
402 if (gnm_xml_attr_double (attrs, "crop-top", &soi->crop_top)) ;
403 else if (gnm_xml_attr_double (attrs, "crop-bottom", &soi->crop_bottom))
404 /* Nothing */ ;
405 else if (gnm_xml_attr_double (attrs, "crop-left", &soi->crop_left))
406 /* Nothing */ ;
407 else if (gnm_xml_attr_double (attrs, "crop-right", &soi->crop_right))
408 /* Nothing */ ;
412 static void
413 gnm_soi_write_xml_sax (SheetObject const *so, GsfXMLOut *output,
414 G_GNUC_UNUSED GnmConventions const *convs)
416 SheetObjectImage *soi;
418 g_return_if_fail (GNM_IS_SO_IMAGE (so));
419 soi = GNM_SO_IMAGE (so);
421 go_xml_out_add_double (output, "crop-top", soi->crop_top);
422 go_xml_out_add_double (output, "crop-bottom", soi->crop_bottom);
423 go_xml_out_add_double (output, "crop-left", soi->crop_left);
424 go_xml_out_add_double (output, "crop-right", soi->crop_right);
425 gsf_xml_out_start_element (output, "Content");
426 if (soi->type != NULL)
427 gsf_xml_out_add_cstr (output, "image-type", soi->type);
428 if (soi->image) {
429 const char *name = go_image_get_name (soi->image);
430 Sheet *sheet = sheet_object_get_sheet (so);
431 if (name)
432 gsf_xml_out_add_cstr (output, "name", name);
433 if (sheet)
434 go_doc_save_image (GO_DOC (sheet->workbook), go_image_get_name (soi->image));
435 else {
436 /* looks that this may happen when pasting from another process, see #687414 */
437 gsize length;
438 guint8 const *data = go_image_get_data (soi->image, &length);
439 gsf_xml_out_add_uint (output, "size-bytes", length);
440 gsf_xml_out_add_base64 (output, NULL, data, length);
442 } else {
443 gsf_xml_out_add_uint (output, "size-bytes", 0);
445 gsf_xml_out_end_element (output);
448 static void
449 gnm_soi_copy (SheetObject *dst, SheetObject const *src)
451 SheetObjectImage const *soi = GNM_SO_IMAGE (src);
452 SheetObjectImage *new_soi = GNM_SO_IMAGE (dst);
454 new_soi->type = g_strdup (soi->type);
455 new_soi->crop_top = soi->crop_top;
456 new_soi->crop_bottom = soi->crop_bottom;
457 new_soi->crop_left = soi->crop_left;
458 new_soi->crop_right = soi->crop_right;
459 new_soi->image = soi->image ? g_object_ref (soi->image) : NULL;
462 static void
463 gnm_soi_draw_cairo (SheetObject const *so, cairo_t *cr,
464 double width, double height)
466 SheetObjectImage *soi = GNM_SO_IMAGE (so);
468 if (soi->image) {
469 int w = go_image_get_width (soi->image);
470 int h = go_image_get_height (soi->image);
471 w -= soi->crop_left - soi->crop_right;
472 h -= soi->crop_top - soi->crop_bottom;
473 if (w <= 0 || h <= 0)
474 return;
475 cairo_save (cr);
476 cairo_rectangle (cr, 0, 0, width, height);
477 cairo_clip (cr);
478 cairo_scale (cr, width / w, height / h);
479 cairo_translate (cr, -soi->crop_left, -soi->crop_top);
480 go_image_draw (soi->image, cr);
481 cairo_restore (cr);
485 static void
486 gnm_soi_default_size (SheetObject const *so, double *w, double *h)
488 SheetObjectImage *soi = GNM_SO_IMAGE (so);
489 if (soi->image) {
490 *w = go_image_get_width (soi->image);
491 *h = go_image_get_height (soi->image);
492 } else {
493 *w = *h = 5;
497 static gboolean
498 gnm_soi_assign_to_sheet (SheetObject *so, Sheet *sheet)
500 SheetObjectImage *soi = GNM_SO_IMAGE (so);
502 if (soi->image/* && !go_image_get_name (soi->image)*/) {
503 GODoc *doc = GO_DOC (sheet->workbook);
504 GOImage *image = go_doc_add_image
505 (doc,
506 (soi->name != NULL) ? soi->name : go_image_get_name (soi->image),
507 soi->image);
508 if (soi->image != image) {
509 g_object_unref (soi->image);
510 soi->image = g_object_ref (image);
512 } else if (soi->name) {
513 GODoc *doc = GO_DOC (sheet->workbook);
514 GType type = go_image_type_for_format (soi->type);
515 if (type != 0) {
516 soi->image = g_object_ref (go_doc_image_fetch (doc, soi->name, type));
517 if (GO_IS_PIXBUF (soi->image))
518 /* we need to ensure that the pixbuf type is set because it used to be missed, see #745297 */
519 g_object_set (soi->image, "image-type", soi->type, NULL);
521 } else {
522 /* There is nothing we can do */
525 return FALSE;
528 static void
529 gnm_soi_get_property (GObject *object,
530 guint property_id,
531 GValue *value,
532 GParamSpec *pspec)
534 SheetObjectImage *soi = GNM_SO_IMAGE (object);
535 GdkPixbuf *pixbuf;
537 switch (property_id) {
538 case PROP_IMAGE_TYPE:
539 g_value_set_string (value, soi->type);
540 break;
541 case PROP_IMAGE:
542 g_value_set_object (value, soi->image);
543 break;
544 case PROP_PIXBUF:
545 pixbuf = go_image_get_pixbuf (soi->image);
546 g_value_take_object (value, pixbuf);
547 break;
548 default:
549 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
550 break;
554 static void
555 gnm_soi_class_init (GObjectClass *object_class)
557 SheetObjectClass *so_class;
559 gnm_soi_parent_class = g_type_class_peek_parent (object_class);
561 /* Object class method overrides */
562 object_class->finalize = gnm_soi_finalize;
563 object_class->get_property = gnm_soi_get_property;
565 /* SheetObject class method overrides */
566 so_class = GNM_SO_CLASS (object_class);
567 so_class->new_view = gnm_soi_new_view;
568 so_class->populate_menu = gnm_soi_populate_menu;
569 so_class->write_xml_sax = gnm_soi_write_xml_sax;
570 so_class->prep_sax_parser = gnm_soi_prep_sax_parser;
571 so_class->copy = gnm_soi_copy;
572 so_class->draw_cairo = gnm_soi_draw_cairo;
573 so_class->user_config = NULL;
574 so_class->default_size = gnm_soi_default_size;
575 so_class->assign_to_sheet = gnm_soi_assign_to_sheet;
576 so_class->rubber_band_directly = TRUE;
578 /* The property strings don't need translation */
579 g_object_class_install_property (object_class, PROP_IMAGE_TYPE,
580 g_param_spec_string ("image-type",
581 P_("Image type"),
582 P_("Type of image"),
583 NULL,
584 GSF_PARAM_STATIC | G_PARAM_READABLE));
585 g_object_class_install_property (object_class, PROP_IMAGE,
586 g_param_spec_object ("image",
587 P_("Image data"),
588 P_("Image data"),
589 GO_TYPE_IMAGE,
590 GSF_PARAM_STATIC | G_PARAM_READABLE));
591 g_object_class_install_property (object_class, PROP_PIXBUF,
592 g_param_spec_object ("pixbuf", "Pixbuf",
593 "Pixbuf",
594 GDK_TYPE_PIXBUF,
595 GSF_PARAM_STATIC | G_PARAM_READABLE));
598 static void
599 gnm_soi_init (GObject *obj)
601 SheetObjectImage *soi;
602 SheetObject *so;
604 soi = GNM_SO_IMAGE (obj);
605 soi->crop_top = soi->crop_bottom = soi->crop_left = soi->crop_right
606 = 0.0;
608 so = GNM_SO (obj);
609 so->anchor.base.direction = GOD_ANCHOR_DIR_DOWN_RIGHT;
610 so->anchor.mode = GNM_SO_ANCHOR_ONE_CELL;
613 static void
614 soi_imageable_init (SheetObjectImageableIface *soi_iface)
616 soi_iface->get_target_list = gnm_soi_get_target_list;
617 soi_iface->write_image = gnm_soi_write_image;
620 GSF_CLASS_FULL (SheetObjectImage, sheet_object_image,
621 NULL, NULL, gnm_soi_class_init, NULL,
622 gnm_soi_init, GNM_SO_TYPE, 0,
623 GSF_INTERFACE (soi_imageable_init, GNM_SO_IMAGEABLE_TYPE))