gnumeric-sheet.c 29.3 KB
Newer Older
1 2 3 4 5 6
/*
 * The Gnumeric Sheet widget.
 *
 * Author:
 *     Miguel de Icaza (miguel@kernel.org)
 */
7
#include <config.h>
8 9

#include <gnome.h>
10 11
#include <gdk/gdkkeysyms.h>
#include <gdk/gdkx.h>
12
#include <string.h>
13 14
#include "gnumeric.h"
#include "gnumeric-sheet.h"
Miguel de Icaza's avatar
New:  
Miguel de Icaza committed
15
#include "sheet-object.h"
16
#include "color.h"
17
#include "cursors.h"
18
#include "utils.h"
19

20 21
#define CURSOR_COL(gsheet) (gsheet)->sheet_view->sheet->cursor_col
#define CURSOR_ROW(gsheet) (gsheet)->sheet_view->sheet->cursor_row
22

23
static GnomeCanvasClass *sheet_parent_class;
24

25 26 27 28 29 30
static void
gnumeric_sheet_destroy (GtkObject *object)
{
	GnumericSheet *gsheet;

	/* Add shutdown code here */
Arturo Espinosa's avatar
Arturo Espinosa committed
31 32
	gsheet = GNUMERIC_SHEET (object);
	
33 34 35 36
	if (GTK_OBJECT_CLASS (sheet_parent_class)->destroy)
		(*GTK_OBJECT_CLASS (sheet_parent_class)->destroy)(object);
}

Arturo Espinosa's avatar
Arturo Espinosa committed
37
static GnumericSheet *
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
38
gnumeric_sheet_create (SheetView *sheet_view, GtkWidget *entry)
Arturo Espinosa's avatar
Arturo Espinosa committed
39 40 41 42 43 44 45
{
	GnumericSheet *gsheet;
	GnomeCanvas   *canvas;
	
	gsheet = gtk_type_new (gnumeric_sheet_get_type ());
	canvas = GNOME_CANVAS (gsheet);

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
46
	gsheet->sheet_view = sheet_view;
Arturo Espinosa's avatar
Arturo Espinosa committed
47 48
	gsheet->top_col = 0;
	gsheet->top_row = 0;
49
	gsheet->entry   = entry;
50
	
Arturo Espinosa's avatar
Arturo Espinosa committed
51 52 53
	return gsheet;
}

54
void
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
55
gnumeric_sheet_get_cell_bounds (GnumericSheet *gsheet, int col, int row, int *x, int *y, int *w, int *h)
56
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
57 58
	Sheet *sheet;
	
Arturo Espinosa's avatar
Arturo Espinosa committed
59
	g_return_if_fail (gsheet != NULL);
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
60
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet)); 
61

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
62
	sheet = gsheet->sheet_view->sheet;
Arturo Espinosa's avatar
Arturo Espinosa committed
63
	
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
64 65
	*x = sheet_col_get_distance (sheet, gsheet->top_col, col);
	*y = sheet_row_get_distance (sheet, gsheet->top_row, row);
Arturo Espinosa's avatar
Arturo Espinosa committed
66

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
67 68
	*w = sheet_col_get_distance (sheet, col, col + 1);
	*h = sheet_row_get_distance (sheet, row, row + 1);
Arturo Espinosa's avatar
Arturo Espinosa committed
69 70
}

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
71 72 73 74 75 76 77 78 79
/*
 * gnumeric_sheet_cursor_set
 *  @gsheet: The sheet
 *  @col:    the column
 *  @row:    the row
 *
 * This informs the GnumericSheet of the cursor position.  It is
 * used to sync the contents of the scrollbars with our position
 */
Arturo Espinosa's avatar
Arturo Espinosa committed
80
void
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
81
gnumeric_sheet_cursor_set (GnumericSheet *gsheet, int col, int row)
Arturo Espinosa's avatar
Arturo Espinosa committed
82
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
83 84 85
	GtkAdjustment *ha, *va;
	SheetView *sheet_view;
	
Arturo Espinosa's avatar
Arturo Espinosa committed
86
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));
87

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
88
	sheet_view = gsheet->sheet_view;
89

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
90 91 92 93 94 95 96 97 98
	if (sheet_view->ha){
		ha = GTK_ADJUSTMENT (sheet_view->ha);
		va = GTK_ADJUSTMENT (sheet_view->va);
		ha->value = col;
		va->value = row;
		
		gtk_adjustment_value_changed (ha);
		gtk_adjustment_value_changed (va);
	}
99 100
}

101 102
/*
 * gnumeric_sheet_set_selection:
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
103
 *  @gsheet:    The sheet name
104 105 106 107 108 109 110
 *  @start_col: The starting column.
 *  @start_row: The starting row
 *  @end_col:   The end column
 *  @end_row:   The end row
 *
 * Set the current selection to cover the inclusive area delimited by
 * start_col, start_row, end_col and end_row.  The actual cursor is
111
 * placed at base_col, base_row
112 113
 */
