1 /*
2 * clipboard.c: A temporary store for contents from a worksheet
3 *
4 * Copyright (C) 2000-2008 Jody Goldberg (jody@gnome.org)
5 * 1999 Miguel de Icaza (miguel@gnu.org)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
24 #include <clipboard.h>
25
26 #include <sheet.h>
27 #include <cell.h>
28 #include <sheet-style.h>
29 #include <sheet-merge.h>
30 #include <dependent.h>
31 #include <selection.h>
32 #include <command-context.h>
33 #include <workbook-control.h>
34 #include <workbook.h>
35 #include <ranges.h>
36 #include <colrow.h>
37 #include <expr.h>
38 #include <value.h>
39 #include <mstyle.h>
40 #include <style-conditions.h>
41 #include <stf-parse.h>
42 #include <gnm-format.h>
43 #include <sheet-object-cell-comment.h>
44
45 #include <glib/gi18n-lib.h>
46 #include <locale.h>
47 #include <string.h>
48 #include <goffice/goffice.h>
49
50 #ifndef USE_CELL_COPY_POOLS
51 #define USE_CELL_COPY_POOLS 1
52 #endif
53
54 #if USE_CELL_COPY_POOLS
55 /* Memory pool for GnmCellCopy. */
56 static GOMemChunk *cell_copy_pool;
57 #define CHUNK_ALLOC(T,p) ((T*)go_mem_chunk_alloc (p))
58 #define CHUNK_FREE(p,v) go_mem_chunk_free ((p), (v))
59 #else
60 #define CHUNK_ALLOC(T,c) g_new (T,1)
61 #define CHUNK_FREE(p,v) g_free ((v))
62 #endif
63
64 /* creating a boxed type for GnmCellCopy (needed by introspection) */
65 static gpointer
pointer_dup(gpointer * cc)66 pointer_dup (gpointer *cc)
67 {
68 return cc;
69 }
70
71 GType
gnm_cell_copy_get_type(void)72 gnm_cell_copy_get_type (void)
73 {
74 static GType t = 0;
75
76 if (t == 0) {
77 t = g_boxed_type_register_static ("GnmCellCopy",
78 (GBoxedCopyFunc)pointer_dup,
79 (GBoxedFreeFunc)pointer_dup);
80 }
81 return t;
82 }
83
84 /* creating a boxed type for GnmPasteTarget (needed by introspection) */
85
86 static GnmPasteTarget *
gnm_paste_target_copy(GnmPasteTarget * pt)87 gnm_paste_target_copy (GnmPasteTarget *pt)
88 {
89 return g_memdup (pt, sizeof (*pt));
90 }
91
92 GType
gnm_paste_target_get_type(void)93 gnm_paste_target_get_type (void)
94 {
95 static GType t = 0;
96
97 if (t == 0) {
98 t = g_boxed_type_register_static ("GnmPasteTarget",
99 (GBoxedCopyFunc)gnm_paste_target_copy,
100 (GBoxedFreeFunc)g_free);
101 }
102 return t;
103 }
104
105 GnmPasteTarget *
gnm_paste_target_new(Sheet * sheet,GnmRange * r,GnmPasteFlags flags)106 gnm_paste_target_new (Sheet *sheet, GnmRange *r, GnmPasteFlags flags)
107 {
108 GnmPasteTarget *res = g_new (GnmPasteTarget, 1);
109 paste_target_init (res, sheet, r, flags);
110 return res;
111 }
112
113
114 static gboolean
cell_has_expr_or_number_or_blank(GnmCell const * cell)115 cell_has_expr_or_number_or_blank (GnmCell const * cell)
116 {
117 return (gnm_cell_is_empty (cell) ||
118 (cell != NULL && gnm_cell_is_number (cell)) ||
119 (cell != NULL && gnm_cell_has_expr (cell)));
120 }
121
122 static GnmExpr const *
contents_as_expr(GnmExprTop const * texpr,GnmValue const * val)123 contents_as_expr (GnmExprTop const *texpr, GnmValue const *val)
124 {
125 if (texpr)
126 return gnm_expr_copy (texpr->expr);
127 if (VALUE_IS_EMPTY (val))
128 return gnm_expr_new_constant (value_new_float (0.0));
129 if (VALUE_IS_NUMBER (val))
130 return gnm_expr_new_constant (value_dup (val));
131 return NULL;
132 }
133
134 static GnmExprOp
paste_op_to_expr_op(int paste_flags)135 paste_op_to_expr_op (int paste_flags)
136 {
137 g_return_val_if_fail (paste_flags & PASTE_OPER_MASK, 0);
138
139 if (paste_flags & PASTE_OPER_ADD)
140 return GNM_EXPR_OP_ADD;
141 else if (paste_flags & PASTE_OPER_SUB)
142 return GNM_EXPR_OP_SUB;
143 else if (paste_flags & PASTE_OPER_MULT)
144 return GNM_EXPR_OP_MULT;
145 else if (paste_flags & PASTE_OPER_DIV)
146 return GNM_EXPR_OP_DIV;
147
148 return 0;
149 }
150
151 static void
paste_cell_with_operation(Sheet * dst_sheet,int target_col,int target_row,GnmExprRelocateInfo const * rinfo,GnmCellCopy const * src,int paste_flags)152 paste_cell_with_operation (Sheet *dst_sheet,
153 int target_col, int target_row,
154 GnmExprRelocateInfo const *rinfo,
155 GnmCellCopy const *src,
156 int paste_flags)
157 {
158 GnmCell *dst;
159 GnmExprOp op;
160
161 if (src->texpr == NULL &&
162 !VALUE_IS_EMPTY (src->val) &&
163 !VALUE_IS_NUMBER (src->val))
164 return;
165
166 dst = sheet_cell_fetch (dst_sheet, target_col, target_row);
167 if (!cell_has_expr_or_number_or_blank (dst))
168 return;
169
170 op = paste_op_to_expr_op (paste_flags);
171 /* FIXME : This does not handle arrays, linked cells, ranges, etc. */
172 if ((paste_flags & PASTE_CONTENTS) &&
173 (NULL != src->texpr || gnm_cell_has_expr (dst))) {
174 GnmExpr const *old_expr = contents_as_expr (dst->base.texpr, dst->value);
175 GnmExpr const *copied_expr = contents_as_expr (src->texpr, src->val);
176 GnmExprTop const *res = gnm_expr_top_new (gnm_expr_new_binary (old_expr, op, copied_expr));
177 GnmExprTop const *relo = gnm_expr_top_relocate (res, rinfo, FALSE);
178 if (relo) {
179 gnm_cell_set_expr (dst, relo);
180 gnm_expr_top_unref (relo);
181 } else
182 gnm_cell_set_expr (dst, res);
183 gnm_expr_top_unref (res);
184 } else {
185 GnmValue *value;
186 GnmEvalPos pos;
187 GnmExpr const *expr = gnm_expr_new_binary (
188 gnm_expr_new_constant (value_dup (dst->value)),
189 op,
190 gnm_expr_new_constant (value_dup (src->val)));
191 GnmExprTop const *texpr = gnm_expr_top_new (expr);
192
193 eval_pos_init_cell (&pos, dst);
194 pos.dep = NULL; /* no dynamic deps */
195 value = gnm_expr_top_eval (texpr, &pos,
196 GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
197 gnm_expr_top_unref (texpr);
198 gnm_cell_set_value (dst, value);
199 }
200 }
201
202 /* NOTE : Make sure to set up any merged regions in the target range BEFORE
203 * this is called.
204 */
205 static void
paste_link(GnmPasteTarget const * pt,int top,int left,GnmCellRegion const * cr)206 paste_link (GnmPasteTarget const *pt, int top, int left,
207 GnmCellRegion const *cr)
208 {
209 GnmCellPos pos;
210 GnmCellRef source_cell_ref;
211 int x, y;
212
213 /* Not possible to link to arbitrary (non gnumeric) sources yet. */
214 /* TODO : eventually support interprocess gnumeric links */
215 if (cr->origin_sheet == NULL)
216 return;
217
218 /* TODO : support relative links ? */
219 source_cell_ref.col_relative = 0;
220 source_cell_ref.row_relative = 0;
221 source_cell_ref.sheet = (cr->origin_sheet != pt->sheet)
222 ? cr->origin_sheet : NULL;
223 pos.col = left;
224 for (x = 0 ; x < cr->cols ; x++, pos.col++) {
225 source_cell_ref.col = cr->base.col + x;
226 pos.row = top;
227 for (y = 0 ; y < cr->rows ; y++, pos.row++) {
228 GnmExprTop const *texpr;
229 GnmCell *cell =
230 sheet_cell_fetch (pt->sheet, pos.col, pos.row);
231
232 /* This could easily be made smarter */
233 if (!gnm_cell_is_merged (cell) &&
234 gnm_sheet_merge_contains_pos (pt->sheet, &pos))
235 continue;
236 source_cell_ref.row = cr->base.row + y;
237 texpr = gnm_expr_top_new (gnm_expr_new_cellref (&source_cell_ref));
238 gnm_cell_set_expr (cell, texpr);
239 gnm_expr_top_unref (texpr);
240 }
241 }
242 }
243
244 struct paste_cell_data {
245 GnmPasteTarget const *pt;
246 GnmCellRegion const *cr;
247 GnmCellPos top_left;
248 GnmExprRelocateInfo rinfo;
249 gboolean translate_dates;
250 };
251
252 /**
253 * paste_cell:
254 * @target_col: Column to put the cell into
255 * @target_row: Row to put the cell into.
256 * @src: A #GnmCellCopy with the content to paste
257 * @paste_flags: Bit mask that describes the paste options.
258 *
259 * Pastes a cell in the spreadsheet.
260 */
261 static void
paste_cell(int target_col,int target_row,GnmCellCopy const * src,const struct paste_cell_data * dat)262 paste_cell (int target_col, int target_row,
263 GnmCellCopy const *src,
264 const struct paste_cell_data *dat)
265 {
266 Sheet *dst_sheet = dat->pt->sheet;
267 int paste_flags = dat->pt->paste_flags;
268
269 if (paste_flags & PASTE_OPER_MASK)
270 paste_cell_with_operation (dst_sheet, target_col, target_row,
271 &dat->rinfo, src, paste_flags);
272 else {
273 GnmCell *dst = sheet_cell_fetch (dst_sheet, target_col, target_row);
274 if (NULL != src->texpr && (paste_flags & PASTE_CONTENTS)) {
275 GnmExprTop const *relo = gnm_expr_top_relocate (
276 src->texpr, &dat->rinfo, FALSE);
277 if (paste_flags & PASTE_TRANSPOSE) {
278 GnmExprTop const *trelo =
279 gnm_expr_top_transpose (relo ? relo : src->texpr);
280 if (trelo) {
281 if (relo)
282 gnm_expr_top_unref (relo);
283 relo = trelo;
284 }
285 } else if (!relo && gnm_expr_top_is_array_corner (src->texpr)) {
286 /* We must not share array expressions. */
287 relo = gnm_expr_top_new (gnm_expr_copy (src->texpr->expr));
288 }
289 gnm_cell_set_expr_and_value (dst, relo ? relo : src->texpr,
290 value_dup (src->val), TRUE);
291 if (NULL != relo)
292 gnm_expr_top_unref (relo);
293 } else if (src->val) {
294 GnmValue *newval = NULL;
295 GnmValue const *oldval = src->val;
296
297 if (dat->translate_dates && oldval && VALUE_IS_FLOAT (oldval)) {
298 GOFormat const *fmt = VALUE_FMT (oldval)
299 ? VALUE_FMT (oldval)
300 : gnm_cell_get_format (dst);
301 if (go_format_is_date (fmt) > 0) {
302 gnm_float fnew = go_date_conv_translate
303 (value_get_as_float (oldval),
304 dat->cr->date_conv,
305 sheet_date_conv (dst_sheet));
306 newval = value_new_float (fnew);
307 value_set_fmt (newval, VALUE_FMT (oldval));
308 }
309 }
310
311 if (!newval)
312 newval = value_dup (src->val);
313 gnm_cell_set_value (dst, newval);
314 }
315 }
316 }
317
318 static void
paste_object(GnmPasteTarget const * pt,SheetObject const * src,int left,int top)319 paste_object (GnmPasteTarget const *pt, SheetObject const *src, int left, int top)
320 {
321 SheetObject *dst;
322 SheetObjectAnchor tmp;
323
324 tmp = *sheet_object_get_anchor (src);
325 if (G_OBJECT_TYPE (src) == GNM_CELL_COMMENT_TYPE) {
326 if ((pt->paste_flags & PASTE_COMMENTS) &&
327 (pt->paste_flags & PASTE_IGNORE_COMMENTS_AT_ORIGIN &&
328 tmp.cell_bound.start.col == 0 &&
329 tmp.cell_bound.start.row == 0))
330 return;
331 } else if (!(pt->paste_flags & PASTE_OBJECTS))
332 return;
333
334 if (NULL == (dst = sheet_object_dup (src)))
335 return;
336
337 if (pt->paste_flags & PASTE_TRANSPOSE) {
338 GnmCellPos origin;
339 origin.col = 0;
340 origin.row = 0;
341 range_transpose (&tmp.cell_bound, pt->sheet, &origin);
342 }
343 range_translate (&tmp.cell_bound, pt->sheet, left, top);
344 sheet_object_set_anchor (dst, &tmp);
345 sheet_object_set_sheet (dst, pt->sheet);
346 g_object_unref (dst);
347 }
348
349 static void
cb_paste_cell(GnmCellCopy const * src,gconstpointer ignore,struct paste_cell_data * dat)350 cb_paste_cell (GnmCellCopy const *src, gconstpointer ignore,
351 struct paste_cell_data *dat)
352 {
353 int target_col = dat->top_left.col;
354 int target_row = dat->top_left.row;
355
356 if (dat->pt->paste_flags & PASTE_TRANSPOSE) {
357 target_col += src->offset.row;
358 target_row += src->offset.col;
359 } else if (dat->pt->paste_flags & PASTE_FLIP_H) {
360 target_col += dat->cr->cols - src->offset.col - 1;
361 target_row += src->offset.row;
362 } else if (dat->pt->paste_flags & PASTE_FLIP_V) {
363 target_col += src->offset.col;
364 target_row += dat->cr->rows - src->offset.row - 1;
365 } else {
366 target_col += src->offset.col;
367 target_row += src->offset.row;
368 }
369
370 dat->rinfo.pos.sheet = dat->pt->sheet;
371 if (dat->pt->paste_flags & PASTE_EXPR_LOCAL_RELOCATE) {
372 dat->rinfo.pos.eval.col = dat->cr->base.col + src->offset.col;
373 dat->rinfo.pos.eval.row = dat->cr->base.row + src->offset.row;
374 } else {
375 dat->rinfo.pos.eval.col = target_col;
376 dat->rinfo.pos.eval.row = target_row;
377 }
378
379 paste_cell (target_col, target_row, src, dat);
380 }
381
382 static gboolean
range_flip_h(GnmRange * range,Sheet const * sheet,int const * data)383 range_flip_h (GnmRange *range, Sheet const *sheet, int const *data)
384 {
385 int t;
386
387 g_return_val_if_fail (range != NULL, TRUE);
388
389 t = *data - range->end.col;
390 range->end.col = *data - range->start.col;
391 range->start.col = t;
392
393 return FALSE;
394 }
395 static gboolean
range_flip_v(GnmRange * range,Sheet const * sheet,int const * data)396 range_flip_v (GnmRange *range, Sheet const *sheet, int const *data)
397 {
398 int t;
399
400 g_return_val_if_fail (range != NULL, TRUE);
401
402 t = *data - range->end.row;
403 range->end.row = *data - range->start.row;
404 range->start.row = t;
405
406 return FALSE;
407 }
408
409 /**
410 * clipboard_paste_region:
411 * @cr: The GnmCellRegion to paste.
412 * @pt: Where to paste the values.
413 * @cc: (nullable): The context for error handling.
414 *
415 * Pastes the supplied GnmCellRegion (@cr) into the supplied
416 * GnmPasteTarget (@pt). This operation is not undoable. It does not auto grow
417 * the destination if the target is a singleton. This is a simple interface to
418 * paste a region.
419 *
420 * Returns: %TRUE if there was a problem.
421 **/
422 gboolean
clipboard_paste_region(GnmCellRegion const * cr,GnmPasteTarget const * pt,GOCmdContext * cc)423 clipboard_paste_region (GnmCellRegion const *cr,
424 GnmPasteTarget const *pt,
425 GOCmdContext *cc)
426 {
427 int repeat_horizontal, repeat_vertical, clearFlags;
428 int dst_cols, dst_rows, src_cols, src_rows;
429 int i, j;
430 GSList *ptr;
431 GnmRange const *r;
432 gboolean has_contents, adjust_merges = TRUE;
433 struct paste_cell_data dat;
434 GnmRange const *merge_src;
435 gboolean no_flipping, do_col_widths, do_row_heights;
436
437 g_return_val_if_fail (pt != NULL, TRUE);
438 g_return_val_if_fail (cr != NULL, TRUE);
439
440 /* we do not need any of this fancy stuff when pasting a simple object */
441 if (cr->cell_content == NULL &&
442 cr->styles == NULL &&
443 cr->merged == NULL &&
444 cr->objects != NULL) {
445 if (pt->paste_flags & (PASTE_COMMENTS | PASTE_OBJECTS))
446 for (ptr = cr->objects; ptr; ptr = ptr->next)
447 paste_object (pt, ptr->data,
448 pt->range.start.col, pt->range.start.row);
449 return FALSE;
450 }
451
452 r = &pt->range;
453 dst_cols = range_width (r);
454 dst_rows = range_height (r);
455 src_cols = cr->cols;
456 src_rows = cr->rows;
457
458 /* If the source is a single cell or a single merge */
459 /* Treat a target of a single merge specially, don't split the merge */
460 if ((src_cols == 1 && src_rows == 1) ||
461 (g_slist_length (cr->merged) == 1 &&
462 (NULL != (merge_src = cr->merged->data)) &&
463 range_height (merge_src) == cr->rows &&
464 range_width (merge_src) == cr->cols)) {
465 GnmRange const *merge = gnm_sheet_merge_is_corner (pt->sheet, &r->start);
466 if (merge != NULL && range_equal (r, merge)) {
467 dst_cols = dst_rows = 1;
468 adjust_merges = FALSE;
469 src_cols = 1;
470 src_rows = 1;
471 }
472 /* Apparently links do not supercede merges */
473 } else if (pt->paste_flags & PASTE_LINK)
474 adjust_merges = FALSE;
475
476 has_contents = pt->paste_flags & (PASTE_CONTENTS|PASTE_AS_VALUES|PASTE_LINK);
477
478 if (pt->paste_flags & PASTE_TRANSPOSE) {
479 int tmp = src_cols;
480 src_cols = src_rows;
481 src_rows = tmp;
482 }
483
484 if (cr->not_as_contents && (pt->paste_flags & PASTE_CONTENTS)) {
485 if (cc)
486 go_cmd_context_error_invalid
487 (cc,
488 _("Unable to paste"),
489 _("Contents can only be pasted by value or by link."));
490 return TRUE;
491 }
492
493 /* calculate the tiling */
494 repeat_horizontal = dst_cols/src_cols;
495 if (repeat_horizontal * src_cols != dst_cols) {
496 char *msg = g_strdup_printf (
497 _("destination does not have an even multiple of source columns (%d vs %d)\n\n"
498 "Try selecting a single cell or an area of the same shape and size."),
499 dst_cols, src_cols);
500 if (cc)
501 go_cmd_context_error_invalid (cc, _("Unable to paste"), msg);
502 g_free (msg);
503 return TRUE;
504 }
505
506 repeat_vertical = dst_rows/src_rows;
507 if (repeat_vertical * src_rows != dst_rows) {
508 char *msg = g_strdup_printf (
509 _("destination does not have an even multiple of source rows (%d vs %d)\n\n"
510 "Try selecting a single cell or an area of the same shape and size."),
511 dst_rows, src_rows);
512 if (cc)
513 go_cmd_context_error_invalid (cc, _("Unable to paste"), msg);
514 g_free (msg);
515 return TRUE;
516 }
517
518 if ((pt->range.start.col + dst_cols) > gnm_sheet_get_max_cols (pt->sheet) ||
519 (pt->range.start.row + dst_rows) > gnm_sheet_get_max_rows (pt->sheet)) {
520 if (cc)
521 go_cmd_context_error_invalid
522 (cc,
523 _("Unable to paste"),
524 _("result passes the sheet boundary"));
525 return TRUE;
526 }
527
528 clearFlags = 0;
529 /* clear the region where we will paste */
530 if (has_contents)
531 clearFlags = CLEAR_VALUES | CLEAR_NORESPAN;
532
533 if (pt->paste_flags & PASTE_COMMENTS)
534 clearFlags |= CLEAR_COMMENTS;
535
536 /* No need to clear the formats. We will paste over top of these. */
537 /* if (pt->paste_flags & PASTE_FORMATS) clearFlags |= CLEAR_FORMATS; */
538
539 if (pt->paste_flags & (PASTE_OPER_MASK | PASTE_SKIP_BLANKS))
540 clearFlags = 0;
541
542 /* remove merged regions even for operations, or blanks */
543 if (has_contents && adjust_merges)
544 clearFlags |= CLEAR_MERGES;
545
546 if (clearFlags != 0) {
547 int const dst_col = pt->range.start.col;
548 int const dst_row = pt->range.start.row;
549 sheet_clear_region (pt->sheet,
550 dst_col, dst_row,
551 dst_col + dst_cols - 1,
552 dst_row + dst_rows - 1,
553 clearFlags, cc);
554 }
555
556 dat.translate_dates = cr->date_conv &&
557 !go_date_conv_equal (cr->date_conv, sheet_date_conv (pt->sheet));
558
559 for (i = 0; i < repeat_horizontal ; i++)
560 for (j = 0; j < repeat_vertical ; j++) {
561 int const left = i * src_cols + pt->range.start.col;
562 int const top = j * src_rows + pt->range.start.row;
563
564 dat.top_left.col = left;
565 dat.top_left.row = top;
566 dat.rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE;
567 dat.rinfo.origin_sheet = dat.rinfo.target_sheet = pt->sheet;
568 if (pt->paste_flags & PASTE_EXPR_LOCAL_RELOCATE) {
569 dat.rinfo.origin.start = cr->base;
570 dat.rinfo.origin.end.col = cr->base.col + cr->cols - 1;
571 dat.rinfo.origin.end.row = cr->base.row + cr->rows - 1;
572 dat.rinfo.col_offset = left - cr->base.col;
573 dat.rinfo.row_offset = top - cr->base.row;
574 } else {
575 dat.rinfo.origin = pt->range;
576 dat.rinfo.col_offset = 0;
577 dat.rinfo.row_offset = 0;
578 }
579
580 /* Move the styles on here so we get correct formats before recalc */
581 if (pt->paste_flags & PASTE_FORMATS) {
582 if (pt->paste_flags & PASTE_TRANSPOSE)
583 sheet_style_set_list (pt->sheet, &dat.top_left,
584 cr->styles,
585 (sheet_style_set_list_cb_t)
586 range_transpose,
587 &dat.top_left);
588 else if (pt->paste_flags & PASTE_FLIP_H) {
589 int data = 2 * left + src_cols - 1;
590 sheet_style_set_list (pt->sheet, &dat.top_left,
591 cr->styles,
592 (sheet_style_set_list_cb_t)
593 range_flip_h, &data);
594 } else if (pt->paste_flags & PASTE_FLIP_V) {
595 int data = 2 * top + src_rows - 1;
596 sheet_style_set_list (pt->sheet, &dat.top_left,
597 cr->styles,
598 (sheet_style_set_list_cb_t)
599 range_flip_v, &data);
600 } else
601 sheet_style_set_list (pt->sheet, &dat.top_left,
602 cr->styles, NULL, NULL);
603 }
604 if (has_contents && !(pt->paste_flags & PASTE_DONT_MERGE)) {
605 for (ptr = cr->merged; ptr != NULL ; ptr = ptr->next) {
606 GnmRange tmp = *((GnmRange const *)ptr->data);
607 if (pt->paste_flags & PASTE_TRANSPOSE) {
608 int x;
609 x = tmp.start.col; tmp.start.col = tmp.start.row; tmp.start.row = x;
610 x = tmp.end.col; tmp.end.col = tmp.end.row; tmp.end.row = x;
611 }
612 if (!range_translate (&tmp, pt->sheet, left, top))
613 gnm_sheet_merge_add (pt->sheet, &tmp, TRUE, cc);
614 }
615 }
616
617 if (has_contents && (pt->paste_flags & PASTE_LINK)) {
618 paste_link (pt, top, left, cr);
619 continue;
620 }
621
622 if (has_contents && NULL != cr->cell_content) {
623 dat.pt = pt;
624 dat.cr = cr;
625 g_hash_table_foreach (cr->cell_content,
626 (GHFunc)cb_paste_cell, &dat);
627 }
628
629 if (pt->paste_flags & (PASTE_COMMENTS | PASTE_OBJECTS))
630 for (ptr = cr->objects; ptr; ptr = ptr->next)
631 paste_object (pt, ptr->data, left, top);
632 }
633
634 no_flipping = (pt->paste_flags & (PASTE_FLIP_H | PASTE_FLIP_V | PASTE_TRANSPOSE)) == 0;
635 do_col_widths =
636 no_flipping &&
637 ((pt->paste_flags & PASTE_COLUMN_WIDTHS) ||
638 ((pt->paste_flags & PASTE_COLUMN_WIDTHS_AUTO) &&
639 cr->origin_sheet &&
640 src_rows == gnm_sheet_get_max_rows (cr->origin_sheet)));
641 if (do_col_widths) {
642 int i;
643 for (i = 0; i < repeat_horizontal; i++) {
644 int first = pt->range.start.col + i * src_cols;
645 colrow_set_states (pt->sheet, TRUE, first, cr->col_state);
646 }
647 }
648
649 do_row_heights =
650 no_flipping &&
651 ((pt->paste_flags & PASTE_ROW_HEIGHTS) ||
652 ((pt->paste_flags & PASTE_ROW_HEIGHTS_AUTO) &&
653 cr->origin_sheet &&
654 src_cols == gnm_sheet_get_max_cols (cr->origin_sheet)));
655 if (do_row_heights) {
656 int i;
657 for (i = 0; i < repeat_vertical; i++) {
658 int first = pt->range.start.row + i * src_rows;
659 colrow_set_states (pt->sheet, FALSE, first, cr->row_state);
660 }
661 }
662
663 if (!(pt->paste_flags & PASTE_NO_RECALC)) {
664 if (has_contents) {
665 sheet_region_queue_recalc (pt->sheet, r);
666 sheet_flag_status_update_range (pt->sheet, r);
667 } else
668 sheet_flag_style_update_range (pt->sheet, r);
669
670 sheet_range_calc_spans (pt->sheet, r,
671 (pt->paste_flags & PASTE_FORMATS) ? GNM_SPANCALC_RE_RENDER : GNM_SPANCALC_RENDER);
672 sheet_redraw_all (pt->sheet, FALSE);
673 }
674
675 return FALSE;
676 }
677
678 static GnmValue *
cb_clipboard_prepend_cell(GnmCellIter const * iter,GnmCellRegion * cr)679 cb_clipboard_prepend_cell (GnmCellIter const *iter, GnmCellRegion *cr)
680 {
681 GnmRange a;
682 GnmCellCopy *copy = gnm_cell_copy_new (cr,
683 iter->pp.eval.col - cr->base.col,
684 iter->pp.eval.row - cr->base.row);
685 copy->val = value_dup (iter->cell->value);
686
687 if (gnm_cell_has_expr (iter->cell)) {
688 gnm_expr_top_ref (copy->texpr = iter->cell->base.texpr);
689
690 /* Check for array division */
691 if (!cr->not_as_contents &&
692 gnm_cell_array_bound (iter->cell, &a) &&
693 (a.start.col < cr->base.col ||
694 a.start.row < cr->base.row ||
695 a.end.col >= (cr->base.col + cr->cols) ||
696 a.end.row >= (cr->base.row + cr->rows)))
697 cr->not_as_contents = TRUE;
698 } else
699 copy->texpr = NULL;
700
701 return NULL;
702 }
703
704 static void
cb_dup_objects(SheetObject const * src,GnmCellRegion * cr)705 cb_dup_objects (SheetObject const *src, GnmCellRegion *cr)
706 {
707 SheetObject *dst = sheet_object_dup (src);
708 if (dst != NULL) {
709 SheetObjectAnchor tmp = *sheet_object_get_anchor (src);
710 range_translate (&tmp.cell_bound, sheet_object_get_sheet (src),
711 - cr->base.col, - cr->base.row);
712 sheet_object_set_anchor (dst, &tmp);
713 cr->objects = g_slist_prepend (cr->objects, dst);
714 }
715 }
716
717 /**
718 * clipboard_copy_range:
719 *
720 * Entry point to the clipboard copy code
721 */
722 GnmCellRegion *
clipboard_copy_range(Sheet * sheet,GnmRange const * r)723 clipboard_copy_range (Sheet *sheet, GnmRange const *r)
724 {
725 GnmCellRegion *cr;
726 GSList *merged, *ptr;
727 GSList *objects;
728
729 g_return_val_if_fail (IS_SHEET (sheet), NULL);
730 g_return_val_if_fail (range_is_sane (r), NULL);
731
732 cr = gnm_cell_region_new (sheet);
733 cr->base = r->start;
734 cr->cols = range_width (r);
735 cr->rows = range_height (r);
736 cr->col_state = colrow_get_states (sheet,
737 TRUE, r->start.col, r->end.col);
738 cr->row_state = colrow_get_states (sheet,
739 FALSE, r->start.row, r->end.row);
740
741 sheet_foreach_cell_in_range ( sheet, CELL_ITER_IGNORE_NONEXISTENT, r,
742 (CellIterFunc) cb_clipboard_prepend_cell,
743 cr);
744 objects = sheet_objects_get (sheet, r, G_TYPE_NONE);
745 g_slist_foreach (objects, (GFunc)cb_dup_objects, cr);
746 g_slist_free (objects);
747
748 cr->styles = sheet_style_get_range (sheet, r);
749
750 merged = gnm_sheet_merge_get_overlap (sheet, r);
751 for (ptr = merged ; ptr != NULL ; ptr = ptr->next) {
752 GnmRange *tmp = gnm_range_dup (ptr->data);
753 range_translate (tmp, sheet, -r->start.col, -r->start.row);
754 cr->merged = g_slist_prepend (cr->merged, tmp);
755 }
756 g_slist_free (merged);
757
758 return cr;
759 }
760
761 static void
cb_clipboard_copy_range_undo(GnmCellRegion * cr,GnmSheetRange * sr,GOCmdContext * cc)762 cb_clipboard_copy_range_undo (GnmCellRegion *cr, GnmSheetRange *sr,
763 GOCmdContext *cc)
764 {
765 GnmPasteTarget pt;
766
767 clipboard_paste_region
768 (cr,
769 paste_target_init (&pt,
770 sr->sheet,
771 &sr->range,
772 PASTE_CONTENTS | PASTE_FORMATS |
773 PASTE_OBJECTS | PASTE_COMMENTS |
774 PASTE_COLUMN_WIDTHS | PASTE_ROW_HEIGHTS),
775 cc);
776 }
777
778 /**
779 * clipboard_copy_range_undo:
780 * @sheet: #Sheet
781 * @r: #GnmRange
782 *
783 * Returns: (transfer full): A #GOUndo object that will restore the contents
784 * of the given range.
785 **/
786 GOUndo *
clipboard_copy_range_undo(Sheet * sheet,GnmRange const * r)787 clipboard_copy_range_undo (Sheet *sheet, GnmRange const *r)
788 {
789 GnmCellRegion *cr = clipboard_copy_range (sheet, r);
790 g_return_val_if_fail (cr != NULL, NULL);
791 return go_undo_binary_new (cr, gnm_sheet_range_new (sheet, r),
792 (GOUndoBinaryFunc)cb_clipboard_copy_range_undo,
793 (GFreeFunc)cellregion_unref,
794 (GFreeFunc)g_free);
795 }
796
797 /**
798 * clipboard_copy_ranges_undo:
799 * @sheet: #Sheet
800 * @ranges: (element-type GnmRange) (transfer none): list of ranges
801 *
802 * Returns: (transfer full): A #GOUndo object that will restore the contents
803 * of the given range.
804 **/
805 GOUndo *
clipboard_copy_ranges_undo(Sheet * sheet,GSList * ranges)806 clipboard_copy_ranges_undo (Sheet *sheet, GSList *ranges)
807 {
808 GSList *l;
809 GOUndo *undo = NULL;
810
811 for (l = ranges; l != NULL; l = l->next) {
812 GnmRange *r = l->data;
813 GOUndo *undo1 = clipboard_copy_range_undo (sheet, r);
814 undo = go_undo_combine (undo, undo1);
815 }
816
817 return undo;
818 }
819
820
821 /**
822 * clipboard_copy_obj:
823 * @sheet: #Sheet
824 * @objects: (element-type SheetObject): #GSList
825 *
826 * Returns a cell region with copies of objects in list. Caller is responsible
827 * for cellregion_unref-ing the result.
828 **/
829 GnmCellRegion *
clipboard_copy_obj(Sheet * sheet,GSList * objects)830 clipboard_copy_obj (Sheet *sheet, GSList *objects)
831 {
832 SheetObjectAnchor tmp_anchor;
833 SheetObjectAnchor const *anchor;
834 GnmCellRegion *cr;
835 GnmRange *r;
836 GSList *ptr;
837 SheetObject *so;
838 double coords [4];
839 guint w, h;
840
841 g_return_val_if_fail (IS_SHEET (sheet), NULL);
842 g_return_val_if_fail (objects != NULL, NULL);
843
844 cr = gnm_cell_region_new (sheet);
845 for (ptr = objects ; ptr != NULL ; ptr = ptr->next)
846 if (NULL != (so = sheet_object_dup (ptr->data))) {
847 anchor = sheet_object_get_anchor (so);
848
849 #warning FIXME : This is only used in gnm_sog_write_image
850 /* NOTE #1 : It seems necessary to handle pasting an object that has been removed from
851 * the sheet after being added to the clipboard. it seems like we would need
852 * this sort of information for anything that implements SheetObjectImageableIface
853 **/
854 sheet_object_anchor_to_pts (anchor, sheet, coords);
855 w = fabs (coords[2] - coords[0]) + 1.5;
856 h = fabs (coords[3] - coords[1]) + 1.5;
857 g_object_set_data (G_OBJECT (so), "pt-width-at-copy",
858 GUINT_TO_POINTER (w));
859 g_object_set_data (G_OBJECT (so), "pt-height-at-copy",
860 GUINT_TO_POINTER (h));
861
862 tmp_anchor = *anchor;
863 r = &tmp_anchor.cell_bound;
864 range_translate (r, sheet,
865 -MIN (r->start.col, r->end.col),
866 -MIN (r->start.row, r->end.row));
867 sheet_object_set_anchor (so, &tmp_anchor);
868
869 cr->objects = g_slist_prepend (cr->objects, so);
870 }
871
872 return cr;
873 }
874
875 GnmPasteTarget*
paste_target_init(GnmPasteTarget * pt,Sheet * sheet,GnmRange const * r,GnmPasteFlags flags)876 paste_target_init (GnmPasteTarget *pt, Sheet *sheet,
877 GnmRange const *r, GnmPasteFlags flags)
878 {
879 pt->sheet = sheet; // No ref
880 pt->range = *r;
881 pt->paste_flags = flags;
882 return pt;
883 }
884
885 /**
886 * gnm_cell_region_new:
887 * @origin_sheet: optionally NULL.
888 *
889 * A convenience routine to create CellRegions and init the flags nicely.
890 */
891 GnmCellRegion *
gnm_cell_region_new(Sheet * origin_sheet)892 gnm_cell_region_new (Sheet *origin_sheet)
893 {
894 GnmCellRegion *cr = g_new0 (GnmCellRegion, 1);
895 cr->origin_sheet = origin_sheet;
896 cr->date_conv = origin_sheet && origin_sheet->workbook
897 ? sheet_date_conv (origin_sheet)
898 : NULL;
899 cr->cols = cr->rows = -1;
900 cr->not_as_contents = FALSE;
901 cr->cell_content = NULL;
902 cr->col_state = NULL;
903 cr->row_state = NULL;
904 cr->styles = NULL;
905 cr->merged = NULL;
906 cr->objects = NULL;
907 cr->ref_count = 1;
908
909 return cr;
910 }
911
912 GnmCellRegion *
cellregion_ref(GnmCellRegion * cr)913 cellregion_ref (GnmCellRegion *cr)
914 {
915 g_return_val_if_fail (cr != NULL, NULL);
916 cr->ref_count++;
917 return cr;
918 }
919
920 void
cellregion_unref(GnmCellRegion * cr)921 cellregion_unref (GnmCellRegion *cr)
922 {
923 g_return_if_fail (cr != NULL);
924
925 if (cr->ref_count > 1) {
926 cr->ref_count--;
927 return;
928 }
929
930 if (NULL != cr->cell_content) {
931 g_hash_table_destroy (cr->cell_content);
932 cr->cell_content = NULL;
933 }
934
935 if (NULL != cr->col_state)
936 cr->col_state = colrow_state_list_destroy (cr->col_state);
937 if (NULL != cr->row_state)
938 cr->row_state = colrow_state_list_destroy (cr->row_state);
939 if (cr->styles != NULL) {
940 style_list_free (cr->styles);
941 cr->styles = NULL;
942 }
943 if (cr->merged != NULL) {
944 GSList *ptr;
945 for (ptr = cr->merged; ptr != NULL ; ptr = ptr->next)
946 g_free (ptr->data);
947 g_slist_free (cr->merged);
948 cr->merged = NULL;
949 }
950 if (cr->objects != NULL) {
951 GSList *ptr;
952 for (ptr = cr->objects; ptr != NULL ; ptr = ptr->next)
953 g_object_unref (ptr->data);
954 g_slist_free (cr->objects);
955 cr->objects = NULL;
956 }
957
958 g_free (cr);
959 }
960
961 GType
gnm_cell_region_get_type(void)962 gnm_cell_region_get_type (void)
963 {
964 static GType t = 0;
965
966 if (t == 0) {
967 t = g_boxed_type_register_static ("GnmCellRegion",
968 (GBoxedCopyFunc)cellregion_ref,
969 (GBoxedFreeFunc)cellregion_unref);
970 }
971 return t;
972 }
973
974 static GnmCellCopy *
cellregion_get_content(GnmCellRegion const * cr,int col,int row)975 cellregion_get_content (GnmCellRegion const *cr, int col, int row)
976 {
977 if (cr->cell_content) {
978 GnmCellPos pos;
979 pos.col = col;
980 pos.row = row;
981 return g_hash_table_lookup (cr->cell_content, &pos);
982 } else
983 return NULL;
984 }
985
986 static void
cb_invalidate_cellcopy(GnmCellCopy * cc,gconstpointer ignore,GnmExprRelocateInfo * rinfo)987 cb_invalidate_cellcopy (GnmCellCopy *cc, gconstpointer ignore,
988 GnmExprRelocateInfo *rinfo)
989 {
990 GnmExprTop const *texpr;
991 if (NULL != cc->texpr) {
992 texpr = gnm_expr_top_relocate (cc->texpr, rinfo, FALSE);
993 if (NULL != texpr) {
994 gnm_expr_top_unref (cc->texpr);
995 cc->texpr = texpr;
996 }
997 }
998 }
999
1000 /**
1001 * cellregion_invalidate_sheet:
1002 * @cr: #GnmCellRegion
1003 * @sheet: #Sheet
1004 *
1005 * Invalidate references from cell content, objects or style to @sheet.
1006 **/
1007 void
cellregion_invalidate_sheet(GnmCellRegion * cr,Sheet * sheet)1008 cellregion_invalidate_sheet (GnmCellRegion *cr,
1009 Sheet *sheet)
1010 {
1011 GSList *ptr;
1012 gboolean save_invalidated;
1013 GnmExprRelocateInfo rinfo;
1014 GnmStyleList *l;
1015
1016 g_return_if_fail (cr != NULL);
1017 g_return_if_fail (IS_SHEET (sheet));
1018
1019 save_invalidated = sheet->being_invalidated;
1020 sheet->being_invalidated = TRUE;
1021
1022 rinfo.reloc_type = GNM_EXPR_RELOCATE_INVALIDATE_SHEET;
1023 if (NULL != cr->cell_content)
1024 g_hash_table_foreach (cr->cell_content,
1025 (GHFunc)cb_invalidate_cellcopy, &rinfo);
1026 sheet->being_invalidated = save_invalidated;
1027
1028 // Remove conditional formats from styles. That's brutal, but
1029 // they reference the sheet. See #406.
1030 for (l = cr->styles; l; l = l->next) {
1031 GnmStyleRegion *sr = l->data;
1032 GnmRange const *r = &sr->range;
1033 GnmStyle const *style = sr->style;
1034 GnmStyleConditions *conds = gnm_style_is_element_set (style, MSTYLE_CONDITIONS)
1035 ? gnm_style_get_conditions (style)
1036 : NULL;
1037
1038 if (conds &&
1039 gnm_style_conditions_get_sheet (conds) == sheet) {
1040 GnmStyle *style2;
1041 GnmStyleRegion *sr2;
1042
1043 style2 = gnm_style_dup (style);
1044 gnm_style_set_conditions (style2, NULL);
1045 sr2 = gnm_style_region_new (r, style2);
1046 gnm_style_unref (style2);
1047
1048 gnm_style_region_free (sr);
1049 l->data = sr2;
1050 }
1051 }
1052
1053 for (ptr = cr->objects; ptr != NULL ; ptr = ptr->next)
1054 sheet_object_invalidate_sheet (ptr->data, sheet);
1055
1056 if (cr->origin_sheet == sheet)
1057 cr->origin_sheet = NULL;
1058 }
1059
1060 static void
cb_cellregion_extent(GnmCellCopy * cc,gconstpointer ignore,GnmRange * extent)1061 cb_cellregion_extent (GnmCellCopy *cc, gconstpointer ignore, GnmRange *extent)
1062 {
1063 if (extent->start.col >= 0) {
1064 if (extent->start.col > cc->offset.col)
1065 extent->start.col = cc->offset.col;
1066 else if (extent->end.col < cc->offset.col)
1067 extent->end.col = cc->offset.col;
1068
1069 if (extent->start.row > cc->offset.row)
1070 extent->start.row = cc->offset.row;
1071 else if (extent->end.row < cc->offset.row)
1072 extent->end.row = cc->offset.row;
1073 } else /* first cell */
1074 extent->start = extent->end = cc->offset;
1075 }
1076
1077 /**
1078 * cellregion_extent:
1079 * @cr: #GnmCellRegion
1080 * @extent: #GnmRange
1081 *
1082 * Find the min and max col/row with cell content
1083 **/
1084 static void
cellregion_extent(GnmCellRegion const * cr,GnmRange * extent)1085 cellregion_extent (GnmCellRegion const *cr, GnmRange *extent)
1086 {
1087 if (NULL != cr->cell_content) {
1088 range_init (extent, -1, -1, -1, -1);
1089 g_hash_table_foreach (cr->cell_content,
1090 (GHFunc)cb_cellregion_extent, extent);
1091 } else
1092 range_init (extent, 0, 0, 0, 0);
1093 }
1094
1095 GString *
cellregion_to_string(GnmCellRegion const * cr,gboolean only_visible,GODateConventions const * date_conv)1096 cellregion_to_string (GnmCellRegion const *cr,
1097 gboolean only_visible,
1098 GODateConventions const *date_conv)
1099 {
1100 GString *all, *line;
1101 GnmCellCopy const *cc;
1102 int col, row, next_col_check, next_row_check;
1103 GnmRange extent;
1104 ColRowStateList const *col_state = NULL, *row_state = NULL;
1105 ColRowRLEState const *rle;
1106 int ncells, i;
1107 GnmStyle const *style;
1108 GOFormat const *fmt;
1109
1110 g_return_val_if_fail (cr != NULL, NULL);
1111 g_return_val_if_fail (cr->rows >= 0, NULL);
1112 g_return_val_if_fail (cr->cols >= 0, NULL);
1113
1114 /* pre-allocate rough approximation of buffer */
1115 ncells = cr->cell_content ? g_hash_table_size (cr->cell_content) : 0;
1116 all = g_string_sized_new (20 * ncells + 1);
1117 line = g_string_new (NULL);
1118
1119 cellregion_extent (cr, &extent);
1120
1121 if (only_visible && NULL != (row_state = cr->row_state)) {
1122 next_row_check = i = 0;
1123 while ((i += ((ColRowRLEState *)(row_state->data))->length) <= extent.start.row) {
1124 if (NULL == (row_state = row_state->next)) {
1125 next_row_check = gnm_sheet_get_max_rows (cr->origin_sheet);
1126 break;
1127 }
1128 next_row_check = i;
1129 }
1130 } else
1131 next_row_check = gnm_sheet_get_max_rows (cr->origin_sheet);
1132
1133 for (row = extent.start.row; row <= extent.end.row;) {
1134 if (row >= next_row_check) {
1135 rle = row_state->data;
1136 row_state = row_state->next;
1137 next_row_check += rle->length;
1138 if (!rle->state.visible) {
1139 row = next_row_check;
1140 continue;
1141 }
1142 }
1143
1144 g_string_assign (line, "");
1145
1146 if (only_visible && NULL != (col_state = cr->col_state)) {
1147 next_col_check = i = 0;
1148 while ((i += ((ColRowRLEState *)(col_state->data))->length) <= extent.start.col) {
1149 if (NULL == (col_state = col_state->next)) {
1150 next_col_check = gnm_sheet_get_max_cols (cr->origin_sheet);
1151 break;
1152 }
1153 next_col_check = i;
1154 }
1155 } else
1156 next_col_check = gnm_sheet_get_max_cols (cr->origin_sheet);
1157
1158 for (col = extent.start.col; col <= extent.end.col;) {
1159 if (col == next_col_check) {
1160 rle = col_state->data;
1161 col_state = col_state->next;
1162 next_col_check += rle->length;
1163 if (!rle->state.visible) {
1164 col = next_col_check;
1165 continue;
1166 }
1167 }
1168
1169 cc = cellregion_get_content (cr, col, row);
1170 if (cc) {
1171 style = style_list_get_style (cr->styles, col, row);
1172 fmt = gnm_style_get_format (style);
1173
1174 if (go_format_is_general (fmt) &&
1175 VALUE_FMT (cc->val))
1176 fmt = VALUE_FMT (cc->val);
1177
1178 format_value_gstring (line, fmt, cc->val,
1179 -1, date_conv);
1180 }
1181 if (++col <= extent.end.col)
1182 g_string_append_c (line, '\t');
1183 }
1184 g_string_append_len (all, line->str, line->len);
1185 if (++row <= extent.end.row)
1186 g_string_append_c (all, '\n');
1187 }
1188
1189 g_string_free (line, TRUE);
1190 return all;
1191 }
1192
1193 int
cellregion_cmd_size(GnmCellRegion const * cr)1194 cellregion_cmd_size (GnmCellRegion const *cr)
1195 {
1196 int res = 1;
1197
1198 g_return_val_if_fail (cr != NULL, 1);
1199
1200 res += g_slist_length (cr->styles);
1201 if (NULL != cr->cell_content)
1202 res += g_hash_table_size (cr->cell_content);
1203 return res;
1204 }
1205
1206 static void
gnm_cell_copy_free(GnmCellCopy * cc)1207 gnm_cell_copy_free (GnmCellCopy *cc)
1208 {
1209 if (cc->texpr) {
1210 gnm_expr_top_unref (cc->texpr);
1211 cc->texpr = NULL;
1212 }
1213 value_release (cc->val);
1214 cc->val = NULL;
1215
1216 CHUNK_FREE (cell_copy_pool, cc);
1217 }
1218
1219 GnmCellCopy *
gnm_cell_copy_new(GnmCellRegion * cr,int col_offset,int row_offset)1220 gnm_cell_copy_new (GnmCellRegion *cr, int col_offset, int row_offset)
1221 {
1222 GnmCellCopy *res = CHUNK_ALLOC (GnmCellCopy, cell_copy_pool);
1223 ((GnmCellPos *)(&res->offset))->col = col_offset;
1224 ((GnmCellPos *)(&res->offset))->row = row_offset;
1225 res->texpr = NULL;
1226 res->val = NULL;
1227
1228 if (NULL == cr->cell_content)
1229 cr->cell_content = g_hash_table_new_full (
1230 (GHashFunc)&gnm_cellpos_hash,
1231 (GCompareFunc)&gnm_cellpos_equal,
1232 (GDestroyNotify) gnm_cell_copy_free,
1233 NULL);
1234
1235 g_hash_table_insert (cr->cell_content, res, res);
1236
1237 return res;
1238 }
1239
1240 /**
1241 * clipboard_init: (skip)
1242 */
1243 void
clipboard_init(void)1244 clipboard_init (void)
1245 {
1246 #if USE_CELL_COPY_POOLS
1247 cell_copy_pool =
1248 go_mem_chunk_new ("cell copy pool",
1249 sizeof (GnmCellCopy),
1250 4 * 1024 - 128);
1251 #endif
1252 }
1253
1254 #if USE_CELL_COPY_POOLS
1255 static void
cb_cell_copy_pool_leak(gpointer data,G_GNUC_UNUSED gpointer user)1256 cb_cell_copy_pool_leak (gpointer data, G_GNUC_UNUSED gpointer user)
1257 {
1258 GnmCellCopy const *cc = data;
1259 g_printerr ("Leaking cell copy at %p.\n", (void *)cc);
1260 }
1261 #endif
1262
1263 /**
1264 * clipboard_shutdown: (skip)
1265 */
1266 void
clipboard_shutdown(void)1267 clipboard_shutdown (void)
1268 {
1269 #if USE_CELL_COPY_POOLS
1270 go_mem_chunk_foreach_leak (cell_copy_pool, cb_cell_copy_pool_leak, NULL);
1271 go_mem_chunk_destroy (cell_copy_pool, FALSE);
1272 cell_copy_pool = NULL;
1273 #endif
1274 }
1275