clipboard.c 16.2 KB
Newer Older
Arturo Espinosa's avatar
Arturo Espinosa committed
1 2 3 4
/*
 * Clipboard.c: Implements the copy/paste operations
 *
 * Author:
5
 *  Miguel de Icaza (miguel@gnu.org)
Arturo Espinosa's avatar
Arturo Espinosa committed
6 7 8
 */
#include <config.h>
#include <gnome.h>
9 10
#include <locale.h>
#include <string.h>
Arturo Espinosa's avatar
Arturo Espinosa committed
11
#include "gnumeric.h"
12
#include "gnumeric-util.h"
Arturo Espinosa's avatar
Arturo Espinosa committed
13
#include "clipboard.h"
14
#include "eval.h"
15
#include "selection.h"
16
#include "application.h"
17 18
#include "render-ascii.h"

19
/*
20
 * Callback information.
21
 *
22
 * Passed through the workbook structure.
23
 */
24 25 26 27 28 29 30
typedef struct {
	Sheet      *dest_sheet;
	CellRegion *region;
	int         dest_col, dest_row;
	int         paste_flags;
} clipboard_paste_closure_t;

31 32 33 34 35 36 37 38
/**
 * paste_cell:
 * @dest_sheet:  The sheet where the pasting will be done
 * @new_cell:    The cell to paste.
 * @target_col:  Column to put the cell into
 * @target_row:  Row to put the cell into.
 * @paste_flags: Bit mask that describes the paste options.
 *
39 40 41
 * Pastes a cell in the spreadsheet
 */
static int
42 43
paste_cell (Sheet *dest_sheet, Cell *new_cell,
	    int target_col, int target_row,
44
	    int paste_flags)
45
{
46 47 48
	g_return_val_if_fail (target_col < SHEET_MAX_COLS, 0);
	g_return_val_if_fail (target_row < SHEET_MAX_ROWS, 0);
	
49 50 51 52 53 54 55 56 57 58
	sheet_cell_add (dest_sheet, new_cell, target_col, target_row);
	
	if (!(paste_flags & PASTE_FORMULAS)){
		if (new_cell->parsed_node){
			expr_tree_unref (new_cell->parsed_node);
			new_cell->parsed_node = NULL;
		}
	}
	
	if (new_cell->parsed_node){
59
		if (paste_flags & PASTE_FORMULAS){
60
			cell_relocate (new_cell);
61 62
			cell_content_changed (new_cell);
		}
63 64 65
		else 
			cell_make_value (new_cell);
	}
66

67 68
	if (new_cell->value)
		cell_render_value (new_cell);
69 70 71 72 73 74 75 76 77 78 79 80 81
	
	sheet_redraw_cell_region (dest_sheet,
				  target_col, target_row,
				  target_col, target_row);

	return new_cell->parsed_node != 0;
}

static int
paste_cell_flags (Sheet *dest_sheet, int target_col, int target_row,
		  CellCopy *c_copy, int paste_flags)
{
	if (!(paste_flags & (PASTE_FORMULAS | PASTE_VALUES))){
82 83 84 85 86 87 88 89 90 91
		Range r;

		r.start.col = target_col;
		r.start.row = target_row;
		r.end.col   = target_col;
		r.end.row   = target_row;
		if (c_copy->u.cell.mstyle) {
			mstyle_ref (c_copy->u.cell.mstyle);
			sheet_style_attach (dest_sheet, r, c_copy->u.cell.mstyle);
		}
92 93 94
	} else {
		Cell *new_cell;
		
95 96 97 98 99 100 101 102 103 104 105 106 107 108
		if (c_copy->type != CELL_COPY_TYPE_TEXT) {
			Range r;

			new_cell = cell_copy (c_copy->u.cell.cell);
			
			r.start.col = target_col;
			r.start.row = target_row;
			r.end.col   = target_col;
			r.end.row   = target_row;
			if (c_copy->u.cell.mstyle) {
				mstyle_ref (c_copy->u.cell.mstyle);
				sheet_style_attach (dest_sheet, r,
						    c_copy->u.cell.mstyle);
			}
109 110 111
			
			return paste_cell (
				dest_sheet, new_cell,
112
				target_col, target_row, paste_flags);
113 114 115 116 117 118 119 120 121 122
		} else {
			new_cell = sheet_cell_new (dest_sheet,
						   target_col, target_row);
			cell_set_text (new_cell, c_copy->u.text);
		}
	}

	return 0;
}