void
Arturo Espinosa's avatar
Arturo Espinosa committed
114
gnumeric_sheet_set_selection (GnumericSheet *gsheet, SheetSelection *ss)
115
{
Arturo Espinosa's avatar
Arturo Espinosa committed
116
	g_return_if_fail (gsheet != NULL);
117
	g_return_if_fail (ss != NULL);
Arturo Espinosa's avatar
Arturo Espinosa committed
118
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));
119

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
120 121
	sheet_cursor_set (
		gsheet->sheet_view->sheet,
122
		ss->base_col, ss->base_row,
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
123 124
		ss->start_col, ss->start_row,
		ss->end_col, ss->end_row);
125 126 127
}

/*
128
 * move_cursor:
129 130 131
 *   @Sheet:    The sheet where the cursor is located
 *   @col:      The new column for the cursor.
 *   @row:      The new row for the cursor.
132
 *   @clear_selection: If set, clear the selection before moving
133 134 135 136 137
 *   Moves the sheet cursor to a new location, it clears the selection,
 *   accepts any pending output on the editing line and moves the cell
 *   cursor.
 */
static void
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
138
move_cursor (GnumericSheet *gsheet, int col, int row, gboolean clear_selection)
139
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
140
	Sheet *sheet = gsheet->sheet_view->sheet;
141 142

	if (clear_selection)
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
143
		sheet_selection_reset_only (sheet);
144

145
	sheet_make_cell_visible (sheet, col, row);
146
	sheet_cursor_set (sheet, col, row, col, row, col, row);
147

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
148 149
	if (clear_selection)
		sheet_selection_append (sheet, col, row);
150 151
}

152 153 154 155 156 157 158 159 160
void
gnumeric_sheet_move_cursor (GnumericSheet *gsheet, int col, int row)
{
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));

	move_cursor (gsheet, col, row, 1);
}

Arturo Espinosa's avatar
Arturo Espinosa committed
161 162 163 164 165 166 167 168
void
gnumeric_sheet_set_cursor_bounds (GnumericSheet *gsheet,
				  int start_col, int start_row,
				  int end_col,   int end_row)
{
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
169 170 171 172
	item_cursor_set_bounds (
		gsheet->item_cursor,
		start_col, start_row,
		end_col, end_row);
Arturo Espinosa's avatar
Arturo Espinosa committed
173 174
}

175
/*
176
 * move_cursor_horizontal:
177 178 179 180 181 182
 *  @Sheet:  The sheet name
 *  @count:  number of units to move the cursor horizontally
 *
 * Moves the cursor count columns
 */
static void
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
183
move_cursor_horizontal (GnumericSheet *gsheet, int count)
184
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
185
	Sheet *sheet = gsheet->sheet_view->sheet;
186 187
	int new_left;
	
188
	new_left = sheet->cursor_col + count;
189 190

	if (new_left < 0)
191
		new_left = 0;
192 193
	if (new_left > SHEET_MAX_COLS-1)
		new_left = SHEET_MAX_COLS-1;
194
	
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
195
	move_cursor (gsheet, new_left, sheet->cursor_row, TRUE);
196 197
}

198
/*
199
 * move_cursor_vertical:
200 201 202 203 204
 *  @Sheet:  The sheet name
 *  @count:  number of units to move the cursor vertically
 *
 * Moves the cursor count rows
 */
205
static void
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
206
move_cursor_vertical (GnumericSheet *gsheet, int count)
207
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
208
	Sheet *sheet = gsheet->sheet_view->sheet;
209
	int new_top;
210

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
211
	new_top = CURSOR_ROW (gsheet) + count;
212 213

	if (new_top < 0)
214
		new_top = 0;
215 216 217
	if (new_top > SHEET_MAX_ROWS-1)
		new_top = SHEET_MAX_ROWS-1;
	
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
218
	move_cursor (gsheet, sheet->cursor_col, new_top, TRUE);
219 220
}

221
static void
222
move_horizontal_selection (GnumericSheet *gsheet, int count)
223
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
224
	sheet_selection_extend_horizontal (gsheet->sheet_view->sheet, count);
225 226
}

227
static void
228
move_vertical_selection (GnumericSheet *gsheet, int count)
229
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
230
	sheet_selection_extend_vertical (gsheet->sheet_view->sheet, count);
231 232
}

233 234 235 236 237 238 239 240
/*
 * gnumeric_sheet_can_move_cursor
 *  @gsheet:   the object
 *
 * Returns true if the cursor keys should be used to select
 * a cell range (if the cursor is in a spot in the expression
 * where it makes sense to have a cell reference), false if not.
 */
