Clipboard: code cleanup
[gnumeric.git] / src / gui-clipboard.c
blob66fb0f4d50918eec210fcda49d6e0183d91056db
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 // ----------------------------------------------------------------------------
54 static gboolean debug_clipboard;
55 static gboolean debug_clipboard_dump;
56 static gboolean debug_clipboard_undump;
58 // ----------------------------------------------------------------------------
60 enum {
61 ATOM_GNUMERIC,
62 ATOM_GOFFICE_GRAPH,
63 // ----------
64 ATOM_UTF8_STRING,
65 ATOM_STRING,
66 ATOM_COMPOUND_TEXT,
67 ATOM_TEXT_HTML,
68 ATOM_TEXT_HTML_WINDOWS,
69 // ----------
70 ATOM_BIFF8,
71 ATOM_BIFF8_OO,
72 ATOM_BIFF8_CITRIX,
73 ATOM_BIFF5,
74 ATOM_BIFF,
75 // ----------
76 ATOM_OOO,
77 ATOM_OOO_WINDOWS,
78 ATOM_OOO11,
79 // ----------
80 ATOM_IMAGE_SVGXML,
81 ATOM_IMAGE_XWMF,
82 ATOM_IMAGE_XEMF,
83 ATOM_IMAGE_PNG,
84 ATOM_IMAGE_JPEG,
85 ATOM_IMAGE_BMP,
86 // ----------
87 ATOM_SAVE_TARGETS,
90 static const char *const atom_names[] = {
91 "application/x-gnumeric",
92 "application/x-goffice-graph",
93 // ----------
94 "UTF8_STRING",
95 "STRING",
96 "COMPOUND_TEXT",
97 "text/html",
98 "HTML Format",
99 // ----------
100 "Biff8",
101 "application/x-openoffice-biff-8;windows_formatname=\"Biff8\"",
102 "_CITRIX_Biff8",
103 "Biff5",
104 "Biff",
105 // ----------
106 "application/x-openoffice;windows_formatname=\"Star Embed Source (XML)\"",
107 "Star Embed Source (XML)",
108 "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"",
109 // ----------
110 "image/svg+xml",
111 "image/x-wmf",
112 "image/x-emf",
113 "image/png",
114 "image/jpeg",
115 "image/bmp",
116 // ----------
117 "SAVE_TARGETS",
120 static GdkAtom atoms[G_N_ELEMENTS(atom_names)];
122 typedef enum {
123 INFO_UNKNOWN,
124 INFO_GNUMERIC,
125 INFO_EXCEL,
126 INFO_OOO,
127 INFO_GENERIC_TEXT,
128 INFO_HTML,
129 INFO_OBJECT,
130 INFO_IMAGE,
131 INFO_STRING
132 } AtomInfoType;
134 static GtkTargetList *generic_text_targets;
135 static GtkTargetList *image_targets;
137 // ----------------------------------------------------------------------------
139 typedef struct {
140 WBCGtk *wbcg;
141 GnmPasteTarget *paste_target;
142 } GnmGtkClipboardCtxt;
144 static void
145 gnm_gtk_clipboard_context_free (GnmGtkClipboardCtxt *ctxt)
147 g_free (ctxt->paste_target);
148 g_free (ctxt);
152 * Emacs hack:
153 * (x-get-selection-internal 'CLIPBOARD 'TARGETS)
156 /* See if this is a "single line + line end", a "multiline" or a "tab separated"
157 * string. If this is _not_ the case we won't invoke the STF, it is
158 * unlikely that the user will actually need it in this case. */
159 static gboolean
160 text_is_single_cell (gchar const *data, int data_len)
162 int i;
164 for (i = 0; i < data_len; i++)
165 if (data[i] == '\n' || data[i] == '\t')
166 return FALSE;
167 return TRUE;
171 static GnmCellRegion *
172 text_to_cell_region (WBCGtk *wbcg,
173 gchar const *data, int data_len,
174 char const *opt_encoding,
175 gboolean fixed_encoding)
177 Workbook *wb = wb_control_get_workbook (GNM_WBC (wbcg));
178 DialogStfResult_t *dialogresult;
179 GnmCellRegion *cr = NULL;
180 gboolean oneline;
181 char *data_converted = NULL;
183 if (!data) {
185 * See Redhat #1160975.
187 * I'm unsure why someone gets NULL here, but this is better
188 * than a crash.
190 data = "";
191 data_len = 0;
194 oneline = text_is_single_cell (data, data_len);
196 if (oneline && (opt_encoding == NULL || strcmp (opt_encoding, "UTF-8") != 0)) {
197 size_t bytes_written;
198 char const *enc = opt_encoding ? opt_encoding : "ASCII";
200 data_converted = g_convert (data, data_len,
201 "UTF-8", enc,
202 NULL, &bytes_written, NULL);
203 if (data_converted) {
204 data = data_converted;
205 data_len = bytes_written;
206 } else {
207 /* Force STF import since we don't know the charset. */
208 oneline = FALSE;
209 fixed_encoding = FALSE;
213 if (oneline) {
214 GODateConventions const *date_conv = workbook_date_conv (wb);
215 GnmCellCopy *cc = gnm_cell_copy_new (
216 (cr = gnm_cell_region_new (NULL)), 0, 0);
217 char *tmp = g_strndup (data, data_len);
219 g_free (data_converted);
221 cc->val = format_match (tmp, NULL, date_conv);
222 if (cc->val)
223 g_free (tmp);
224 else
225 cc->val = value_new_string_nocopy (tmp);
226 cc->texpr = NULL;
228 cr->cols = cr->rows = 1;
229 } else {
230 dialogresult = stf_dialog (wbcg, opt_encoding, fixed_encoding,
231 NULL, FALSE,
232 _("clipboard"), data, data_len);
234 if (dialogresult != NULL) {
235 cr = stf_parse_region (dialogresult->parseoptions,
236 dialogresult->text, NULL, wb);
237 g_return_val_if_fail (cr != NULL, gnm_cell_region_new (NULL));
239 stf_dialog_result_attach_formats_to_cr (dialogresult, cr);
241 stf_dialog_result_free (dialogresult);
242 } else
243 cr = gnm_cell_region_new (NULL);
246 return cr;
249 static void
250 text_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
251 gpointer closure)
253 GnmGtkClipboardCtxt *ctxt = closure;
254 WBCGtk *wbcg = ctxt->wbcg;
255 WorkbookControl *wbc = GNM_WBC (wbcg);
256 GnmPasteTarget *pt = ctxt->paste_target;
257 GnmCellRegion *content = NULL;
258 GdkAtom target = gtk_selection_data_get_target (sel);
259 int sel_len = gtk_selection_data_get_length (sel);
261 if (debug_clipboard) {
262 int maxlen = 1024;
263 char *name = gdk_atom_name (gtk_selection_data_get_target (sel));
264 g_printerr ("Received %d bytes of text for target %s\n",
265 sel_len, name);
266 g_free (name);
267 if (sel_len > 0) {
268 gsf_mem_dump (gtk_selection_data_get_data (sel), MIN (sel_len, maxlen));
269 if (sel_len > maxlen)
270 g_printerr ("...\n");
274 /* Nothing on clipboard? */
275 if (sel_len < 0) {
277 } else if (target == atoms[ATOM_UTF8_STRING]) {
278 content = text_to_cell_region (wbcg, (const char *)gtk_selection_data_get_data (sel),
279 sel_len, "UTF-8", TRUE);
280 } else if (target == atoms[ATOM_COMPOUND_TEXT]) {
281 /* COMPOUND_TEXT is icky. Just let GTK+ do the work. */
282 char *data_utf8 = (char *)gtk_selection_data_get_text (sel);
283 content = text_to_cell_region (wbcg, data_utf8, strlen (data_utf8), "UTF-8", TRUE);
284 g_free (data_utf8);
285 } else if (target == atoms[ATOM_STRING]) {
286 char const *locale_encoding;
287 g_get_charset (&locale_encoding);
289 content = text_to_cell_region (wbcg, (const char *)gtk_selection_data_get_data (sel),
290 sel_len, locale_encoding, FALSE);
292 if (content) {
294 * if the conversion from the X selection -> a cellregion
295 * was canceled this may have content sized -1,-1
297 if (content->cols > 0 && content->rows > 0)
298 cmd_paste_copy (wbc, pt, content);
300 /* Release the resources we used */
301 cellregion_unref (content);
304 gnm_gtk_clipboard_context_free (ctxt);
307 static void
308 utf8_content_received (GtkClipboard *clipboard, const gchar *text,
309 gpointer closure)
311 GnmGtkClipboardCtxt *ctxt = closure;
312 WBCGtk *wbcg = ctxt->wbcg;
313 WorkbookControl *wbc = GNM_WBC (wbcg);
314 GnmPasteTarget *pt = ctxt->paste_target;
315 GnmCellRegion *content = NULL;
317 /* Nothing on clipboard? */
318 if (!text || strlen(text) == 0) {
320 } else {
321 content = text_to_cell_region (wbcg, text, strlen(text), "UTF-8", TRUE);
323 if (content) {
325 * if the conversion from the X selection -> a cellregion
326 * was canceled this may have content sized -1,-1
328 if (content->cols > 0 && content->rows > 0)
329 cmd_paste_copy (wbc, pt, content);
331 /* Release the resources we used */
332 cellregion_unref (content);
335 gnm_gtk_clipboard_context_free (ctxt);
339 * Use the file_opener plugin service to read into a temporary workbook, in
340 * order to copy from it to the paste target. A temporary sheet would do just
341 * as well, but the file_opener service makes workbooks, not sheets.
343 * We use the file_opener service by wrapping the selection data in a GsfInput,
344 * and calling workbook_view_new_from_input.
346 static GnmCellRegion *
347 table_cellregion_read (WorkbookControl *wbc, char const *reader_id,
348 GnmPasteTarget *pt, const guchar *buffer, int length)
350 WorkbookView *wb_view = NULL;
351 Workbook *wb = NULL;
352 GnmCellRegion *ret = NULL;
353 const GOFileOpener *reader = go_file_opener_for_id (reader_id);
354 GOIOContext *ioc;
355 GsfInput *input;
357 if (!reader) {
358 // Likely cause: plugin not loaded
359 g_warning ("No file opener for %s", reader_id);
360 return NULL;
363 ioc = go_io_context_new (GO_CMD_CONTEXT (wbc));
364 input = gsf_input_memory_new (buffer, length, FALSE);
365 wb_view = workbook_view_new_from_input (input, NULL, reader, ioc, NULL);
366 if (go_io_error_occurred (ioc) || wb_view == NULL) {
367 go_io_error_display (ioc);
368 goto out;
371 wb = wb_view_get_workbook (wb_view);
372 if (workbook_sheet_count (wb) > 0) {
373 GnmRange r;
374 Sheet *tmpsheet = workbook_sheet_by_index (wb, 0);
375 GnmRange *rp = g_object_get_data (G_OBJECT (tmpsheet),
376 "DIMENSION");
377 if (rp) {
378 r = *rp;
379 } else {
380 // File format didn't tell us the range being
381 // pasted. Looking at you, LibreOffice!
382 // Make a guess.
384 GnmRange fullr;
385 GnmStyle **col_defaults =
386 sheet_style_most_common (tmpsheet, TRUE);
388 range_init_full_sheet (&fullr, tmpsheet);
390 r = sheet_get_cells_extent (tmpsheet);
391 sheet_style_get_nondefault_extent
392 (tmpsheet, &r, &fullr, col_defaults);
394 g_free (col_defaults);
396 // Just in case there was absolutely nothing in
397 // tmpsheet:
398 if (r.start.col > r.end.col)
399 range_init (&r, 0, 0, 0, 0);
401 ret = clipboard_copy_range (tmpsheet, &r);
404 /* This isn't particularly right, but we are going to delete
405 the workbook shortly. See #490479. */
406 WORKBOOK_FOREACH_SHEET (wb, sheet, {
407 cellregion_invalidate_sheet (ret, sheet);
410 out:
411 if (wb_view)
412 g_object_unref (wb_view);
413 if (wb)
414 g_object_unref (wb);
415 g_object_unref (ioc);
416 g_object_unref (input);
418 return ret;
421 static void
422 image_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
423 gpointer closure)
425 GnmGtkClipboardCtxt *ctxt = closure;
426 WBCGtk *wbcg = ctxt->wbcg;
427 GnmPasteTarget *pt = ctxt->paste_target;
428 int sel_len = gtk_selection_data_get_length (sel);
430 if (debug_clipboard) {
431 int maxlen = 1024;
432 char *name = gdk_atom_name (gtk_selection_data_get_target (sel));
433 g_printerr ("Received %d bytes of image for target %s\n",
434 sel_len,
435 name);
436 g_free (name);
437 if (sel_len > 0) {
438 gsf_mem_dump (gtk_selection_data_get_data (sel), MIN (sel_len, maxlen));
439 if (sel_len > maxlen)
440 g_printerr ("...\n");
444 if (sel_len > 0) {
445 scg_paste_image (wbcg_cur_scg (wbcg), &pt->range,
446 gtk_selection_data_get_data (sel), sel_len);
449 gnm_gtk_clipboard_context_free (ctxt);
452 static void
453 parse_ms_headers (const char *data, size_t length, size_t *start, size_t *end)
455 GHashTable *headers = g_hash_table_new_full
456 (g_str_hash, g_str_equal, g_free, g_free);
457 size_t limit = length;
458 size_t i = 0;
459 char *key = NULL;
460 char *value = NULL;
461 long sf, ef;
462 const char *v;
464 while (i < limit && data[i] != '<') {
465 size_t j, k;
467 for (j = i; j < limit; j++) {
468 if (data[j] == ':') {
469 key = g_strndup (data + i, j - i);
470 break;
472 if (g_ascii_isspace (data[j]))
473 goto bad;
475 if (j >= limit)
476 goto bad;
477 j++;
479 for (k = j; k < limit; k++) {
480 if (data[k] == '\n' || data[k] == '\r') {
481 value = g_strndup (data + j, k - j);
482 break;
485 if (k >= limit)
486 goto bad;
487 while (g_ascii_isspace (data[k]))
488 k++;
490 i = k;
492 if (debug_clipboard)
493 g_printerr ("MS HTML Header [%s] => [%s]\n", key, value);
495 if (strcmp (key, "StartHTML") == 0) {
496 long l = strtol (value, NULL, 10);
497 limit = MIN (limit, (size_t)MAX (0, l));
500 g_hash_table_replace (headers, key, value);
501 key = value = NULL;
504 v = g_hash_table_lookup (headers, "StartFragment");
505 sf = v ? strtol (v, NULL, 10) : -1;
506 if (sf < (long)limit)
507 goto bad;
509 v = g_hash_table_lookup (headers, "EndFragment");
510 ef = v ? strtol (v, NULL, 10) : -1;
511 if (ef < sf || ef > (long)length)
512 goto bad;
514 *start = sf;
515 *end = ef;
516 goto out;
518 bad:
519 g_free (key);
520 g_free (value);
521 *start = 0;
522 *end = length;
524 out:
525 g_hash_table_destroy (headers);
529 static void
530 table_content_received (GtkClipboard *clipboard, GtkSelectionData *sel,
531 gpointer closure)
533 GnmGtkClipboardCtxt *ctxt = closure;
534 WBCGtk *wbcg = ctxt->wbcg;
535 WorkbookControl *wbc = GNM_WBC (wbcg);
536 GnmPasteTarget *pt = ctxt->paste_target;
537 GnmCellRegion *content = NULL;
538 GdkAtom target = gtk_selection_data_get_target (sel);
539 const guint8 *buffer = gtk_selection_data_get_data (sel);
540 int sel_len = gtk_selection_data_get_length (sel);
542 if (debug_clipboard) {
543 int maxlen = 1024;
544 char *name = gdk_atom_name (gtk_selection_data_get_target (sel));
545 g_printerr ("Received %d bytes of table for target %s\n",
546 sel_len, name);
547 g_free (name);
548 if (sel_len > 0) {
549 gsf_mem_dump (buffer, MIN (sel_len, maxlen));
550 if (sel_len > maxlen)
551 g_printerr ("...\n");
555 if (gnm_debug_flag ("clipboard-dump")) {
556 g_file_set_contents ("paste-to-gnumeric.dat",
557 buffer, sel_len < 0 ? 0 : sel_len, NULL);
560 /* Nothing on clipboard? */
561 if (sel_len < 0) {
563 } else if (target == atoms[ATOM_GNUMERIC]) {
564 /* The data is the gnumeric specific XML interchange format */
565 GOIOContext *io_context =
566 go_io_context_new (GO_CMD_CONTEXT (wbcg));
567 content = gnm_xml_cellregion_read
568 (wbc, io_context,
569 pt->sheet,
570 (const char *)buffer, sel_len);
571 g_object_unref (io_context);
572 } else if (target == atoms[ATOM_OOO] ||
573 target == atoms[ATOM_OOO_WINDOWS] ||
574 target == atoms[ATOM_OOO11]) {
575 content = table_cellregion_read (wbc, "Gnumeric_OpenCalc:openoffice",
576 pt, buffer,
577 sel_len);
578 } else if (target == atoms[ATOM_TEXT_HTML] ||
579 target == atoms[ATOM_TEXT_HTML_WINDOWS]) {
580 size_t start = 0, end = sel_len;
582 if (target == atoms[ATOM_TEXT_HTML_WINDOWS]) {
583 /* See bug 143084 */
584 parse_ms_headers (buffer, sel_len, &start, &end);
587 content = table_cellregion_read (wbc, "Gnumeric_html:html",
589 buffer + start,
590 end - start);
591 } else if (target == atoms[ATOM_BIFF8] ||
592 target == atoms[ATOM_BIFF8_CITRIX] ||
593 target == atoms[ATOM_BIFF8_OO] ||
594 target == atoms[ATOM_BIFF5] ||
595 target == atoms[ATOM_BIFF]) {
596 content = table_cellregion_read (wbc, "Gnumeric_Excel:excel",
597 pt, buffer,
598 sel_len);
600 if (content) {
602 * if the conversion from the X selection -> a cellregion
603 * was canceled this may have content sized -1,-1
605 if ((content->cols > 0 && content->rows > 0) ||
606 content->objects != NULL)
607 cmd_paste_copy (wbc, pt, content);
609 /* Release the resources we used */
610 cellregion_unref (content);
613 gnm_gtk_clipboard_context_free (ctxt);
616 static gboolean
617 find_in_table (GdkAtom *targets, int n, GdkAtom a)
619 int i;
620 for (i = 0; i < n; i++)
621 if (targets[i] == a)
622 return TRUE;
623 return FALSE;
627 * x_targets_received:
629 * Invoked when the selection has been received by our application.
630 * This is triggered by a call we do to gtk_clipboard_request_contents.
632 * We try to import a spreadsheet/table, next an image, and finally fall back
633 * to a string format if the others fail, e.g. for html which does not
634 * contain a table.
636 static void
637 x_targets_received (GtkClipboard *clipboard, GdkAtom *targets,
638 gint n_targets, gpointer closure)
640 GnmGtkClipboardCtxt *ctxt = closure;
641 int i;
642 unsigned ui;
644 // In order of preference
645 static const int table_fmts[] = {
646 ATOM_GNUMERIC,
647 ATOM_BIFF8, ATOM_BIFF8_CITRIX,
648 ATOM_OOO, ATOM_OOO11, ATOM_OOO_WINDOWS,
649 ATOM_BIFF5, ATOM_BIFF,
650 ATOM_TEXT_HTML, ATOM_TEXT_HTML_WINDOWS,
653 // In order of preference
654 static const int string_fmts[] = {
655 ATOM_UTF8_STRING,
656 ATOM_STRING,
657 ATOM_COMPOUND_TEXT
660 // Nothing on clipboard? Try text.
661 if (targets == NULL || n_targets == 0) {
662 gtk_clipboard_request_text (clipboard, utf8_content_received,
663 ctxt);
664 return;
667 if (debug_clipboard) {
668 int j;
670 for (j = 0; j < n_targets; j++) {
671 char *name = gdk_atom_name (targets[j]);
672 g_printerr ("Clipboard target %d is %s\n",
673 j, name);
674 g_free (name);
678 // First look for anything that can be considered a spreadsheet
679 for (ui = 0; ui < G_N_ELEMENTS(table_fmts); ui++) {
680 GdkAtom atom = atoms[table_fmts[ui]];
681 if (find_in_table (targets, n_targets, atom)) {
682 gtk_clipboard_request_contents (clipboard, atom,
683 table_content_received,
684 ctxt);
685 return;
689 // Try an image format
690 for (i = 0; i < n_targets; i++) {
691 GdkAtom atom = targets[i];
692 if (gtk_target_list_find (image_targets, atom, NULL)) {
693 gtk_clipboard_request_contents (clipboard, atom,
694 image_content_received,
695 ctxt);
696 return;
700 // Try a string format
701 for (ui = 0; ui < G_N_ELEMENTS (string_fmts); ui++) {
702 GdkAtom atom = atoms[string_fmts[ui]];
703 if (find_in_table (targets, n_targets, atom)) {
704 gtk_clipboard_request_contents (clipboard, atom,
705 text_content_received,
706 ctxt);
707 return;
711 // Give up
712 gnm_gtk_clipboard_context_free (ctxt);
715 /* Cheezy implementation: paste into a temporary workbook, save that. */
716 static guchar *
717 table_cellregion_write (GOCmdContext *ctx, GnmCellRegion *cr,
718 const char *saver_id, int *size)
720 guchar *ret = NULL;
721 const GOFileSaver *saver;
722 GsfOutput *output;
723 GOIOContext *ioc;
724 Workbook *wb;
725 WorkbookView *wb_view;
726 Sheet *sheet;
727 GnmPasteTarget pt;
728 GnmRange r;
730 if (gnm_debug_flag ("clipboard-undump")) {
731 gsize siz;
732 gchar *contents;
733 if (g_file_get_contents ("paste-from-gnumeric.dat", &contents,
734 &siz, NULL)) {
735 g_printerr ("Sending %d prepackaged bytes.\n",
736 (int)siz);
737 *size = siz;
738 return (guchar *)contents;
742 *size = 0;
744 saver = go_file_saver_for_id (saver_id);
745 if (!saver) {
746 // Likely cause: plugin not loaded
747 g_printerr ("Failed to get saver for %s for clipboard use.\n",
748 saver_id);
749 return NULL;
752 output = gsf_output_memory_new ();
753 ioc = go_io_context_new (ctx);
756 int cols = cr->cols;
757 int rows = cr->rows;
758 gnm_sheet_suggest_size (&cols, &rows);
759 wb = workbook_new ();
760 workbook_sheet_add (wb, -1, cols, rows);
763 wb_view = workbook_view_new (wb);
765 sheet = workbook_sheet_by_index (wb, 0);
766 range_init (&r, 0, 0, cr->cols - 1, cr->rows - 1);
768 paste_target_init (&pt, sheet, &r,
769 PASTE_AS_VALUES | PASTE_FORMATS |
770 PASTE_COMMENTS | PASTE_OBJECTS);
771 if (clipboard_paste_region (cr, &pt, ctx) == FALSE) {
772 go_file_saver_save (saver, ioc, GO_VIEW (wb_view), output);
773 if (!go_io_error_occurred (ioc)) {
774 GsfOutputMemory *omem = GSF_OUTPUT_MEMORY (output);
775 gsf_off_t osize = gsf_output_size (output);
776 const guint8 *data = gsf_output_memory_get_bytes (omem);
778 if (gnm_debug_flag ("clipboard-dump")) {
779 g_file_set_contents ("paste-from-gnumeric.dat",
780 data, osize, NULL);
783 *size = osize;
784 if (*size == osize) {
785 ret = g_memdup (data, *size);
786 } else {
787 g_warning ("Overflow"); /* Far fetched! */
791 if (!gsf_output_is_closed (output))
792 gsf_output_close (output);
793 g_object_unref (wb_view);
794 g_object_unref (wb);
795 g_object_unref (ioc);
796 g_object_unref (output);
798 return ret;
801 static guchar *
802 image_write (GnmCellRegion *cr, gchar const *mime_type, int *size)
804 guchar *ret = NULL;
805 SheetObject *so = NULL;
806 char *format;
807 GsfOutput *output;
808 GsfOutputMemory *omem;
809 gsf_off_t osize;
810 GSList *l;
812 *size = -1;
814 g_return_val_if_fail (cr->objects != NULL, NULL);
815 so = GNM_SO (cr->objects->data);
816 g_return_val_if_fail (so != NULL, NULL);
818 if (strncmp (mime_type, "image/", 6) != 0)
819 return ret;
820 for (l = cr->objects; l != NULL; l = l->next) {
821 if (GNM_IS_SO_IMAGEABLE (GNM_SO (l->data))) {
822 so = GNM_SO (l->data);
823 break;
826 if (so == NULL) {
827 g_warning ("non imageable object requested as image\n");
828 return ret;
831 format = go_mime_to_image_format (mime_type);
832 if (!format) {
833 g_warning ("No image format for %s\n", mime_type);
834 g_free (format);
835 return ret;
837 output = gsf_output_memory_new ();
838 omem = GSF_OUTPUT_MEMORY (output);
839 sheet_object_write_image (so, format, 150.0, output, NULL);
840 osize = gsf_output_size (output);
842 *size = osize;
843 if (*size == osize) {
844 ret = g_malloc (*size);
845 memcpy (ret, gsf_output_memory_get_bytes (omem), *size);
846 } else {
847 g_warning ("Overflow"); /* Far fetched! */
849 gsf_output_close (output);
850 g_object_unref (output);
851 g_free (format);
853 return ret;
856 static guchar *
857 object_write (GnmCellRegion *cr, gchar const *mime_type, int *size)
859 guchar *ret = NULL;
860 SheetObject *so = NULL;
861 GsfOutput *output;
862 GsfOutputMemory *omem;
863 gsf_off_t osize;
864 GSList *l;
866 *size = -1;
868 g_return_val_if_fail (cr->objects != NULL, NULL);
869 so = GNM_SO (cr->objects->data);
870 g_return_val_if_fail (so != NULL, NULL);
872 for (l = cr->objects; l != NULL; l = l->next) {
873 if (GNM_IS_SO_EXPORTABLE (GNM_SO (l->data))) {
874 so = GNM_SO (l->data);
875 break;
878 if (so == NULL) {
879 g_warning ("non exportable object requested\n");
880 return ret;
882 output = gsf_output_memory_new ();
883 omem = GSF_OUTPUT_MEMORY (output);
884 sheet_object_write_object (so, mime_type, output, NULL,
885 gnm_conventions_default);
886 osize = gsf_output_size (output);
888 *size = osize;
889 if (*size == osize)
890 ret = g_memdup (gsf_output_memory_get_bytes (omem), *size);
891 else
892 g_warning ("Overflow"); /* Far fetched! */
893 gsf_output_close (output);
894 g_object_unref (output);
896 return ret;
900 * x_clipboard_get_cb
902 * Callback invoked when another application requests we render the selection.
904 static void
905 x_clipboard_get_cb (GtkClipboard *gclipboard, GtkSelectionData *selection_data,
906 guint info_, gpointer app)
908 gboolean to_gnumeric = FALSE, content_needs_free = FALSE;
909 GnmCellRegion *clipboard = gnm_app_clipboard_contents_get ();
910 Sheet *sheet = gnm_app_clipboard_sheet_get ();
911 GnmRange const *a = gnm_app_clipboard_area_get ();
912 GOCmdContext *ctx = gnm_cmd_context_stderr_new ();
913 GdkAtom target = gtk_selection_data_get_target (selection_data);
914 AtomInfoType info = info_;
915 gchar *target_name = gdk_atom_name (target);
917 if (debug_clipboard)
918 g_printerr ("clipboard requested, target=%s\n", target_name);
921 * There are 4 cases. What variables are valid depends on case:
922 * source is
923 * a cut: clipboard NULL, sheet, area non-NULL.
924 * a copy: clipboard, sheet, area all non-NULL.
925 * a cut, source closed: clipboard, sheet, area all NULL.
926 * a copy, source closed: clipboard non-NULL, sheet, area non-NULL.
928 * If the source is a cut, we copy it for pasting. We
929 * postpone clearing it until after the selection has been
930 * rendered to the requested format.
932 if (clipboard == NULL && sheet != NULL) {
933 content_needs_free = TRUE;
934 clipboard = clipboard_copy_range (sheet, a);
937 if (clipboard == NULL)
938 goto out;
940 /* What format does the other application want? */
941 if (target == atoms[ATOM_GNUMERIC]) {
942 GsfOutputMemory *output = gnm_cellregion_to_xml (clipboard);
943 if (output) {
944 gsf_off_t size = gsf_output_size (GSF_OUTPUT (output));
945 gconstpointer data = gsf_output_memory_get_bytes (output);
946 if (gnm_debug_flag ("clipboard-dump")) {
947 g_file_set_contents ("paste-from-gnumeric.dat",
948 data, size, NULL);
950 if (debug_clipboard)
951 g_printerr ("clipboard .gnumeric of %d bytes\n",
952 (int)size);
953 gtk_selection_data_set
954 (selection_data, target, 8,
955 data,
956 size);
957 g_object_unref (output);
958 to_gnumeric = TRUE;
960 } else if (info == INFO_HTML) {
961 const char *saver_id = "Gnumeric_html:xhtml_range";
962 int buffer_size;
963 guchar *buffer = table_cellregion_write (ctx, clipboard,
964 saver_id,
965 &buffer_size);
966 if (debug_clipboard)
967 g_message ("clipboard html of %d bytes",
968 buffer_size);
969 gtk_selection_data_set (selection_data,
970 target, 8,
971 buffer, buffer_size);
972 g_free (buffer);
973 } else if (info == INFO_EXCEL) {
974 const char *saver_id = "Gnumeric_Excel:excel_biff8";
975 int buffer_size;
976 guchar *buffer = table_cellregion_write (ctx, clipboard,
977 saver_id,
978 &buffer_size);
979 if (debug_clipboard)
980 g_message ("clipboard biff8 of %d bytes",
981 buffer_size);
982 gtk_selection_data_set (selection_data,
983 target, 8,
984 buffer, buffer_size);
985 g_free (buffer);
986 } else if (target == atoms[ATOM_GOFFICE_GRAPH] ||
987 g_slist_find_custom (go_components_get_mime_types (), target_name, (GCompareFunc) strcmp) != NULL) {
988 int buffer_size;
989 guchar *buffer = object_write (clipboard, target_name,
990 &buffer_size);
991 if (debug_clipboard)
992 g_message ("clipboard graph of %d bytes",
993 buffer_size);
994 gtk_selection_data_set (selection_data,
995 target, 8,
996 buffer, buffer_size);
997 g_free (buffer);
998 } else if (info == INFO_IMAGE) {
999 int buffer_size;
1000 guchar *buffer = image_write (clipboard, target_name,
1001 &buffer_size);
1002 if (debug_clipboard)
1003 g_message ("clipboard image of %d bytes",
1004 buffer_size);
1005 gtk_selection_data_set (selection_data,
1006 target, 8,
1007 buffer, buffer_size);
1008 g_free (buffer);
1009 } else if (target == atoms[ATOM_SAVE_TARGETS]) {
1010 /* We implicitly registered this when calling
1011 * gtk_clipboard_set_can_store. We're supposed to
1012 * ignore it. */
1013 } else if (info == INFO_STRING) {
1014 Workbook *wb = clipboard->origin_sheet->workbook;
1015 GString *res = cellregion_to_string (clipboard,
1016 TRUE, workbook_date_conv (wb));
1017 if (res != NULL) {
1018 if (debug_clipboard)
1019 g_message ("clipboard text of %d bytes",
1020 (int)res->len);
1021 gtk_selection_data_set_text (selection_data,
1022 res->str, res->len);
1023 g_string_free (res, TRUE);
1024 } else {
1025 if (debug_clipboard)
1026 g_message ("clipboard empty text");
1027 gtk_selection_data_set_text (selection_data, "", 0);
1029 } else
1030 gtk_selection_data_set_text (selection_data, "", 0);
1033 * If this was a CUT operation we need to clear the content that
1034 * was pasted into another application and release the stuff on
1035 * the clipboard
1037 if (content_needs_free) {
1039 /* If the other app was a gnumeric, emulate a cut */
1040 if (to_gnumeric) {
1041 GOUndo *redo, *undo;
1042 GnmSheetRange *sr = gnm_sheet_range_new (sheet, a);
1043 SheetView const *sv = gnm_app_clipboard_sheet_view_get ();
1044 SheetControl *sc = g_ptr_array_index (sv->controls, 0);
1045 WorkbookControl *wbc = sc_wbc (sc);
1046 char *name;
1047 char *text;
1049 redo = sheet_clear_region_undo
1050 (sr,
1051 CLEAR_VALUES|CLEAR_COMMENTS|CLEAR_RECALC_DEPS);
1052 undo = clipboard_copy_range_undo (sheet, a);
1053 name = undo_range_name (sheet, a);
1054 text = g_strdup_printf (_("Cut of %s"), name);
1055 g_free (name);
1056 cmd_generic (wbc, text, undo, redo);
1057 g_free (text);
1058 gnm_app_clipboard_clear (TRUE);
1061 cellregion_unref (clipboard);
1063 out:
1064 g_free (target_name);
1065 g_object_unref (ctx);
1069 * x_clipboard_clear_cb:
1071 * Callback for the "we lost the X selection" signal.
1073 static void
1074 x_clipboard_clear_cb (GtkClipboard *clipboard, gpointer app_)
1076 if (debug_clipboard)
1077 g_printerr ("Lost clipboard ownership.\n");
1079 gnm_app_clipboard_clear (FALSE);
1082 void
1083 gnm_x_request_clipboard (WBCGtk *wbcg, GnmPasteTarget const *pt)
1085 GnmGtkClipboardCtxt *ctxt;
1086 GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (wbcg_toplevel (wbcg)));
1087 GtkClipboard *clipboard =
1088 gtk_clipboard_get_for_display
1089 (display,
1090 gnm_conf_get_cut_and_paste_prefer_clipboard ()
1091 ? GDK_SELECTION_CLIPBOARD
1092 : GDK_SELECTION_PRIMARY);
1094 ctxt = g_new (GnmGtkClipboardCtxt, 1);
1095 ctxt->wbcg = wbcg;
1096 ctxt->paste_target = g_new (GnmPasteTarget, 1);
1097 *ctxt->paste_target = *pt;
1099 /* Query the formats, This will callback x_targets_received */
1100 gtk_clipboard_request_targets (clipboard,
1101 x_targets_received, ctxt);
1104 /* Restrict the set of formats offered to clipboard manager. */
1105 /* We include bmp in the whitelist because that's the only image format
1106 * we share with OOo over clipboard (!) */
1108 static gboolean
1109 is_clipman_target (const char *target)
1111 return (g_str_equal (target, atom_names[ATOM_GNUMERIC]) ||
1112 g_str_equal (target, atom_names[ATOM_GOFFICE_GRAPH]) ||
1113 g_str_equal (target, atom_names[ATOM_TEXT_HTML]) ||
1114 g_str_equal (target, atom_names[ATOM_UTF8_STRING]) ||
1115 g_str_equal (target, atom_names[ATOM_BIFF8_OO]) ||
1116 g_str_equal (target, atom_names[ATOM_IMAGE_SVGXML]) ||
1117 g_str_equal (target, atom_names[ATOM_IMAGE_XWMF]) ||
1118 g_str_equal (target, atom_names[ATOM_IMAGE_XEMF]) ||
1119 g_str_equal (target, atom_names[ATOM_IMAGE_PNG]) ||
1120 g_str_equal (target, atom_names[ATOM_IMAGE_JPEG]) ||
1121 g_str_equal (target, atom_names[ATOM_IMAGE_BMP]));
1124 static void
1125 set_clipman_targets (GdkDisplay *disp, GArray *targets)
1127 GArray *allowed = g_array_new (FALSE, FALSE, sizeof (GtkTargetEntry));
1128 unsigned ui;
1130 for (ui = 0; ui < targets->len; ui++) {
1131 GtkTargetEntry *te = &g_array_index (targets, GtkTargetEntry, ui);
1132 if (is_clipman_target (te->target))
1133 g_array_append_val (allowed, *te);
1136 gtk_clipboard_set_can_store
1137 (gtk_clipboard_get_for_display
1138 (disp, GDK_SELECTION_CLIPBOARD),
1139 &g_array_index (allowed, GtkTargetEntry, 0),
1140 allowed->len);
1142 g_array_free (allowed, TRUE);
1145 static void
1146 add_target (GArray *targets, const char *target, int flags, AtomInfoType info)
1148 GtkTargetEntry t;
1149 t.target = (char *)target;
1150 t.flags = flags;
1151 t.info = info;
1152 g_array_append_val (targets, t);
1156 static void
1157 add_target_list (GArray *targets, GtkTargetList *src, AtomInfoType info)
1159 int n;
1160 GtkTargetEntry *entries = gtk_target_table_new_from_list (src, &n);
1161 unsigned ui = targets->len;
1162 g_array_append_vals (targets, entries, n);
1163 if (info != INFO_UNKNOWN) {
1164 for (; ui < targets->len; ui++)
1165 g_array_index (targets, GtkTargetEntry, ui).info = info;
1167 gtk_target_table_free (entries, n);
1170 gboolean
1171 gnm_x_claim_clipboard (GdkDisplay *display)
1173 GnmCellRegion *content = gnm_app_clipboard_contents_get ();
1174 SheetObject *imageable = NULL, *exportable = NULL;
1175 GArray *targets = g_array_new (FALSE, FALSE, sizeof (GtkTargetEntry));
1176 gboolean ret;
1177 GObject *app = gnm_app_get_app ();
1178 gboolean no_cells = (!content) || (content->cols <= 0 || content->rows <= 0);
1180 if (no_cells) {
1181 GSList *ptr = content ? content->objects : NULL;
1183 add_target (targets, atom_names[ATOM_GNUMERIC], 0, INFO_GNUMERIC);
1185 for (; ptr != NULL; ptr = ptr->next) {
1186 SheetObject *candidate = GNM_SO (ptr->data);
1187 if (exportable == NULL && GNM_IS_SO_EXPORTABLE (candidate))
1188 exportable = candidate;
1189 if (imageable == NULL && GNM_IS_SO_IMAGEABLE (candidate))
1190 imageable = candidate;
1192 } else {
1193 add_target (targets, atom_names[ATOM_GNUMERIC], 0, INFO_GNUMERIC);
1194 add_target (targets, atom_names[ATOM_BIFF8], 0, INFO_EXCEL);
1195 add_target (targets, atom_names[ATOM_BIFF8_CITRIX], 0, INFO_EXCEL);
1196 add_target (targets, atom_names[ATOM_BIFF8_OO], 0, INFO_EXCEL);
1197 #ifdef G_OS_WIN32
1198 add_target (targets, atom_names[ATOM_TEXT_HTML_WINDOWS], 0, INFO_HTML);
1199 #else
1200 add_target (targets, atom_names[ATOM_TEXT_HTML], 0, INFO_HTML);
1201 #endif
1202 add_target (targets, atom_names[ATOM_UTF8_STRING], 0, INFO_STRING);
1203 add_target (targets, atom_names[ATOM_COMPOUND_TEXT], 0, INFO_STRING);
1204 add_target (targets, atom_names[ATOM_STRING], 0, INFO_STRING);
1207 if (exportable) {
1208 GtkTargetList *tl =
1209 sheet_object_exportable_get_target_list (exportable);
1210 add_target_list (targets, tl, INFO_OBJECT);
1211 gtk_target_list_unref (tl);
1214 if (imageable) {
1215 GtkTargetList *tl =
1216 sheet_object_get_target_list (imageable);
1217 add_target_list (targets, tl, INFO_IMAGE);
1218 gtk_target_list_unref (tl);
1221 /* Register a x_clipboard_clear_cb only for CLIPBOARD, not for
1222 * PRIMARY */
1223 ret = gtk_clipboard_set_with_owner (
1224 gtk_clipboard_get_for_display (display, GDK_SELECTION_CLIPBOARD),
1225 &g_array_index(targets,GtkTargetEntry,0), targets->len,
1226 x_clipboard_get_cb,
1227 x_clipboard_clear_cb,
1228 app);
1229 if (ret) {
1230 if (debug_clipboard) {
1231 unsigned ui;
1232 g_printerr ("Clipboard successfully claimed.\n");
1233 g_printerr ("Clipboard targets offered: ");
1234 for (ui = 0; ui < targets->len; ui++) {
1235 g_printerr ("%s%s",
1236 (ui ? ", " : ""),
1237 g_array_index(targets,GtkTargetEntry,ui).target);
1239 g_printerr ("\n");
1242 g_object_set_data_full (app, APP_CLIP_DISP_KEY,
1243 g_slist_prepend (g_object_steal_data (app, APP_CLIP_DISP_KEY),
1244 display),
1245 (GDestroyNotify)g_slist_free);
1247 set_clipman_targets (display, targets);
1248 (void)gtk_clipboard_set_with_owner (
1249 gtk_clipboard_get_for_display (display,
1250 GDK_SELECTION_PRIMARY),
1251 &g_array_index(targets,GtkTargetEntry,0), targets->len,
1252 x_clipboard_get_cb,
1253 NULL,
1254 app);
1255 } else {
1256 if (debug_clipboard)
1257 g_printerr ("Failed to claim clipboard.\n");
1260 g_array_free (targets, TRUE);
1262 return ret;
1265 void
1266 gnm_x_disown_clipboard (void)
1268 GObject *app = gnm_app_get_app ();
1269 GSList *displays = g_object_steal_data (app, APP_CLIP_DISP_KEY);
1270 GSList *l;
1272 for (l = displays; l; l = l->next) {
1273 GdkDisplay *display = l->data;
1274 gtk_selection_owner_set_for_display (display, NULL,
1275 GDK_SELECTION_PRIMARY,
1276 GDK_CURRENT_TIME);
1277 gtk_selection_owner_set_for_display (display, NULL,
1278 GDK_SELECTION_CLIPBOARD,
1279 GDK_CURRENT_TIME);
1281 g_slist_free (displays);
1284 /* Hand clipboard off to clipboard manager. To be called before workbook
1285 * object is destroyed.
1287 void
1288 gnm_x_store_clipboard_if_needed (Workbook *wb)
1290 Sheet *sheet = gnm_app_clipboard_sheet_get ();
1291 WBCGtk *wbcg = NULL;
1293 g_return_if_fail (GNM_IS_WORKBOOK (wb));
1295 if (sheet && sheet->workbook == wb) {
1296 WORKBOOK_FOREACH_CONTROL (wb, view, control, {
1297 if (GNM_IS_WBC_GTK (control)) {
1298 wbcg = WBC_GTK (control);
1302 if (wbcg) {
1303 GtkClipboard *clip = gtk_clipboard_get_for_display
1304 (gtk_widget_get_display
1305 (GTK_WIDGET (wbcg_toplevel (wbcg))),
1306 GDK_SELECTION_CLIPBOARD);
1307 if (gtk_clipboard_get_owner (clip) == gnm_app_get_app ()) {
1308 if (debug_clipboard)
1309 g_printerr ("Handing off clipboard\n");
1310 gtk_clipboard_store (clip);
1317 * gui_clipboard_init: (skip)
1319 void
1320 gui_clipboard_init (void)
1322 unsigned ui;
1324 debug_clipboard = gnm_debug_flag ("clipboard");
1325 debug_clipboard_dump = gnm_debug_flag ("clipboard-dump");
1326 debug_clipboard_undump = gnm_debug_flag ("clipboard-undump");
1328 for (ui = 0; ui < G_N_ELEMENTS (atoms); ui++)
1329 atoms[ui] = gdk_atom_intern_static_string (atom_names[ui]);
1331 generic_text_targets = gtk_target_list_new (NULL, 0);
1332 gtk_target_list_add_text_targets (generic_text_targets, INFO_STRING);
1334 image_targets = gtk_target_list_new (NULL, 0);
1335 gtk_target_list_add_image_targets (image_targets, 0, FALSE);
1339 * gui_clipboard_shutdown: (skip)
1341 void
1342 gui_clipboard_shutdown (void)
1344 gtk_target_list_unref (generic_text_targets);
1345 gtk_target_list_unref (image_targets);