123
/**
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
 * do_clipboard_paste_cell_region:
 *
 * region:       a Cell Region that contains information to be pasted
 * dest_col:     initial column where cell pasting takes place
 * dest_row:     initial row where cell pasting takes place
 * paste_width:  boundary for pasting (in columns)
 * paste_height: boundar for pasting (in rows)
 * paste_flags:  controls what gets pasted (see clipboard.h for details
 */
static void
do_clipboard_paste_cell_region (CellRegion *region, Sheet *dest_sheet,
				int dest_col,       int dest_row,
				int paste_width,    int paste_height,
				int paste_flags)
{
	CellCopyList *l;
	GList *deps;
	int formulas = 0;
142
	int col, row, col_inc, row_inc;
143 144 145 146 147 148

	/* clear the region where we will paste */
	if (paste_flags & (PASTE_VALUES | PASTE_FORMULAS))
		sheet_clear_region (dest_sheet,
				    dest_col, dest_row,
				    dest_col + paste_width - 1,
149 150
				    dest_row + paste_height - 1,
				    NULL);
151 152

	/* If no operations are defined, we clear the area */
153
	if (!(paste_flags & PASTE_OPER_MASK))
154 155 156 157 158 159
		sheet_redraw_cell_region (dest_sheet,
					  dest_col, dest_row,
					  dest_col + paste_width - 1,
					  dest_row + paste_height - 1);
	
	/* Paste each element */
160
	if (paste_flags & PASTE_TRANSPOSE) {
161 162 163 164 165 166 167 168 169
		col_inc = region->rows;
		row_inc = region->cols;
	} else {
		col_inc = region->cols;
		row_inc = region->rows;
	}

	for (col = 0; col < paste_width; col += col_inc){
		for (row = 0; row < paste_height; row += row_inc){
170 171 172 173
			for (l = region->list; l; l = l->next){
				CellCopy *c_copy = l->data;
				int target_col, target_row;

174 175 176 177 178 179 180 181
				if (paste_flags & PASTE_TRANSPOSE){
					target_col = col + dest_col + c_copy->row_offset;
					target_row = row + dest_row + c_copy->col_offset;
				} else {
					target_col = col + dest_col + c_copy->col_offset;
					target_row = row + dest_row + c_copy->row_offset;
				}
				
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
				if (target_col > dest_col + paste_width - 1)
					continue;

				if (target_row > dest_row + paste_height - 1)
					continue;

				formulas |= paste_cell_flags (
					dest_sheet, target_col, target_row,
					c_copy, paste_flags);
			}
		}
	}
	
	deps = region_get_dependencies (
		dest_sheet,
		dest_col, dest_row,
		dest_col + paste_width - 1,
		dest_row + paste_height -1);

201
	if (deps) {
Morten Welinder's avatar
Morten Welinder committed
202
		cell_queue_recalc_list (deps, TRUE);
203 204 205 206 207 208 209 210
		formulas = 1;
	}

	/* Trigger a recompute if required */
	if (formulas)
		workbook_recalc (dest_sheet->workbook);
}

211 212 213 214 215 216
static GList *
new_node (GList *list, char *data, char *p, int col, int row)
{
	CellCopy *c_copy;
	char *text;

Miguel de Icaza's avatar
Miguel de Icaza committed
217 218 219 220
	/* Eliminate spaces */
	while (*data == ' ' && *data)
		data++;
	
221 222 223 224 225 226 227 228 229 230 231 232 233
	text = g_malloc (p-data+1);
	text = strncpy (text, data, p-data);
	text [p-data] = 0;
	
	c_copy = g_new (CellCopy, 1);
	c_copy->type = CELL_COPY_TYPE_TEXT;
	c_copy->col_offset = col;
	c_copy->row_offset = row;
	c_copy->u.text = text;
	
	return g_list_prepend (list, c_copy);
}