Miguel de Icaza's avatar
Miguel de Icaza committed
241
int
242 243
gnumeric_sheet_can_move_cursor (GnumericSheet *gsheet)
{
Arturo Espinosa's avatar
Arturo Espinosa committed
244 245
	GtkEntry *entry;
	int cursor_pos;
246

Arturo Espinosa's avatar
Arturo Espinosa committed
247 248
	g_return_val_if_fail (gsheet != NULL, FALSE);
	g_return_val_if_fail (GNUMERIC_IS_SHEET (gsheet), FALSE);
249 250 251

	if (!gsheet->sheet_view->sheet->editing)
		return FALSE;
Arturo Espinosa's avatar
Arturo Espinosa committed
252
	
253 254
	if (gsheet->item_editor && gsheet->selecting_cell)
		return TRUE;
Arturo Espinosa's avatar
Arturo Espinosa committed
255 256 257
	
	entry = GTK_ENTRY (gsheet->entry);
	cursor_pos = GTK_EDITABLE (entry)->current_pos;
258 259 260 261 262 263 264 265 266
	
	if (entry->text [0] != '=')
		return FALSE;
	if (cursor_pos == 0)
		return FALSE;
		
	switch (entry->text [cursor_pos-1]){
	case '=': case '-': case '*': case '/': case '^': 
	case '+': case '&': case '(': case '%': case '!':
267
	case ':': case ',':
268 269 270 271 272 273 274
		return TRUE;
	}
	
	return FALSE;
}

static void
Miguel de Icaza's avatar
Miguel de Icaza committed
275
start_cell_selection_at (GnumericSheet *gsheet, int col, int row)
276 277 278
{
	GnomeCanvas *canvas = GNOME_CANVAS (gsheet);
	GnomeCanvasGroup *group = GNOME_CANVAS_GROUP (canvas->root);
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
279
	
280 281 282 283 284 285
	g_return_if_fail (gsheet->selecting_cell == FALSE);
	
	gsheet->selecting_cell = TRUE;
	gsheet->selection = ITEM_CURSOR (gnome_canvas_item_new (
		group,
		item_cursor_get_type (),
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
286
		"Sheet", gsheet->sheet_view->sheet,
287 288
		"Grid",  gsheet->item_grid,
		"Style", ITEM_CURSOR_ANTED, NULL));
Miguel de Icaza's avatar
Miguel de Icaza committed
289 290 291
	gsheet->selection->base_col = col;
	gsheet->selection->base_row = row;
	item_cursor_set_bounds (ITEM_CURSOR (gsheet->selection), col, row, col, row);
292
				
293 294 295 296 297
	gsheet->sel_cursor_pos = GTK_EDITABLE (gsheet->entry)->current_pos;
	gsheet->sel_text_len = 0;
}

static void
Miguel de Icaza's avatar
Miguel de Icaza committed
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
start_cell_selection (GnumericSheet *gsheet)
{
	Sheet *sheet = gsheet->sheet_view->sheet;

	start_cell_selection_at (gsheet, sheet->cursor_col, sheet->cursor_row);
}

void
gnumeric_sheet_start_cell_selection (GnumericSheet *gsheet, int col, int row)
{
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));

	if (gsheet->selecting_cell)
		return;

	start_cell_selection_at (gsheet, col, row);
}

void
gnumeric_sheet_stop_cell_selection (GnumericSheet *gsheet)
319
{
Miguel de Icaza's avatar
Miguel de Icaza committed
320 321 322
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));

323 324 325 326 327 328 329 330
	if (!gsheet->selecting_cell)
		return;
	
	gsheet->selecting_cell = FALSE;
	gtk_object_destroy (GTK_OBJECT (gsheet->selection));
	gsheet->selection = NULL;
}

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
void
gnumeric_sheet_create_editing_cursor (GnumericSheet *gsheet)
{
	GnomeCanvas *canvas = GNOME_CANVAS (gsheet);
	GnomeCanvasItem *item;
	Sheet *sheet;
	int col, row;

	sheet = gsheet->sheet_view->sheet;
	col = sheet->cursor_col;
	row = sheet->cursor_row;
	
	item = gnome_canvas_item_new (GNOME_CANVAS_GROUP(canvas->root),
				      item_edit_get_type (),
				      "ItemEdit::Sheet",    sheet,
				      "ItemEdit::Grid",     gsheet->item_grid,
				      "ItemEdit::Col",      col, 
				      "ItemEdit::Row",      row,
				      "ItemEdit::GtkEntry", sheet->workbook->ea_input,
				      NULL);

	gsheet->item_editor = ITEM_EDIT (item);
}

Arturo Espinosa's avatar
Arturo Espinosa committed
355
static void
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
356 357 358 359 360 361 362 363 364 365
destroy_item_editor (GnumericSheet *gsheet)
{
	g_return_if_fail (gsheet->item_editor);
	
	gtk_object_destroy (GTK_OBJECT (gsheet->item_editor));
	gsheet->item_editor = NULL;
}

void
gnumeric_sheet_destroy_editing_cursor (GnumericSheet *gsheet)
Arturo Espinosa's avatar
Arturo Espinosa committed
366
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
367 368 369
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));
	
Miguel de Icaza's avatar
Miguel de Icaza committed
370
	gnumeric_sheet_stop_cell_selection (gsheet);
Arturo Espinosa's avatar
Arturo Espinosa committed
371
	
372 373 374
	if (!gsheet->item_editor)
		return;

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
375 376 377 378 379 380 381 382 383 384 385 386
	destroy_item_editor (gsheet);
}

