ssdiff: move comparison engine into its own file.
[gnumeric.git] / src / gui-clipboard.c
blob372266cf704e7198afa2b59d5c8b0a265ffc128c
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * gui-clipboard.c: Implements the X11 based copy/paste operations
5 * Author:
6 * Miguel de Icaza (miguel@gnu.org)
7 * Jody Goldberg (jody@gnome.org)
8 */
9 #include <gnumeric-config.h>
10 #include "gnumeric.h"
11 #include "gui-clipboard.h"
13 #include "gui-util.h"
14 #include "clipboard.h"
15 #include "command-context-stderr.h"
16 #include "selection.h"
17 #include "application.h"
18 #include "workbook-control.h"
19 #include "wbc-gtk.h"
20 #include "workbook-priv.h"
21 #include "workbook.h"
22 #include "workbook-view.h"
23 #include "ranges.h"
24 #include "sheet.h"
25 #include "sheet-style.h"
26 #include "sheet-object.h"
27 #include "sheet-control-gui.h"
28 #include "sheet-view.h"
29 #include "commands.h"
30 #include "value.h"
31 #include "number-match.h"
32 #include "dialog-stf.h"
33 #include "stf-parse.h"
34 #include "mstyle.h"
35 #include "gnm-format.h"
36 #include "gnumeric-conf.h"
37 #include "xml-sax.h"
38 #include "gutils.h"
40 #include <goffice/goffice.h>
41 #include <gsf/gsf-input-memory.h>
42 #include <gsf/gsf-output-memory.h>
43 #include <gsf/gsf-utils.h>
44 #include <glib/gi18n-lib.h>
45 #include <libxml/globals.h>
46 #include <locale.h>
47 #include <string.h>
48 #include <unistd.h>
50 #define APP_CLIP_DISP_KEY "clipboard-displays"
52 static gboolean
53 debug_clipboard (void)
55 static gboolean d_clipboard;
56 static gboolean inited = FALSE;
58 if (!inited) {
59 inited = TRUE;
60 d_clipboard = gnm_debug_flag ("clipboard");
63 return d_clipboard;
66 typedef struct {
67 WBCGtk *wbcg;
68 GnmPasteTarget *paste_target;
69 GdkAtom image_atom;
70 GdkAtom string_atom;
71 } GnmGtkClipboardCtxt;
73 /* The name of our clipboard atom and the 'magic' info number */
74 #define GNUMERIC_ATOM_NAME "application/x-gnumeric"
75 #define GNUMERIC_ATOM_INFO 2001
76 #define GOFFICE_GRAPH_ATOM_NAME "application/x-goffice-graph"
79 * Emacs hack:
80 * (x-get-selection-internal 'CLIPBOARD 'TARGETS)
83 /* From MS Excel */
84 #define BIFF8_ATOM_NAME "Biff8"
85 #define BIFF8_ATOM_NAME_CITRIX "_CITRIX_Biff8"
86 #define BIFF5_ATOM_NAME "Biff5"
87 #define BIFF4_ATOM_NAME "Biff4"
88 #define BIFF3_ATOM_NAME "Biff3"
89 #define BIFF_ATOM_NAME "Biff"
91 #define HTML_ATOM_NAME_UNIX "text/html"
92 #define HTML_ATOM_NAME_WINDOWS "HTML Format"
93 #ifdef G_OS_WIN32
94 #define HTML_ATOM_NAME HTML_ATOM_NAME_WINDOWS
95 #else
96 #define HTML_ATOM_NAME HTML_ATOM_NAME_UNIX
97 #endif
99 #define OOO_ATOM_NAME "application/x-openoffice;windows_formatname=\"Star Embed Source (XML)\""
100 #define OOO11_ATOM_NAME "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""
101 #define OOO_ATOM_NAME_WINDOWS "Star Embed Source (XML)"
103 #define UTF8_ATOM_NAME "UTF8_STRING"
104 #define CTEXT_ATOM_NAME "COMPOUND_TEXT"
105 #define STRING_ATOM_NAME "STRING"
107 /* See if this is a "single line + line end", a "multiline" or a "tab separated"
108 * string. If this is _not_ the case we won't invoke the STF, it is
109 * unlikely that the user will actually need it in this case. */
110 static gboolean
111 text_is_single_cell (gchar const *data, int data_len)
113 int i;
115 for (i = 0; i < data_len; i++)
116 if (data[i] == '\n' || data[i] == '\t')
117 return FALSE;
118 return TRUE;
122 static GnmCellRegion *
123 text_to_cell_region (WBCGtk *wbcg,
124 gchar const *data, int data_len,
125 char const *opt_encoding,
126 gboolean fixed_encoding)
128 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
129 DialogStfResult_t *dialogresult;
130 GnmCellRegion *cr = NULL;
131 gboolean oneline;
132 char *data_converted = NULL;
134 if (!data) {
136 * See Redhat #1160975.
138 * I'm unsure why someone get NULL here, but this is better
139 * than a crash.
141 data = "";
142 data_len = 0;
145 oneline = text_is_single_cell (data, data_len);
147 if (oneline && (opt_encoding == NULL || strcmp (opt_encoding, "UTF-8") != 0)) {
148 size_t bytes_written;
149 char const *enc = opt_encoding ? opt_encoding : "ASCII";
151 data_converted = g_convert (data, data_len,
152 "UTF-8", enc,
153 NULL, &bytes_written, NULL);
154 if (data_converted) {
155 data = data_converted;
156 data_len = bytes_written;
157 } else {
158 /* Force STF import since we don't know the charset. */
159 oneline = FALSE;
160 fixed_encoding = FALSE;
164 if (oneline) {
165 GODateConventions const *date_conv = workbook_date_conv (wb);
166 GnmCellCopy *cc = gnm_cell_copy_new (
167 (cr = gnm_cell_region_new (NULL)), 0, 0);
168 char *tmp = g_strndup (data, data_len);
170 g_free (data_converted);
172 cc->val = format_match (tmp, NULL, date_conv);
173 if (cc->val)
174 g_free (tmp);
175 else
176 cc->val = value_new_string_nocopy (tmp);
177 cc->texpr = NULL;
179 cr->cols = cr->rows = 1;
180 } else {
181 dialogresult = stf_dialog (wbcg, opt_encoding, fixed_encoding,
182 NULL, FALSE,
183 _("clipboard"), data, data_len);
185 if (dialogresult != NULL) {
186 cr = stf_parse_region (dialogresult->parseoptions,
187 dialogresult->text, NULL, wb);
188 g_return_val_if_fail (cr != NULL, gnm_cell_region_new (NULL));
190 stf_dialog_result_attach_formats_to_cr (dialogresult, cr);
192 stf_dialog_result_free (dialogresult);
193 } else
194 cr = gnm_cell_region_new (NULL);
197 return cr;
200 static void
201 text_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
202 gpointer closure)
204 GnmGtkClipboardCtxt *ctxt = closure;
205 WBCGtk *wbcg = ctxt->wbcg;
206 WorkbookControl *wbc = GNM_WBC (wbcg);
207 GnmPasteTarget *pt = ctxt->paste_target;
208 GnmCellRegion *content = NULL;
209 GdkAtom target = gtk_selection_data_get_target (sel);
211 if (debug_clipboard ()) {
212 int maxlen = 1024;
213 char *name = gdk_atom_name (gtk_selection_data_get_target (sel));
214 g_printerr ("Received %d bytes of text for target %s\n",
215 gtk_selection_data_get_length (sel),
216 name);
217 g_free (name);
218 if (gtk_selection_data_get_length (sel) > 0) {
219 gsf_mem_dump (gtk_selection_data_get_data (sel), MIN (gtk_selection_data_get_length (sel), maxlen));
220 if (gtk_selection_data_get_length (sel) > maxlen)
221 g_printerr ("...\n");
225 /* Nothing on clipboard? */
226 if (gtk_selection_data_get_length (sel) < 0) {
228 } else if (target == gdk_atom_intern (UTF8_ATOM_NAME, FALSE)) {
229 content = text_to_cell_region (wbcg, (const char *)gtk_selection_data_get_data (sel),
230 gtk_selection_data_get_length (sel), "UTF-8", TRUE);
231 } else if (target == gdk_atom_intern (CTEXT_ATOM_NAME, FALSE)) {
232 /* COMPOUND_TEXT is icky. Just let GTK+ do the work. */
233 char *data_utf8 = (char *)gtk_selection_data_get_text (sel);
234 content = text_to_cell_region (wbcg, data_utf8, strlen (data_utf8), "UTF-8", TRUE);
235 g_free (data_utf8);
236 } else if (target == gdk_atom_intern (STRING_ATOM_NAME, FALSE)) {
237 char const *locale_encoding;
238 g_get_charset (&locale_encoding);
240 content = text_to_cell_region (wbcg, (const char *)gtk_selection_data_get_data (sel),
241 gtk_selection_data_get_length (sel), locale_encoding, FALSE);
243 if (content) {
245 * if the conversion from the X selection -> a cellregion
246 * was canceled this may have content sized -1,-1
248 if (content->cols > 0 && content->rows > 0)
249 cmd_paste_copy (wbc, pt, content);
251 /* Release the resources we used */
252 cellregion_unref (content);
254 g_free (ctxt->paste_target);
255 g_free (ctxt);
258 static void
259 utf8_content_received (GtkClipboard *clipboard, const gchar *text,
260 gpointer closure)
262 GnmGtkClipboardCtxt *ctxt = closure;
263 WBCGtk *wbcg = ctxt->wbcg;
264 WorkbookControl *wbc = GNM_WBC (wbcg);
265 GnmPasteTarget *pt = ctxt->paste_target;
266 GnmCellRegion *content = NULL;
268 /* Nothing on clipboard? */
269 if (!text || strlen(text) == 0) {
271 } else {
272 content = text_to_cell_region (wbcg, text, strlen(text), "UTF-8", TRUE);
274 if (content) {
276 * if the conversion from the X selection -> a cellregion
277 * was canceled this may have content sized -1,-1
279 if (content->cols > 0 && content->rows > 0)
280 cmd_paste_copy (wbc, pt, content);
282 /* Release the resources we used */
283 cellregion_unref (content);
285 g_free (ctxt->paste_target);
286 g_free (ctxt);
290 * Use the file_opener plugin service to read into a temporary workbook, in
291 * order to copy from it to the paste target. A temporary sheet would do just
292 * as well, but the file_opener service makes workbooks, not sheets.
294 * We use the file_opener service by wrapping the selection data in a GsfInput,
295 * and calling workbook_view_new_from_input.
297 static GnmCellRegion *
298 table_cellregion_read (WorkbookControl *wbc, char const *reader_id,
299 GnmPasteTarget *pt, const guchar *buffer, int length)
301 WorkbookView *wb_view = NULL;
302 Workbook *wb = NULL;
303 GnmCellRegion *ret = NULL;
304 const GOFileOpener *reader = go_file_opener_for_id (reader_id);
305 GOIOContext *ioc;
306 GsfInput *input;
308 if (!reader) {
309 g_warning ("No file opener for %s", reader_id);
310 return NULL;
313 ioc = go_io_context_new (GO_CMD_CONTEXT (wbc));
314 input = gsf_input_memory_new (buffer, length, FALSE);
315 wb_view = workbook_view_new_from_input (input, NULL, reader, ioc, NULL);
316 if (go_io_error_occurred (ioc) || wb_view == NULL) {
317 go_io_error_display (ioc);
318 goto out;
321 wb = wb_view_get_workbook (wb_view);
322 if (workbook_sheet_count (wb) > 0) {
323 GnmRange r;
324 Sheet *tmpsheet = workbook_sheet_by_index (wb, 0);
325 GnmRange *rp = g_object_get_data (G_OBJECT (tmpsheet),
326 "DIMENSION");
327 if (rp) {
328 r = *rp;
329 } else {
330 r.start.col = 0;
331 r.start.row = 0;
332 r.end.col = tmpsheet->cols.max_used;
333 r.end.row = tmpsheet->rows.max_used;
335 ret = clipboard_copy_range (tmpsheet, &r);
338 /* This isn't particularly right, but we are going to delete
339 the workbook shortly. See #490479. */
340 WORKBOOK_FOREACH_SHEET (wb, sheet, {
341 cellregion_invalidate_sheet (ret, sheet);
344 out:
345 if (wb_view)
346 g_object_unref (wb_view);
347 if (wb)
348 g_object_unref (wb);
349 g_object_unref (ioc);
350 g_object_unref (input);
352 return ret;
355 static void
356 image_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
357 gpointer closure)
359 GnmGtkClipboardCtxt *ctxt = closure;
360 WBCGtk *wbcg = ctxt->wbcg;
361 GnmPasteTarget *pt = ctxt->paste_target;
363 if (debug_clipboard ()) {
364 int maxlen = 1024;
365 char *name = gdk_atom_name (gtk_selection_data_get_target (sel));
366 g_printerr ("Received %d bytes of image for target %s\n",
367 gtk_selection_data_get_length (sel),
368 name);
369 g_free (name);
370 if (gtk_selection_data_get_length (sel) > 0) {
371 gsf_mem_dump (gtk_selection_data_get_data (sel), MIN (gtk_selection_data_get_length (sel), maxlen));
372 if (gtk_selection_data_get_length (sel) > maxlen)
373 g_printerr ("...\n");
377 if (gtk_selection_data_get_length (sel) > 0) {
378 scg_paste_image (wbcg_cur_scg (wbcg), &pt->range,
379 gtk_selection_data_get_data (sel), gtk_selection_data_get_length (sel));
380 g_free (ctxt->paste_target);
381 g_free (ctxt);
382 } else if (ctxt->string_atom != GDK_NONE) {
383 gtk_clipboard_request_contents (clipboard, ctxt->string_atom,
384 text_content_received, ctxt);
385 } else {
386 g_free (ctxt->paste_target);
387 g_free (ctxt);
391 static void
392 parse_ms_headers (const char *data, size_t length, size_t *start, size_t *end)
394 GHashTable *headers = g_hash_table_new_full
395 (g_str_hash, g_str_equal, g_free, g_free);
396 size_t limit = length;
397 size_t i = 0;
398 char *key = NULL;
399 char *value = NULL;
400 long sf, ef;
401 const char *v;
403 while (i < limit && data[i] != '<') {
404 size_t j, k;
406 for (j = i; j < limit; j++) {
407 if (data[j] == ':') {
408 key = g_strndup (data + i, j - i);
409 break;
411 if (g_ascii_isspace (data[j]))
412 goto bad;
414 if (j >= limit)
415 goto bad;
416 j++;
418 for (k = j; k < limit; k++) {
419 if (data[k] == '\n' || data[k] == '\r') {
420 value = g_strndup (data + j, k - j);
421 break;
424 if (k >= limit)
425 goto bad;
426 while (g_ascii_isspace (data[k]))
427 k++;
429 i = k;
431 if (debug_clipboard ())
432 g_printerr ("MS HTML Header [%s] => [%s]\n", key, value);
434 if (strcmp (key, "StartHTML") == 0) {
435 long l = strtol (value, NULL, 10);
436 limit = MIN (limit, (size_t)MAX (0, l));
439 g_hash_table_replace (headers, key, value);
440 key = value = NULL;
443 v = g_hash_table_lookup (headers, "StartFragment");
444 sf = v ? strtol (v, NULL, 10) : -1;
445 if (sf < (long)limit)
446 goto bad;
448 v = g_hash_table_lookup (headers, "EndFragment");
449 ef = v ? strtol (v, NULL, 10) : -1;
450 if (ef < sf || ef > (long)length)
451 goto bad;
453 *start = sf;
454 *end = ef;
455 goto out;
457 bad:
458 g_free (key);
459 g_free (value);
460 *start = 0;
461 *end = length;
463 out:
464 g_hash_table_destroy (headers);
468 static void
469 table_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
470 gpointer closure)
472 GnmGtkClipboardCtxt *ctxt = closure;
473 WBCGtk *wbcg = ctxt->wbcg;
474 WorkbookControl *wbc = GNM_WBC (wbcg);
475 GnmPasteTarget *pt = ctxt->paste_target;
476 GnmCellRegion *content = NULL;
477 GdkAtom target = gtk_selection_data_get_target (sel);
479 if (debug_clipboard ()) {
480 int maxlen = 1024;
481 char *name = gdk_atom_name (gtk_selection_data_get_target (sel));
482 g_printerr ("Received %d bytes of table for target %s\n",
483 gtk_selection_data_get_length (sel),
484 name);
485 g_free (name);
486 if (gtk_selection_data_get_length (sel) > 0) {
487 gsf_mem_dump (gtk_selection_data_get_data (sel), MIN (gtk_selection_data_get_length (sel), maxlen));
488 if (gtk_selection_data_get_length (sel) > maxlen)
489 g_printerr ("...\n");
493 /* Nothing on clipboard? */
494 if (gtk_selection_data_get_length (sel) < 0) {
496 } else if (target == gdk_atom_intern (GNUMERIC_ATOM_NAME, FALSE)) {
497 /* The data is the gnumeric specific XML interchange format */
498 GOIOContext *io_context =
499 go_io_context_new (GO_CMD_CONTEXT (wbcg));
500 content = gnm_xml_cellregion_read
501 (wbc, io_context,
502 pt->sheet,
503 (const char *)gtk_selection_data_get_data (sel), gtk_selection_data_get_length (sel));
504 g_object_unref (io_context);
505 } else if (target == gdk_atom_intern (OOO_ATOM_NAME, FALSE) ||
506 target == gdk_atom_intern (OOO_ATOM_NAME_WINDOWS, FALSE) ||
507 target == gdk_atom_intern (OOO11_ATOM_NAME, FALSE)) {
508 content = table_cellregion_read (wbc, "Gnumeric_OpenCalc:openoffice",
509 pt, gtk_selection_data_get_data (sel),
510 gtk_selection_data_get_length (sel));
511 } else if (target == gdk_atom_intern (HTML_ATOM_NAME_UNIX, FALSE) ||
512 target == gdk_atom_intern (HTML_ATOM_NAME_WINDOWS, FALSE)) {
513 size_t length = gtk_selection_data_get_length (sel);
514 size_t start = 0, end = length;
516 if (target == gdk_atom_intern (HTML_ATOM_NAME_WINDOWS, FALSE)) {
517 /* See bug 143084 */
518 parse_ms_headers (gtk_selection_data_get_data (sel), length, &start, &end);
521 content = table_cellregion_read (wbc, "Gnumeric_html:html",
523 gtk_selection_data_get_data (sel) + start,
524 end - start);
525 } else if ((target == gdk_atom_intern ( BIFF8_ATOM_NAME, FALSE)) ||
526 (target == gdk_atom_intern ( BIFF8_ATOM_NAME_CITRIX, FALSE)) ||
527 (target == gdk_atom_intern ( BIFF5_ATOM_NAME, FALSE)) ||
528 (target == gdk_atom_intern ( BIFF4_ATOM_NAME, FALSE)) ||
529 (target == gdk_atom_intern ( BIFF3_ATOM_NAME, FALSE)) ||
530 (target == gdk_atom_intern ( BIFF_ATOM_NAME, FALSE))) {
531 content = table_cellregion_read (wbc, "Gnumeric_Excel:excel",
532 pt, gtk_selection_data_get_data (sel),
533 gtk_selection_data_get_length (sel));
535 if (content) {
537 * if the conversion from the X selection -> a cellregion
538 * was canceled this may have content sized -1,-1
540 if ((content->cols > 0 && content->rows > 0) ||
541 content->objects != NULL)
542 cmd_paste_copy (wbc, pt, content);
544 /* Release the resources we used */
545 cellregion_unref (content);
546 g_free (ctxt->paste_target);
547 g_free (ctxt);
548 } else if (ctxt->image_atom != GDK_NONE) {
549 gtk_clipboard_request_contents (clipboard, ctxt->image_atom,
550 image_content_received, ctxt);
551 } else if (ctxt->string_atom != GDK_NONE) {
552 gtk_clipboard_request_contents (clipboard, ctxt->string_atom,
553 text_content_received, ctxt);
554 } else {
555 g_free (ctxt->paste_target);
556 g_free (ctxt);
561 * x_targets_received:
563 * Invoked when the selection has been received by our application.
564 * This is triggered by a call we do to gtk_clipboard_request_contents.
566 * We try to import a spreadsheet/table, next an image, and finally fall back
567 * to a string format if the others fail, e.g. for html which does not
568 * contain a table.
570 static void
571 x_targets_received (GtkClipboard *clipboard, GdkAtom *targets,
572 gint n_targets, gpointer closure)
574 GnmGtkClipboardCtxt *ctxt = closure;
575 GdkAtom table_atom = GDK_NONE;
576 gint i, j;
578 /* in order of preference */
579 static char const *table_fmts [] = {
580 GNUMERIC_ATOM_NAME,
582 BIFF8_ATOM_NAME,
583 BIFF8_ATOM_NAME_CITRIX,
584 BIFF5_ATOM_NAME,
585 BIFF4_ATOM_NAME,
586 BIFF3_ATOM_NAME,
587 BIFF_ATOM_NAME,
589 OOO_ATOM_NAME,
590 OOO11_ATOM_NAME,
591 OOO_ATOM_NAME_WINDOWS,
593 HTML_ATOM_NAME_UNIX,
594 HTML_ATOM_NAME_WINDOWS,
595 NULL
597 static char const *string_fmts [] = {
598 UTF8_ATOM_NAME,
599 STRING_ATOM_NAME,
600 CTEXT_ATOM_NAME,
601 NULL
604 /* Nothing on clipboard? */
605 if (targets == NULL || n_targets == 0) {
606 gtk_clipboard_request_text (clipboard, utf8_content_received,
607 ctxt);
608 return;
611 if (debug_clipboard ()) {
612 int j;
614 for (j = 0; j < n_targets; j++)
615 g_printerr ("Clipboard target %d is %s\n",
616 j, gdk_atom_name (targets[j]));
619 /* The data is a list of atoms */
620 /* Find the best table format offered */
621 for (i = 0 ; table_fmts[i] && table_atom == GDK_NONE ; i++) {
622 /* Look for one we can use */
623 GdkAtom atom = gdk_atom_intern (table_fmts[i], FALSE);
624 /* is it on offer? */
625 for (j = 0; j < n_targets && table_atom == GDK_NONE; j++) {
626 if (targets [j] == atom)
627 table_atom = atom;
631 if (table_atom == GDK_NONE) {
632 GtkTargetList *tl = gtk_target_list_new (NULL, 0);
633 gboolean found = FALSE;
635 gtk_target_list_add_image_targets (tl, 0, FALSE);
637 /* Find an image format */
638 for (i = 0 ; i < n_targets && !found; i++) {
639 if (gtk_target_list_find (tl, targets[i], NULL)) {
640 ctxt->image_atom = targets[i];
641 found = TRUE;
644 gtk_target_list_unref (tl);
647 /* Find a string format to fall back to */
648 for (i = 0 ; string_fmts[i] && ctxt->string_atom == GDK_NONE ; i++) {
649 /* Look for one we can use */
650 GdkAtom atom = gdk_atom_intern (string_fmts[i], FALSE);
651 /* is it on offer? */
652 for (j = 0; j < n_targets && ctxt->string_atom == GDK_NONE;
653 j++) {
654 if (targets [j] == atom)
655 ctxt->string_atom = atom;
657 if (ctxt->string_atom != GDK_NONE)
658 break;
661 if (table_atom != GDK_NONE)
662 gtk_clipboard_request_contents (clipboard, table_atom,
663 table_content_received, ctxt);
664 else if (ctxt->image_atom != GDK_NONE)
665 gtk_clipboard_request_contents (clipboard, ctxt->image_atom,
666 image_content_received, ctxt);
667 else if (ctxt->string_atom != GDK_NONE)
668 gtk_clipboard_request_contents (clipboard, ctxt->string_atom,
669 text_content_received, ctxt);
670 else {
671 g_free (ctxt->paste_target);
672 g_free (ctxt);
676 /* Cheezy implementation: paste into a temporary workbook, save that. */
677 static guchar *
678 table_cellregion_write (GOCmdContext *ctx, GnmCellRegion *cr,
679 char * saver_id, int *size)
681 guchar *ret = NULL;
682 const GOFileSaver *saver = go_file_saver_for_id (saver_id);
683 GsfOutput *output;
684 GOIOContext *ioc;
685 Workbook *wb;
686 WorkbookView *wb_view;
687 Sheet *sheet;
688 GnmPasteTarget pt;
689 GnmRange r;
691 *size = 0;
692 if (!saver)
693 return NULL;
695 output = gsf_output_memory_new ();
696 ioc = go_io_context_new (ctx);
699 int cols = cr->cols;
700 int rows = cr->rows;
701 gnm_sheet_suggest_size (&cols, &rows);
702 wb = workbook_new ();
703 workbook_sheet_add (wb, -1, cols, rows);
706 wb_view = workbook_view_new (wb);
708 sheet = workbook_sheet_by_index (wb, 0);
709 memset (&r, 0, sizeof r);
710 r.end.col = cr->cols - 1;
711 r.end.row = cr->rows - 1;
713 paste_target_init (&pt, sheet, &r,
714 PASTE_AS_VALUES | PASTE_FORMATS |
715 PASTE_COMMENTS | PASTE_OBJECTS);
716 if (clipboard_paste_region (cr, &pt, ctx) == FALSE) {
717 go_file_saver_save (saver, ioc, GO_VIEW (wb_view), output);
718 if (!go_io_error_occurred (ioc)) {
719 GsfOutputMemory *omem = GSF_OUTPUT_MEMORY (output);
720 gsf_off_t osize = gsf_output_size (output);
722 *size = osize;
723 if (*size == osize) {
724 ret = g_malloc (*size);
725 memcpy (ret,
726 gsf_output_memory_get_bytes (omem),
727 *size);
728 } else {
729 g_warning ("Overflow"); /* Far fetched! */
733 gsf_output_close (output);
734 g_object_unref (wb_view);
735 g_object_unref (wb);
736 g_object_unref (ioc);
737 g_object_unref (output);
739 return ret;
742 static guchar *
743 image_write (GnmCellRegion *cr, gchar const *mime_type, int *size)
745 guchar *ret = NULL;
746 SheetObject *so = NULL;
747 char *format;
748 GsfOutput *output;
749 GsfOutputMemory *omem;
750 gsf_off_t osize;
751 GSList *l;
753 *size = -1;
755 g_return_val_if_fail (cr->objects != NULL, NULL);
756 so = GNM_SO (cr->objects->data);
757 g_return_val_if_fail (so != NULL, NULL);
759 if (strncmp (mime_type, "image/", 6) != 0)
760 return ret;
761 for (l = cr->objects; l != NULL; l = l->next) {
762 if (GNM_IS_SO_IMAGEABLE (GNM_SO (l->data))) {
763 so = GNM_SO (l->data);
764 break;
767 if (so == NULL) {
768 g_warning ("non imageable object requested as image\n");
769 return ret;
772 format = go_mime_to_image_format (mime_type);
773 if (!format) {
774 g_warning ("No image format for %s\n", mime_type);
775 g_free (format);
776 return ret;
778 output = gsf_output_memory_new ();
779 omem = GSF_OUTPUT_MEMORY (output);
780 sheet_object_write_image (so, format, 150.0, output, NULL);
781 osize = gsf_output_size (output);
783 *size = osize;
784 if (*size == osize) {
785 ret = g_malloc (*size);
786 memcpy (ret, gsf_output_memory_get_bytes (omem), *size);
787 } else {
788 g_warning ("Overflow"); /* Far fetched! */
790 gsf_output_close (output);
791 g_object_unref (output);
792 g_free (format);
794 return ret;
797 static guchar *
798 object_write (GnmCellRegion *cr, gchar const *mime_type, int *size)
800 guchar *ret = NULL;
801 SheetObject *so = NULL;
802 GsfOutput *output;
803 GsfOutputMemory *omem;
804 gsf_off_t osize;
805 GSList *l;
807 *size = -1;
809 g_return_val_if_fail (cr->objects != NULL, NULL);
810 so = GNM_SO (cr->objects->data);
811 g_return_val_if_fail (so != NULL, NULL);
813 for (l = cr->objects; l != NULL; l = l->next) {
814 if (GNM_IS_SO_EXPORTABLE (GNM_SO (l->data))) {
815 so = GNM_SO (l->data);
816 break;
819 if (so == NULL) {
820 g_warning ("non exportable object requested\n");
821 return ret;
823 output = gsf_output_memory_new ();
824 omem = GSF_OUTPUT_MEMORY (output);
825 sheet_object_write_object (so, mime_type, output, NULL,
826 gnm_conventions_default);
827 osize = gsf_output_size (output);
829 *size = osize;
830 if (*size == osize)
831 ret = g_memdup (gsf_output_memory_get_bytes (omem), *size);
832 else
833 g_warning ("Overflow"); /* Far fetched! */
834 gsf_output_close (output);
835 g_object_unref (output);
837 return ret;
841 * x_clipboard_get_cb
843 * Callback invoked when another application requests we render the selection.
845 static void
846 x_clipboard_get_cb (GtkClipboard *gclipboard, GtkSelectionData *selection_data,
847 guint info, GObject *app)
849 gboolean to_gnumeric = FALSE, content_needs_free = FALSE;
850 GnmCellRegion *clipboard = gnm_app_clipboard_contents_get ();
851 Sheet *sheet = gnm_app_clipboard_sheet_get ();
852 GnmRange const *a = gnm_app_clipboard_area_get ();
853 GOCmdContext *ctx = gnm_cmd_context_stderr_new ();
854 GdkAtom target = gtk_selection_data_get_target (selection_data);
855 gchar *target_name = gdk_atom_name (target);
857 if (debug_clipboard ())
858 g_printerr ("clipboard target=%s\n", target_name);
861 * There are 4 cases. What variables are valid depends on case:
862 * source is
863 * a cut: clipboard NULL, sheet, area non NULL.
864 * a copy: clipboard, sheet, area all non NULL.
865 * a cut, source closed: clipboard, sheet, area all NULL.
866 * a copy, source closed: clipboard non NULL, sheet, area non NULL.
868 * If the source is a cut, we copy it for pasting. We
869 * postpone clearing it until after the selection has been
870 * rendered to the requested format.
872 if (clipboard == NULL && sheet != NULL) {
873 content_needs_free = TRUE;
874 clipboard = clipboard_copy_range (sheet, a);
877 if (clipboard == NULL)
878 goto out;
880 /* What format does the other application want? */
881 if (target == gdk_atom_intern (GNUMERIC_ATOM_NAME, FALSE)) {
882 GsfOutputMemory *output = gnm_cellregion_to_xml (clipboard);
883 if (output) {
884 gsf_off_t size = gsf_output_size (GSF_OUTPUT (output));
885 if (debug_clipboard ())
886 g_printerr ("clipboard .gnumeric of %d bytes\n",
887 (int)size);
888 gtk_selection_data_set
889 (selection_data, target, 8,
890 gsf_output_memory_get_bytes (output),
891 size);
892 g_object_unref (output);
893 to_gnumeric = TRUE;
895 } else if (target == gdk_atom_intern (HTML_ATOM_NAME, FALSE)) {
896 char *saver_id = (char *) "Gnumeric_html:xhtml_range";
897 int buffer_size;
898 guchar *buffer = table_cellregion_write (ctx, clipboard,
899 saver_id,
900 &buffer_size);
901 if (debug_clipboard ())
902 g_message ("clipboard html of %d bytes",
903 buffer_size);
904 gtk_selection_data_set (selection_data,
905 target, 8,
906 (guchar *) buffer, buffer_size);
907 g_free (buffer);
908 } else if (strcmp (target_name, "application/x-goffice-graph") == 0 ||
909 g_slist_find_custom (go_components_get_mime_types (), target_name, (GCompareFunc) strcmp) != NULL) {
910 int buffer_size;
911 guchar *buffer = object_write (clipboard, target_name,
912 &buffer_size);
913 if (debug_clipboard ())
914 g_message ("clipboard graph of %d bytes",
915 buffer_size);
916 gtk_selection_data_set (selection_data,
917 target, 8,
918 (guchar *) buffer, buffer_size);
919 g_free (buffer);
920 } else if (strncmp (target_name, "image/", 6) == 0) {
921 int buffer_size;
922 guchar *buffer = image_write (clipboard, target_name,
923 &buffer_size);
924 if (debug_clipboard ())
925 g_message ("clipboard image of %d bytes",
926 buffer_size);
927 gtk_selection_data_set (selection_data,
928 target, 8,
929 (guchar *) buffer, buffer_size);
930 g_free (buffer);
931 } else if (strcmp (target_name, "SAVE_TARGETS") == 0) {
932 /* We implicitly registered this when calling
933 * gtk_clipboard_set_can_store. We're supposed to
934 * ignore it. */
935 } else if (clipboard->origin_sheet) {
936 Workbook *wb = clipboard->origin_sheet->workbook;
937 GString *res = cellregion_to_string (clipboard,
938 TRUE, workbook_date_conv (wb));
939 if (res != NULL) {
940 if (debug_clipboard ())
941 g_message ("clipboard text of %d bytes",
942 (int)res->len);
943 gtk_selection_data_set_text (selection_data,
944 res->str, res->len);
945 g_string_free (res, TRUE);
946 } else {
947 if (debug_clipboard ())
948 g_message ("clipboard empty text");
949 gtk_selection_data_set_text (selection_data, "", 0);
951 } else
952 gtk_selection_data_set_text (selection_data, "", 0);
955 * If this was a CUT operation we need to clear the content that
956 * was pasted into another application and release the stuff on
957 * the clipboard
959 if (content_needs_free) {
961 /* If the other app was a gnumeric, emulate a cut */
962 if (to_gnumeric) {
963 GOUndo *redo, *undo;
964 GnmSheetRange *sr = gnm_sheet_range_new (sheet, a);
965 SheetView const *sv = gnm_app_clipboard_sheet_view_get ();
966 SheetControl *sc = g_ptr_array_index (sv->controls, 0);
967 WorkbookControl *wbc = sc_wbc (sc);
968 char *name;
969 char *text;
971 redo = sheet_clear_region_undo
972 (sr,
973 CLEAR_VALUES|CLEAR_COMMENTS|CLEAR_RECALC_DEPS);
974 undo = clipboard_copy_range_undo (sheet, a);
975 name = undo_range_name (sheet, a);
976 text = g_strdup_printf (_("Cut of %s"), name);
977 g_free (name);
978 cmd_generic (wbc, text, undo, redo);
979 g_free (text);
980 gnm_app_clipboard_clear (TRUE);
983 cellregion_unref (clipboard);
985 out:
986 g_free (target_name);
987 g_object_unref (ctx);
991 * x_clipboard_clear_cb:
993 * Callback for the "we lost the X selection" signal.
995 static gint
996 x_clipboard_clear_cb (GtkClipboard *clipboard,
997 GObject *obj)
999 if (debug_clipboard ())
1000 g_printerr ("Lost clipboard ownership.\n");
1002 gnm_app_clipboard_clear (FALSE);
1004 return TRUE;
1007 void
1008 gnm_x_request_clipboard (WBCGtk *wbcg, GnmPasteTarget const *pt)
1010 GnmGtkClipboardCtxt *ctxt;
1011 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (wbcg_toplevel (wbcg)));
1012 GtkClipboard *clipboard =
1013 gtk_clipboard_get_for_display
1014 (display,
1015 gnm_conf_get_cut_and_paste_prefer_clipboard ()
1016 ? GDK_SELECTION_CLIPBOARD
1017 : GDK_SELECTION_PRIMARY);
1019 ctxt = g_new (GnmGtkClipboardCtxt, 1);
1020 ctxt->wbcg = wbcg;
1021 ctxt->paste_target = g_new (GnmPasteTarget, 1);
1022 *ctxt->paste_target = *pt;
1023 ctxt->image_atom = GDK_NONE;
1024 ctxt->string_atom = GDK_NONE;
1026 /* Query the formats, This will callback x_targets_received */
1027 gtk_clipboard_request_targets (clipboard,
1028 x_targets_received, ctxt);
1031 /* Restrict the set of formats offered to clipboard manager. */
1032 /* We include bmp in the whitelist because that's the only image format
1033 * we share with OOo over clipboard (!) */
1034 static void
1035 set_clipman_targets (GdkDisplay *disp, GtkTargetEntry *targets, guint n_targets)
1037 static GtkTargetEntry clipman_whitelist[] = {
1038 { (char *) GNUMERIC_ATOM_NAME, 0, GNUMERIC_ATOM_INFO },
1039 { (char *) HTML_ATOM_NAME, 0, 0 },
1040 { (char *)"UTF8_STRING", 0, 0 },
1041 { (char *)"application/x-goffice-graph", 0, 0 },
1042 { (char *)"image/svg+xml", 0, 0 },
1043 { (char *)"image/x-wmf", 0, 0 },
1044 { (char *)"image/x-emf", 0, 0 },
1045 { (char *)"image/png", 0, 0 },
1046 { (char *)"image/jpeg", 0, 0 },
1047 { (char *)"image/bmp", 0, 0 },
1049 guint n_whitelist = G_N_ELEMENTS (clipman_whitelist);
1050 int n_allowed;
1051 GtkTargetList *tl = gtk_target_list_new (NULL, 0);
1052 GtkTargetEntry *t, *wt, *t_allowed;
1054 for (t = targets; t < targets + n_targets; t++) {
1055 for (wt = clipman_whitelist;
1056 wt < clipman_whitelist + n_whitelist; wt++) {
1057 if (strcmp(t->target, wt->target) == 0) {
1058 gtk_target_list_add
1059 (tl, gdk_atom_intern (t->target, FALSE),
1060 t->flags, t->info);
1061 break;
1066 t_allowed = gtk_target_table_new_from_list (tl, &n_allowed);
1067 gtk_target_list_unref (tl);
1069 gtk_clipboard_set_can_store (
1070 gtk_clipboard_get_for_display (
1071 disp, GDK_SELECTION_CLIPBOARD),
1072 t_allowed, n_allowed);
1073 gtk_target_table_free (t_allowed, n_allowed);
1074 t_allowed = NULL;
1077 gboolean
1078 gnm_x_claim_clipboard (GdkDisplay *display)
1080 GnmCellRegion *content = gnm_app_clipboard_contents_get ();
1081 SheetObject *imageable = NULL, *exportable = NULL;
1082 GtkTargetEntry *targets = NULL;
1083 int n_targets;
1084 gboolean ret;
1085 GObject *app = gnm_app_get_app ();
1087 static GtkTargetEntry const table_targets[] = {
1088 { (char *) GNUMERIC_ATOM_NAME, 0, GNUMERIC_ATOM_INFO },
1089 { (char *) HTML_ATOM_NAME, 0, 0 },
1090 { (char *)"UTF8_STRING", 0, 0 },
1091 { (char *)"COMPOUND_TEXT", 0, 0 },
1092 { (char *)"STRING", 0, 0 },
1095 targets = (GtkTargetEntry *) table_targets;
1096 n_targets = G_N_ELEMENTS (table_targets);
1097 if (content &&
1098 (content->cols <= 0 || content->rows <= 0)) {
1099 GSList *ptr;
1100 for (ptr = content->objects; ptr != NULL;
1101 ptr = ptr->next) {
1102 SheetObject *candidate = GNM_SO (ptr->data);
1103 if (exportable == NULL && GNM_IS_SO_EXPORTABLE (candidate))
1104 exportable = candidate;
1105 if (imageable == NULL && GNM_IS_SO_IMAGEABLE (candidate))
1106 imageable = candidate;
1108 /* Currently, we can't render sheet objects as text or html */
1109 n_targets = 1;
1111 if (exportable) {
1112 GtkTargetList *tl =
1113 sheet_object_exportable_get_target_list (exportable);
1114 /* _add_table prepends to target_list */
1115 gtk_target_list_add_table (tl, table_targets, 1);
1116 targets = gtk_target_table_new_from_list (tl, &n_targets);
1117 gtk_target_list_unref (tl);
1119 if (imageable) {
1120 GtkTargetList *tl =
1121 sheet_object_get_target_list (imageable);
1122 /* _add_table prepends to target_list */
1123 gtk_target_list_add_table (tl, targets, (exportable)? n_targets: 1);
1124 targets = gtk_target_table_new_from_list (tl, &n_targets);
1125 gtk_target_list_unref (tl);
1127 /* Register a x_clipboard_clear_cb only for CLIPBOARD, not for
1128 * PRIMARY */
1129 ret = gtk_clipboard_set_with_owner (
1130 gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD),
1131 targets, n_targets,
1132 (GtkClipboardGetFunc) x_clipboard_get_cb,
1133 (GtkClipboardClearFunc) x_clipboard_clear_cb,
1134 app);
1135 if (ret) {
1136 if (debug_clipboard ())
1137 g_printerr ("Clipboard successfully claimed.\n");
1139 g_object_set_data_full (app, APP_CLIP_DISP_KEY,
1140 g_slist_prepend (g_object_steal_data (app, APP_CLIP_DISP_KEY),
1141 display),
1142 (GDestroyNotify)g_slist_free);
1145 set_clipman_targets (display, targets, n_targets);
1146 (void)gtk_clipboard_set_with_owner (
1147 gtk_clipboard_get_for_display (display,
1148 GDK_SELECTION_PRIMARY),
1149 targets, n_targets,
1150 (GtkClipboardGetFunc) x_clipboard_get_cb,
1151 NULL,
1152 app);
1153 } else {
1154 if (debug_clipboard ())
1155 g_printerr ("Failed to claim clipboard.\n");
1157 if (exportable || imageable)
1158 gtk_target_table_free (targets, n_targets);
1160 return ret;
1163 void
1164 gnm_x_disown_clipboard (void)
1166 GObject *app = gnm_app_get_app ();
1167 GSList *displays = g_object_steal_data (app, APP_CLIP_DISP_KEY);
1168 GSList *l;
1170 for (l = displays; l; l = l->next) {
1171 GdkDisplay *display = l->data;
1172 gtk_selection_owner_set_for_display (display, NULL,
1173 GDK_SELECTION_PRIMARY,
1174 GDK_CURRENT_TIME);
1175 gtk_selection_owner_set_for_display (display, NULL,
1176 GDK_SELECTION_CLIPBOARD,
1177 GDK_CURRENT_TIME);
1179 g_slist_free (displays);
1182 /* Hand clipboard off to clipboard manager. To be called before workbook
1183 * object is destroyed.
1185 void
1186 gnm_x_store_clipboard_if_needed (Workbook *wb)
1188 Sheet *sheet = gnm_app_clipboard_sheet_get ();
1189 WBCGtk *wbcg = NULL;
1191 g_return_if_fail (GNM_IS_WORKBOOK (wb));
1193 if (sheet && sheet->workbook == wb) {
1194 WORKBOOK_FOREACH_CONTROL (wb, view, control, {
1195 if (GNM_IS_WBC_GTK (control)) {
1196 wbcg = WBC_GTK (control);
1200 if (wbcg) {
1201 GtkClipboard *clip = gtk_clipboard_get_for_display
1202 (gtk_widget_get_display
1203 (GTK_WIDGET (wbcg_toplevel (wbcg))),
1204 GDK_SELECTION_CLIPBOARD);
1205 if (gtk_clipboard_get_owner (clip) == gnm_app_get_app ()) {
1206 if (debug_clipboard ())
1207 g_printerr ("Handing off clipboard\n");
1208 gtk_clipboard_store (clip);