234 235 236
/**
 * x_selection_to_cell_region:
 * @data: points to an array of chars are received.
237
 * @len:  The length of the @data buffer as received. 
238
 *
239 240 241 242 243 244 245 246 247
 * Creates a CellRegion based on the X selection
 *
 * We use \t, ; and "," as cell separators
 * \n is a line separator
 */
static CellRegion *
x_selection_to_cell_region (char *data, int len)
{
	CellRegion *cr;
248
	int cols = 1, cur_col = 0;
249 250 251 252
	int rows = 0;
	GList *list = NULL;
	char *p = data;

253
	for (;len >= 0; len--, p++){
254
		if (*p == '\t' || *p == ';' || *p == ',' || *p == '\n'){
255 256
			if (p != data)
				list = new_node (list, data, p, cur_col, rows);
257
			
258
			if (*p == '\n'){
259
				rows++;
260 261
				cur_col = 0;
			} else {
262
				cur_col++;
263 264 265 266
				if (cur_col > cols)
					cols = cur_col;
			}

267
			data = p+1;
268 269 270
		}
	}

271
	/* Handle the remainings */
272
	if (p != data) {
273
		list = new_node (list, data, p, cur_col, rows);
274 275 276 277 278
		cur_col++;
		if (cur_col > cols)
			cols = cur_col;
	}
	
279 280 281
	/* Return the CellRegion */
	cr = g_new (CellRegion, 1);	
	cr->list = list;
282 283
	cr->cols = cols ? cols : 1;
	cr->rows = rows + 1;
284 285 286 287

	return cr;
}

Miguel de Icaza's avatar
Miguel de Icaza committed
288
/**
289
 * sheet_paste_selection:
290 291 292
 *
 */
static void
293
sheet_paste_selection (Sheet *sheet, CellRegion *content, SheetSelection *ss, clipboard_paste_closure_t *pc)
294 295 296
{
	int        paste_height, paste_width;
	int        end_col, end_row;
297

298
	/* Compute the bigger bounding box (selection u clipboard-region) */
299 300
	if (ss->user.end.col - ss->user.start.col + 1 > content->cols)
		paste_width = ss->user.end.col - ss->user.start.col + 1;
301 302 303
	else
		paste_width = content->cols;

304 305
	if (ss->user.end.row - ss->user.start.row + 1 > content->rows)
		paste_height = ss->user.end.row - ss->user.start.row + 1;
306 307 308
	else
		paste_height = content->rows;

309 310 311 312 313
	if (pc->dest_col + paste_width > SHEET_MAX_COLS)
		paste_width = SHEET_MAX_COLS - pc->dest_col;
	if (pc->dest_row + paste_height > SHEET_MAX_ROWS)
		paste_height = SHEET_MAX_ROWS - pc->dest_row;
	
314 315 316 317 318
	if (pc->paste_flags & PASTE_TRANSPOSE){
		int t;
		
		end_col = pc->dest_col + paste_height - 1;
		end_row = pc->dest_row + paste_width - 1;
319

320 321 322 323 324 325 326 327
		/* Swap the paste dimensions for transposing */
		t = paste_height;
		paste_height = paste_width;
		paste_width = t;
	} else {
		end_col = pc->dest_col + paste_width - 1;
		end_row = pc->dest_row + paste_height - 1;
	}
328

329 330
	/* Do the actual paste operation */
	do_clipboard_paste_cell_region (
331
		content,      sheet,
332 333 334
		pc->dest_col, pc->dest_row,
		paste_width,  paste_height,
		pc->paste_flags);
335

336 337 338 339 340 341 342 343 344
	{
		Range r;
		r.start.col = pc->dest_col;
		r.end.col   = pc->dest_col + paste_width;
		r.start.row = pc->dest_row;
		r.end.row   = pc->dest_row + paste_height;
		sheet_style_optimize (sheet, r);
	}

345
	sheet_cursor_set (pc->dest_sheet,
346
			  pc->dest_col, pc->dest_row,
347 348 349 350 351 352 353
			  pc->dest_col, pc->dest_row,
			  end_col,      end_row);

	sheet_selection_reset_only (pc->dest_sheet);
	sheet_selection_append (pc->dest_sheet, pc->dest_col, pc->dest_row);
	sheet_selection_extend_to (pc->dest_sheet, end_col, end_row);

354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
}