void
gnumeric_sheet_stop_editing (GnumericSheet *gsheet)
{
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));

	if (!gsheet->item_editor)
		return;

387
	destroy_item_editor (gsheet);
Arturo Espinosa's avatar
Arturo Espinosa committed
388 389
}

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
static void
selection_remove_selection_string (GnumericSheet *gsheet)
{
	gtk_editable_delete_text (GTK_EDITABLE (gsheet->entry),
				  gsheet->sel_cursor_pos,
				  gsheet->sel_cursor_pos+gsheet->sel_text_len);
}

static void
selection_insert_selection_string (GnumericSheet *gsheet)
{
	ItemCursor *sel = gsheet->selection;
	char buffer [20];
	int pos;

	/* Get the new selection string */
	strcpy (buffer, cell_name (sel->start_col, sel->start_row));
	if (!(sel->start_col == sel->end_col && sel->start_row == sel->end_row)){
		strcat (buffer, ":");
		strcat (buffer, cell_name (sel->end_col, sel->end_row));
	}
	gsheet->sel_text_len = strlen (buffer);

	pos = gsheet->sel_cursor_pos;
	gtk_editable_insert_text (GTK_EDITABLE (gsheet->entry),
				  buffer, strlen (buffer),
				  &pos);

	/* Set the cursor at the end.  It looks nicer */
	gtk_editable_set_position (GTK_EDITABLE (gsheet->entry),
				   gsheet->sel_cursor_pos +
				   gsheet->sel_text_len);
}

Miguel de Icaza's avatar
Miguel de Icaza committed
424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
/*
 * Invoked by Item-Grid to extend the cursor selection
 */
void
gnumeric_sheet_selection_extend (GnumericSheet *gsheet, int col, int row)
{
	ItemCursor *ic;
	
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));
	g_return_if_fail (gsheet->selecting_cell);
	g_return_if_fail (col < SHEET_MAX_COLS);
	g_return_if_fail (row < SHEET_MAX_ROWS);

	ic = gsheet->selection;

	selection_remove_selection_string (gsheet);
	item_cursor_set_bounds (ic,
				MIN (ic->base_col, col),
				MIN (ic->base_row, row),
				MAX (ic->base_col, col),
				MAX (ic->base_row, row));
	selection_insert_selection_string (gsheet);
}

/*
 * Invoked by Item-Grid to place the selection cursor on a specific
 * spot.
 */
void
gnumeric_sheet_selection_cursor_place (GnumericSheet *gsheet, int col, int row)
{
	ItemCursor *ic;
	
	g_return_if_fail (gsheet != NULL);
	g_return_if_fail (GNUMERIC_IS_SHEET (gsheet));
	g_return_if_fail (gsheet->selecting_cell);
	g_return_if_fail (col < SHEET_MAX_COLS);
	g_return_if_fail (row < SHEET_MAX_ROWS);
	
	ic = gsheet->selection;
	
	selection_remove_selection_string (gsheet);
	item_cursor_set_bounds (ic, col, row, col, row);
	selection_insert_selection_string (gsheet);
}

471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
static void
selection_cursor_move_horizontal (GnumericSheet *gsheet, int dir)
{
	ItemCursor *ic;

	g_return_if_fail (dir == -1 || dir == 1);
	
	if (!gsheet->selecting_cell)
		start_cell_selection (gsheet);

	ic = gsheet->selection;
	if (dir == -1){
		if (ic->start_col == 0)
			return;
	} else {
		if (ic->end_col + 1 > (SHEET_MAX_COLS-1))
			return;
	}
	
	selection_remove_selection_string (gsheet);
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
491
	item_cursor_set_bounds (ic, 
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520
				ic->start_col + dir,
				ic->start_row, 
				ic->end_col + dir,
				ic->end_row);
	selection_insert_selection_string (gsheet);
}

static void
selection_cursor_move_vertical (GnumericSheet *gsheet, int dir)
{
	ItemCursor *ic;

	g_return_if_fail (dir == -1 || dir == 1);

	if (!gsheet->selecting_cell)
		start_cell_selection (gsheet);

	ic = gsheet->selection;
	if (dir == -1){
		if (ic->start_row == 0)
			return;
	} else {
		if (ic->end_row + 1 > (SHEET_MAX_ROWS-1))
			return;
	}

	if (!gsheet->selecting_cell)
		start_cell_selection (gsheet);
	item_cursor_set_bounds (ic,
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
521 522 523 524
				ic->start_col,
				ic->start_row + dir,
				ic->end_col,
				ic->end_row + dir);
525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541
	selection_remove_selection_string (gsheet);
	selection_insert_selection_string (gsheet);
}

static void
selection_expand_horizontal (GnumericSheet *gsheet, int dir)
{
	ItemCursor *ic;

	g_return_if_fail (dir == -1 || dir == 1);

	if (!gsheet->selecting_cell){
		selection_cursor_move_vertical (gsheet, dir);
		return;
	}

	ic = gsheet->selection;
Miguel de Icaza's avatar
Miguel de Icaza committed
542
	if (ic->end_col == SHEET_MAX_COLS-1)
543 544
			return;

Miguel de Icaza's avatar
Miguel de Icaza committed
545 546 547
	if (dir == -1 && ic->start_col == ic->end_col)
		return;
	
548 549
	selection_remove_selection_string (gsheet);
	item_cursor_set_bounds (ic,
Miguel de Icaza's avatar
Miguel de Icaza committed
550
				ic->start_col,
551
				ic->start_row,
Miguel de Icaza's avatar
Miguel de Icaza committed
552
				ic->end_col  + dir,
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
				ic->end_row);
	selection_insert_selection_string (gsheet);
}

static void
selection_expand_vertical (GnumericSheet *gsheet, int dir)
{
	ItemCursor *ic;

	g_return_if_fail (dir == -1 || dir == 1);

	if (!gsheet->selecting_cell){
		selection_cursor_move_vertical (gsheet, dir);
		return;
	}

	ic = gsheet->selection;
	
Miguel de Icaza's avatar
Miguel de Icaza committed
571 572
	if (ic->end_row == SHEET_MAX_ROWS-1)
		return;
573

Miguel de Icaza's avatar
Miguel de Icaza committed
574 575 576
	if (dir == -1 && ic->start_row == ic->end_row)
		return;
	
577 578 579
	selection_remove_selection_string (gsheet);
	item_cursor_set_bounds (ic,
				ic->start_col,
Miguel de Icaza's avatar
Miguel de Icaza committed
580
				ic->start_row,
581
				ic->end_col,
Miguel de Icaza's avatar
Miguel de Icaza committed
582
				ic->end_row + dir);
583 584 585 586
	selection_insert_selection_string (gsheet);
}

/*
Miguel de Icaza's avatar
New:  
Miguel de Icaza committed
587
 * key press event handler for the gnumeric sheet for the sheet mode
588
 */