/**
 * x_selection_received:
 *
 * Invoked when the selection has been received by our application.
 * This is triggered by a call we do to gtk_selection_convert.
 */
static void
x_selection_received (GtkWidget *widget, GtkSelectionData *sel, guint time, gpointer data)
{
	SheetSelection *ss;
	Workbook       *wb = data;
	clipboard_paste_closure_t *pc = wb->clipboard_paste_callback_data;
	CellRegion *content;

	ss = pc->dest_sheet->selections->data;
	
	/* Did X provide any selection? */
	if (sel->length < 0){
		content = pc->region;
		if (!content)
			return;
	} else
		content = x_selection_to_cell_region (sel->data, sel->length);

	sheet_paste_selection (pc->dest_sheet, content, ss, pc);
			    
382
	/* Release the resources we used */
383
	if (sel->length >= 0)
384 385 386
		clipboard_release (content);

	/* Remove our used resources */
387 388 389 390
	if (wb->clipboard_paste_callback_data != NULL) {
		g_free (wb->clipboard_paste_callback_data);
		wb->clipboard_paste_callback_data = NULL;
	}
391 392
}

Miguel de Icaza's avatar
Miguel de Icaza committed
393
/**
394 395 396 397
 * x_selection_handler:
 *
 * Callback invoked when another application requests we render the selection.
 */
398
static void
Tom Dyas's avatar
Tom Dyas committed
399
x_selection_handler (GtkWidget *widget, GtkSelectionData *selection_data, guint info, guint time, gpointer data)
400
{
401 402
	gboolean content_needs_free = FALSE;
	CellRegion *clipboard = application_clipboard_contents_get ();
403 404
	char *rendered_selection;
	
405 406 407 408 409 410 411 412 413
	if (clipboard == NULL) {
		Sheet *sheet = application_clipboard_sheet_get ();
		Range const *a = application_clipboard_area_get ();
		content_needs_free = TRUE;
		clipboard =
		    clipboard_copy_cell_range (sheet,
					       a->start.col, a->start.row,
					       a->end.col,   a->end.row);
	}
414

415 416 417
	g_return_if_fail (clipboard != NULL);

	rendered_selection = cell_region_render_ascii (clipboard);
418 419 420 421
	
	gtk_selection_data_set (
		selection_data, GDK_SELECTION_TYPE_STRING, 8,
		rendered_selection, strlen (rendered_selection));
Morten Welinder's avatar
Morten Welinder committed
422
	g_free (rendered_selection);
423 424 425

	if (content_needs_free)
		clipboard_release (clipboard);
426 427
}

428
/**
429
 * x_selection_clear:
430
 *
431
 * Callback for the "we lost the X selection" signal
432
 */
433 434
static gint
x_selection_clear (GtkWidget *widget, GdkEventSelection *event, Workbook *wb)
435
{
436
	wb->have_x_selection = FALSE;
437

438
	return TRUE;
439 440
}

441 442
/**
 * x_clipboard_bind_workbook:
443 444
 *
 * Binds the signals related to the X selection to the Workbook
445
 * and initialized the clipboard data structures for the Workbook.
446
 */