589
static gint
Miguel de Icaza's avatar
New:  
Miguel de Icaza committed
590
gnumeric_sheet_key_mode_sheet (GnumericSheet *gsheet, GdkEventKey *event)
591
{
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
592 593
	Sheet *sheet = gsheet->sheet_view->sheet;
	Workbook *wb = sheet->workbook;
Arturo Espinosa's avatar
Arturo Espinosa committed
594 595
	void (*movefn_horizontal) (GnumericSheet *, int);
	void (*movefn_vertical)   (GnumericSheet *, int);
Arturo Espinosa's avatar
Arturo Espinosa committed
596
	int  cursor_move = gnumeric_sheet_can_move_cursor (gsheet);
Miguel de Icaza's avatar
Miguel de Icaza committed
597
	
598
	if ((event->state & GDK_SHIFT_MASK) != 0){
599 600
		if (cursor_move){
			movefn_horizontal = selection_expand_horizontal;
Miguel de Icaza's avatar
Miguel de Icaza committed
601
			movefn_vertical = selection_expand_vertical;
602 603 604 605
		} else {
			movefn_horizontal = move_horizontal_selection;
			movefn_vertical   = move_vertical_selection;
		}
606
	} else {
607 608 609 610 611 612 613
		if (cursor_move){
			movefn_horizontal = selection_cursor_move_horizontal;
			movefn_vertical   = selection_cursor_move_vertical;
		} else {
			movefn_horizontal = move_cursor_horizontal;
			movefn_vertical = move_cursor_vertical;
		}
614
	}
615

616 617 618 619 620
	/* Ignore a few keys (to avoid the selection cursor to be killed
	 * in some cases
	 */
	if (cursor_move){
		switch (event->keyval){
Miguel de Icaza's avatar
Miguel de Icaza committed
621 622 623
		case GDK_Shift_L:   case GDK_Shift_R:
		case GDK_Alt_L:     case GDK_Alt_R:
		case GDK_Control_L: case GDK_Control_R:
624 625 626
			return 1;
		}
	}
Tristan Tarrant's avatar
Tristan Tarrant committed
627

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
	/*
	 * The following sequences do not trigger an editor-start
	 * but if the editor is running we forward the events to it.
	 */
	if (!gsheet->item_editor){
		
		if ((event->state & GDK_CONTROL_MASK) != 0) {

			switch (event->keyval) {

			case GDK_space:
				sheet_selection_reset_only (sheet);
				sheet_selection_append_range (
					sheet,
					sheet->cursor_col, sheet->cursor_row,
					sheet->cursor_col, 0,
					sheet->cursor_col, SHEET_MAX_ROWS-1);
				sheet_redraw_all (sheet);
				return 1;
			}
Tristan Tarrant's avatar
Tristan Tarrant committed
648
		}
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664

		if ((event->state & GDK_SHIFT_MASK) != 0){

			switch (event->keyval) {
				/* select row */
			case GDK_space:
				sheet_selection_reset_only (sheet);
				sheet_selection_append_range (
					sheet,
					sheet->cursor_col, sheet->cursor_row,
					0, sheet->cursor_row,
					SHEET_MAX_COLS-1, sheet->cursor_row);
				sheet_redraw_all (sheet);
				return 1;
			}
		}
Arturo Espinosa's avatar
Arturo Espinosa committed
665

Tristan Tarrant's avatar
Tristan Tarrant committed
666 667
	}

668
	switch (event->keyval){
Miguel de Icaza's avatar
Miguel de Icaza committed
669
	case GDK_KP_Left:
670
	case GDK_Left:
Arturo Espinosa's avatar
Arturo Espinosa committed
671
		(*movefn_horizontal)(gsheet, -1);
672 673
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
674
	case GDK_KP_Right:
675
	case GDK_Right:
Arturo Espinosa's avatar
Arturo Espinosa committed
676
		(*movefn_horizontal)(gsheet, 1);
677 678
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
679
	case GDK_KP_Up:
680
	case GDK_Up:
Arturo Espinosa's avatar
Arturo Espinosa committed
681
		(*movefn_vertical)(gsheet, -1);
682 683
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
684
	case GDK_KP_Down:
685
	case GDK_Down:
Arturo Espinosa's avatar
Arturo Espinosa committed
686
		(*movefn_vertical)(gsheet, 1);
687 688
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
689
	case GDK_KP_Page_Up:
690 691 692 693
	case GDK_Page_Up:
	        (*movefn_vertical)(gsheet, -(gsheet->last_visible_row-gsheet->top_row));
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
694
	case GDK_KP_Page_Down:
695 696 697 698
	case GDK_Page_Down:
	        (*movefn_vertical)(gsheet, gsheet->last_visible_row-gsheet->top_row);
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
699
	case GDK_KP_Home:
700
	case GDK_Home:
701
		if ((event->state & GDK_CONTROL_MASK) != 0){
702
			sheet_make_cell_visible (sheet, 0, 0);
703 704 705 706
			sheet_cursor_move (sheet, 0, 0);
			break;
		} else
			(*movefn_horizontal)(gsheet, -CURSOR_COL(gsheet));
707 708 709 710
		break;

	case GDK_KP_Delete:
	case GDK_Delete: 
Miguel de Icaza's avatar
Miguel de Icaza committed
711
		sheet_selection_clear (sheet);
712 713
		break;

Miguel de Icaza's avatar
Miguel de Icaza committed
714
	case GDK_KP_Enter:
715
	case GDK_Return:
Miguel de Icaza's avatar
Miguel de Icaza committed
716 717 718
		if ((event->state & GDK_CONTROL_MASK) != 0){
			if (gsheet->item_editor){
				Cell *cell;
719 720
				char *text;
				
721
				sheet_accept_pending_input (sheet);
Miguel de Icaza's avatar
Miguel de Icaza committed
722 723 724 725
				cell = sheet_cell_get (sheet,
						       sheet->cursor_col,
						       sheet->cursor_row);

726
				/* I am assuming sheet_accept_pending_input
Miguel de Icaza's avatar
Miguel de Icaza committed
727 728 729 730 731 732
				 * will always create the cell with the given
				 * input (based on the fact that we had an
				 * gsheet->item_editor when we entered this
				 * part of the code
				 */
				g_return_val_if_fail (cell != NULL, 1);
733 734 735
				text = cell_get_text (cell);
				sheet_fill_selection_with (sheet, text);
				g_free (text);
736 737
			} else {
				gtk_widget_grab_focus (gsheet->entry);
Miguel de Icaza's avatar
Miguel de Icaza committed
738 739 740 741 742 743
			}
			return 1;
		}
		/* fall down */
		
	case GDK_Tab:
744 745 746 747 748 749 750 751 752 753
	{
		int col, row;
		int walking_selection;
		int direction, horizontal;

		/* Figure out the direction */
		direction = (event->state & GDK_SHIFT_MASK) ? 0 : 1;
		horizontal = (event->keyval == GDK_Tab) ? 1 : 0;
		
		walking_selection = sheet_selection_walk_step (
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
754 755
			sheet, direction, horizontal,
			sheet->cursor_col, sheet->cursor_row,
756 757
			&col, &row);
		move_cursor (gsheet, col, row, walking_selection == 0);
758
		break;
759
	}
Arturo Espinosa's avatar
Arturo Espinosa committed
760 761

	case GDK_Escape:
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
762
		sheet_cancel_pending_input (sheet);
Arturo Espinosa's avatar
Arturo Espinosa committed
763
		break;
764
		
Arturo Espinosa's avatar
Arturo Espinosa committed
765 766
	case GDK_F2:
		gtk_window_set_focus (GTK_WINDOW (wb->toplevel), wb->ea_input);
Arturo Espinosa's avatar
Arturo Espinosa committed
767
		sheet->editing = TRUE;
768
		/* fall down */
769

770
	default:
Arturo Espinosa's avatar
Arturo Espinosa committed
771
		if (!gsheet->item_editor){
Arturo Espinosa's avatar
Arturo Espinosa committed
772 773 774
			if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0)
				return 0;
			
Miguel de Icaza's avatar
Miguel de Icaza committed
775 776 777
			if ((event->keyval >= 0x20 && event->keyval <= 0xff) ||
			    (event->keyval >= GDK_KP_Add && event->keyval <= GDK_KP_9))
				sheet_start_editing_at_cursor (sheet);
778
		}
Miguel de Icaza's avatar
Miguel de Icaza committed
779
		gnumeric_sheet_stop_cell_selection (gsheet);
780
		
Arturo Espinosa's avatar
Arturo Espinosa committed
781
		/* Forward the keystroke to the input line */
782
		return gtk_widget_event (gsheet->entry, (GdkEvent *) event);
783
	}
784 785

	return TRUE;
786 787
}

Miguel de Icaza's avatar
New:  
Miguel de Icaza committed
788 789 790 791 792 793 794 795 796 797 798 799
static gint
gnumeric_sheet_key_mode_object (GnumericSheet *gsheet, GdkEventKey *event)
{
	Sheet *sheet = gsheet->sheet_view->sheet;
	
	switch (event->keyval){
	case GDK_Escape:
		sheet_set_mode_type (sheet, SHEET_MODE_SHEET);
		break;

	case GDK_BackSpace:
	case GDK_Delete:
800
		gtk_object_destroy (GTK_OBJECT (sheet->current_object));
Miguel de Icaza's avatar
New:  
Miguel de Icaza committed
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823
		sheet_set_mode_type (sheet, SHEET_MODE_SHEET);
		break;

	default:
		return FALSE;
	}
	return TRUE;
}

static gint
gnumeric_sheet_key (GtkWidget *widget, GdkEventKey *event)
{
	GnumericSheet *gsheet = GNUMERIC_SHEET (widget);
	Sheet *sheet = gsheet->sheet_view->sheet;
	
	switch (sheet->mode){
	case SHEET_MODE_SHEET:
		return gnumeric_sheet_key_mode_sheet (gsheet, event);

	case SHEET_MODE_OBJECT_SELECTED:
		return gnumeric_sheet_key_mode_object (gsheet, event);

	default:
824
		return FALSE;
Miguel de Icaza's avatar
New:  
Miguel de Icaza committed
825 826 827
	}
}

Arturo Espinosa's avatar
Arturo Espinosa committed
828
GtkWidget *
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
829
gnumeric_sheet_new (SheetView *sheet_view, ItemBar *colbar, ItemBar *rowbar)
Arturo Espinosa's avatar
Arturo Espinosa committed
830
{
831
	GnomeCanvasItem *item;
Arturo Espinosa's avatar
Arturo Espinosa committed
832
	GnumericSheet *gsheet;
833 834 835
	GnomeCanvas   *gsheet_canvas;
	GnomeCanvasGroup *gsheet_group;
	GtkWidget *widget;
836
	GtkWidget *entry;
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
837 838 839 840 841
	Sheet     *sheet;
	Workbook  *workbook;
	
	g_return_val_if_fail (sheet_view  != NULL, NULL);
	g_return_val_if_fail (IS_SHEET_VIEW (sheet_view), NULL);
842 843 844 845 846
	g_return_val_if_fail (colbar != NULL, NULL);
	g_return_val_if_fail (rowbar != NULL, NULL);
	g_return_val_if_fail (IS_ITEM_BAR (colbar), NULL);
	g_return_val_if_fail (IS_ITEM_BAR (rowbar), NULL);

Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
847 848 849 850 851 852
	sheet = sheet_view->sheet;
	workbook = sheet->workbook;
	
	entry = workbook->ea_input;
	gsheet = gnumeric_sheet_create (sheet_view, entry);
	
853 854
	/* FIXME: figure out some real size for the canvas scrolling region */
	gnome_canvas_set_scroll_region (GNOME_CANVAS (gsheet), 0, 0, 1000000, 1000000);
855 856 857 858

	/* handy shortcuts */
	gsheet_canvas = GNOME_CANVAS (gsheet);
	gsheet_group = GNOME_CANVAS_GROUP (gsheet_canvas->root);
859 860 861

	gsheet->colbar = colbar;
	gsheet->rowbar = rowbar;
862 863
	
	/* The grid */
864
	item = gnome_canvas_item_new (gsheet_group,
865
				      item_grid_get_type (),
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
866
				      "ItemGrid::SheetView", sheet_view,
867 868 869 870
				      NULL);
	gsheet->item_grid = ITEM_GRID (item);

	/* The cursor */
871
	item = gnome_canvas_item_new (gsheet_group,
872 873 874 875 876
				      item_cursor_get_type (),
				      "ItemCursor::Sheet", sheet,
				      "ItemCursor::Grid", gsheet->item_grid,
				      NULL);
	gsheet->item_cursor = ITEM_CURSOR (item);
877 878
	item_cursor_set_bounds (gsheet->item_cursor, 0, 0, 1, 1);
		
879 880 881 882 883
	widget = GTK_WIDGET (gsheet);

	return widget;
}