447 448 449 450
void
x_clipboard_bind_workbook (Workbook *wb)
{
	wb->have_x_selection = FALSE;
451
	wb->clipboard_paste_callback_data = NULL;
452 453 454
	
	gtk_signal_connect (
		GTK_OBJECT (wb->toplevel), "selection_clear_event",
Arturo Espinosa's avatar
Arturo Espinosa committed
455
		GTK_SIGNAL_FUNC (x_selection_clear), wb);
456

457 458
	gtk_signal_connect (
		GTK_OBJECT (wb->toplevel), "selection_received",
Arturo Espinosa's avatar
Arturo Espinosa committed
459
		GTK_SIGNAL_FUNC (x_selection_received), wb);
Tom Dyas's avatar
Tom Dyas committed
460 461 462

	gtk_signal_connect (
		GTK_OBJECT (wb->toplevel), "selection_get",
Arturo Espinosa's avatar
Arturo Espinosa committed
463
		GTK_SIGNAL_FUNC (x_selection_handler), NULL);
Tom Dyas's avatar
Tom Dyas committed
464 465

	gtk_selection_add_target (
466
		wb->toplevel,
Tom Dyas's avatar
Tom Dyas committed
467
		GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, 0);
468 469
}

470
/**
471 472 473 474 475
 * clipboard_export_cell_region:
 *
 * This routine exports a CellRegion to the X selection
 */
static void
476
clipboard_export_cell_region (Workbook *wb)
477 478
{
	wb->have_x_selection = gtk_selection_owner_set (
479
		wb->toplevel,
480 481
		GDK_SELECTION_PRIMARY,
		GDK_CURRENT_TIME);
482

483
}
Arturo Espinosa's avatar
Arturo Espinosa committed
484 485 486 487 488 489

typedef struct {
	int        base_col, base_row;
	CellRegion *r;
} append_cell_closure_t;

490
static Value *
491
clipboard_prepend_cell (Sheet *sheet, int col, int row, Cell *cell, void *user_data)
Arturo Espinosa's avatar
Arturo Espinosa committed
492 493 494 495 496 497
{
	append_cell_closure_t *c = user_data;
	CellCopy *copy;

	copy = g_new (CellCopy, 1);

498
	copy->type = CELL_COPY_TYPE_CELL;
499 500 501
	copy->u.cell.cell = cell_copy (cell);
	/* Horrific inefficiency */
	copy->u.cell.mstyle = sheet_style_compute (sheet, col, row);
502 503
	copy->col_offset    = col - c->base_col;
	copy->row_offset    = row - c->base_row;
Arturo Espinosa's avatar
Arturo Espinosa committed
504 505 506
	
	c->r->list = g_list_prepend (c->r->list, copy);

507
	return NULL;
Arturo Espinosa's avatar
Arturo Espinosa committed
508 509
}

510
/**
511 512 513 514
 * clipboard_copy_cell_range:
 *
 * Entry point to the clipboard copy code
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
515
CellRegion *
516 517
clipboard_copy_cell_range (Sheet *sheet,
			   int start_col, int start_row,
518
			   int end_col, int end_row)
Arturo Espinosa's avatar
Arturo Espinosa committed
519 520
{
	append_cell_closure_t c;
521
	
522 523 524 525
	g_return_val_if_fail (sheet != NULL, NULL);
	g_return_val_if_fail (IS_SHEET (sheet), NULL);
	g_return_val_if_fail (start_col <= end_col, NULL);
	g_return_val_if_fail (start_row <= end_row, NULL);
526

527
	c.r = g_new0 (CellRegion, 1);
Arturo Espinosa's avatar
Arturo Espinosa committed
528 529 530

	c.base_col = start_col;
	c.base_row = start_row;
531 532
	c.r->cols = end_col - start_col + 1;
	c.r->rows = end_row - start_row + 1;
Arturo Espinosa's avatar
Arturo Espinosa committed
533 534
	
	sheet_cell_foreach_range (
535
		sheet, TRUE, start_col, start_row, end_col, end_row,
536 537 538 539
		clipboard_prepend_cell, &c);

	/* reverse the list so that upper left corner is first */
	c.r->list = g_list_reverse (c.r->list);
540

541
	clipboard_export_cell_region (sheet->workbook);
542
	
543
	return c.r;
Arturo Espinosa's avatar
Arturo Espinosa committed
544 545
}

546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566
static gboolean
workbook_selection_locator (Workbook *wb, gpointer data)
{
	Workbook **target = data;
	
	if (wb->have_x_selection)
		*target = wb;

	return TRUE;
}