884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
gnumeric_sheet_pattern_t gnumeric_sheet_patterns [GNUMERIC_SHEET_PATTERNS] = {
	{ N_("75%"),
	  { 0xee, 0xbb, 0xee, 0xbb, 0xee, 0xbb, 0xee, 0xbb } },

	{ N_("50%"),
	  { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 } },

	{ N_("25%"),
	  { 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44 } },

	{ N_("12%"),
	  { 0x01, 0x08, 0x40, 0x02, 0x10, 0x80, 0x04, 0x20 } },
	
	{ N_("6%"),
	  { 0x80, 0x00, 0x04, 0x00, 0x80, 0x00, 0x04, 0x00 } },

	{ N_("Horizontal lines"),
	  { 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 } },

	{ N_("Vertical lines"),
	  { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa } },

	{ N_("Diagonal lines"),
	  { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 } },

	{ N_("Diagonal lines"),
	  { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 } },

	{ N_("Diagonal lines"),
	  { 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88 } },

	{ N_("Diagonal lines"),
	  { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 } },

	{ N_("Crossed diagonals"),
	  { 0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81 } },

	{ N_("Bricks"),
	  { 0x80, 0x80, 0x80, 0xff, 0x04, 0x04, 0x04, 0xff } },
};

925

926 927 928
static void
gnumeric_sheet_realize (GtkWidget *widget)
{
929
	GnumericSheet *gsheet = GNUMERIC_SHEET (widget);
930
	GdkWindow *window;
931 932
	int i;
	
933 934
	if (GTK_WIDGET_CLASS (sheet_parent_class)->realize)
		(*GTK_WIDGET_CLASS (sheet_parent_class)->realize)(widget);
935

936
	window = widget->window;
937
	gdk_window_set_back_pixmap (GTK_LAYOUT (widget)->bin_window, NULL, FALSE);
938 939 940 941

	for (i = 0; i < GNUMERIC_SHEET_PATTERNS; i++)
		gsheet->patterns [i] = gdk_bitmap_create_from_data (
			window, gnumeric_sheet_patterns [i].pattern, 8, 8);
942

Arturo Espinosa's avatar
Arturo Espinosa committed
943
	cursor_set (window, GNUMERIC_CURSOR_FAT_CROSS);
Arturo Espinosa's avatar
Arturo Espinosa committed
944 945
}

946 947
void
gnumeric_sheet_compute_visible_ranges (GnumericSheet *gsheet)
948
{
949
	GnomeCanvas   *canvas = GNOME_CANVAS (gsheet);
950
	int pixels, col, row, width, height;
Arturo Espinosa's avatar
Arturo Espinosa committed
951 952 953 954

	/* Find out the last visible col and the last full visible column */
	pixels = 0;
	col = gsheet->top_col;
955 956
	width = GTK_WIDGET (canvas)->allocation.width;
	
Arturo Espinosa's avatar
Arturo Espinosa committed
957 958 959 960
	do {
		ColRowInfo *ci;
		int cb;
		
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
961
		ci = sheet_col_get_info (gsheet->sheet_view->sheet, col);
Arturo Espinosa's avatar
Arturo Espinosa committed
962 963
		cb = pixels + ci->pixels;
		
964
		if (cb == width){
Arturo Espinosa's avatar
Arturo Espinosa committed
965 966
			gsheet->last_visible_col = col;
			gsheet->last_full_col = col;
967
		} if (cb > width){
Arturo Espinosa's avatar
Arturo Espinosa committed
968 969 970 971 972 973 974 975
			gsheet->last_visible_col = col;
			if (col == gsheet->top_col)
				gsheet->last_full_col = gsheet->top_col;
			else
				gsheet->last_full_col = col - 1;
		}
		pixels = cb;
		col++;
976
	} while (pixels < width);
Arturo Espinosa's avatar
Arturo Espinosa committed
977 978 979 980

	/* Find out the last visible row and the last fully visible row */
	pixels = 0;
	row = gsheet->top_row;
981
	height = GTK_WIDGET (canvas)->allocation.height;
Arturo Espinosa's avatar
Arturo Espinosa committed
982 983 984 985
	do {
		ColRowInfo *ri;
		int cb;
		
Arturo Espinosa's avatar
Today:  
Arturo Espinosa committed
986
		ri = sheet_row_get_info (gsheet->sheet_view->sheet, row);
Arturo Espinosa's avatar
Arturo Espinosa committed
987 988
		cb = pixels + ri->pixels;
		
989
		if (cb == height){
Arturo Espinosa's avatar
Arturo Espinosa committed
990 991
			gsheet->last_visible_row = row;
			gsheet->last_full_row = row;
992
		} if (cb > height){
Arturo Espinosa's avatar
Arturo Espinosa committed
993
			gsheet->last_visible_row = row;
994
			if (row == gsheet->top_row)
Arturo Espinosa's avatar
Arturo Espinosa committed
995 996 997 998 999 1000
				gsheet->last_full_row = gsheet->top_row;
			else
				gsheet->last_full_row = row - 1;
		}
		pixels = cb;
		row++;
1001
	} while (pixels < height);
1002
}
Arturo Espinosa's avatar
Arturo Espinosa committed
1003

1004 1005
static int
gnumeric_sheet_bar_set_top_row (GnumericSheet *gsheet, int new_top_row)
1006
{
Miguel de Icaza's avatar