static Workbook *
find_local_workbook_with_selection (void)
{
	Workbook *result = NULL;
	
	workbook_foreach (workbook_selection_locator, &result);

	return result;
}

567 568 569 570 571 572 573 574 575
/**
 * clipboard_paste_region:
 * @region:      A cell region
 * @dest_sheet:  Destination sheet
 * @dest_col:    Column where we should paste the region in dest_sheet
 * @dest_row:    Row where we should paste the region in dest_sheet
 * @paste_flags: Paste flags
 * @time:        Time at which the event happened.
 *
576 577
 * Main entry point for the paste code
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
578
void
Arturo Espinosa's avatar
Arturo Espinosa committed
579
clipboard_paste_region (CellRegion *region, Sheet *dest_sheet,
580 581
			int dest_col,       int dest_row,
			int paste_flags,    guint32 time)
Arturo Espinosa's avatar
Arturo Espinosa committed
582
{
583
	clipboard_paste_closure_t *data;
584
	Workbook *workbook_holding_selection;
585
	
586 587 588
	g_return_if_fail (dest_sheet != NULL);
	g_return_if_fail (IS_SHEET (dest_sheet));

589
	if (dest_sheet->workbook->clipboard_paste_callback_data != NULL) {
590 591
		g_free (dest_sheet->workbook->clipboard_paste_callback_data);	
		dest_sheet->workbook->clipboard_paste_callback_data = NULL;
Arturo Espinosa's avatar
Arturo Espinosa committed
592 593
	}

594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617
	/*
	 * OK, we dont have the X selection, so we try to get it:
	 * we setup all of the parameters for the callback in
	 * case the X selection fails and we have to fallback to
	 * using our internal selection
	 */
	data = g_new (clipboard_paste_closure_t, 1);
	dest_sheet->workbook->clipboard_paste_callback_data = data;

	data->region       = region;
	data->dest_sheet   = dest_sheet;
	data->dest_col     = dest_col;
	data->dest_row     = dest_row;
	data->paste_flags  = paste_flags;

	/*
	 * If we own the selection, there is no need to ask X for
	 * the selection: we do the paste from our internal buffer.
	 *
	 * This is better as we have all sorts of useful information
	 * to paste from in this case (instead of the simplistic text
	 * we get from X
	 */

618 619 620 621 622 623 624 625
	workbook_holding_selection = find_local_workbook_with_selection ();
	if (workbook_holding_selection && region){
		CellRegion *content;
		
		content = region;

		sheet_paste_selection (dest_sheet, content, dest_sheet->selections->data, data);
		
626 627 628 629 630
		/* Check that this has not already been freed */
		if (workbook_holding_selection->clipboard_paste_callback_data != NULL) {
			workbook_holding_selection->clipboard_paste_callback_data = NULL;
			g_free (data);
		}
631
		return;
632
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
633

634 635 636
	/* Now, trigger a grab of the X selection */
	gtk_selection_convert (
		dest_sheet->workbook->toplevel, GDK_SELECTION_PRIMARY,
637
		GDK_TARGET_STRING, time);
638 639
}

640 641 642
/*
 * Destroys the contents of a CellRegion
 */
643 644 645 646 647 648 649
void
clipboard_release (CellRegion *region)
{
	CellCopyList *l;

	g_return_if_fail (region != NULL);
	
650
	for (l = region->list; l; l = l->next){
651
		CellCopy *this_cell = l->data;
652
		
653 654
		if (this_cell->type != CELL_COPY_TYPE_TEXT) {
			/* The cell is not really in the rows or columns */
655 656 657 658 659 660
			this_cell->u.cell.cell->sheet = NULL;
			this_cell->u.cell.cell->row = NULL;
			this_cell->u.cell.cell->col = NULL;
			mstyle_unref (this_cell->u.cell.mstyle);
			this_cell->u.cell.mstyle = NULL;
			cell_destroy (this_cell->u.cell.cell);
661
		} else
662
			g_free (this_cell->u.text);
663 664
		g_free (this_cell);
	}
665

666 667
	g_list_free (region->list);
	g_free (region);
Arturo Espinosa's avatar
Arturo Espinosa committed
668
}
669

670