/* * Sheet.c: Implements the sheet management and per-sheet storage * * Author: * Miguel de Icaza (miguel@gnu.org) * */ #include #include #include #include "gnumeric.h" #include "gnumeric-sheet.h" #include "utils.h" #include "gnumeric-util.h" #include "eval.h" #include "number-match.h" #include "format.h" #define GNUMERIC_SHEET_VIEW(p) GNUMERIC_SHEET (SHEET_VIEW(p)->sheet_view); /* Used to locate cells in a sheet */ typedef struct { int col; int row; } CellPos; void sheet_redraw_all (Sheet *sheet) { GList *l; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_redraw_all (sheet_view); } } static void sheet_redraw_cols (Sheet *sheet) { GList *l; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_redraw_columns (sheet_view); } } static void sheet_redraw_rows (Sheet *sheet) { GList *l; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_redraw_rows (sheet_view); } } static void sheet_init_default_styles (Sheet *sheet) { /* The default column style */ sheet->default_col_style.pos = -1; sheet->default_col_style.units = 80; sheet->default_col_style.pixels = 0; sheet->default_col_style.margin_a = 1; sheet->default_col_style.margin_b = 1; sheet->default_col_style.data = NULL; /* The default row style */ sheet->default_row_style.pos = -1; sheet->default_row_style.units = 18; sheet->default_row_style.pixels = 0; sheet->default_row_style.margin_a = 1; sheet->default_row_style.margin_b = 1; sheet->default_row_style.data = NULL; } /* Initialize some of the columns and rows, to test the display engine */ static void sheet_init_dummy_stuff (Sheet *sheet) { ColRowInfo *cp, *rp; int x, y; for (x = 0; x < 40; x += 2){ cp = sheet_row_new (sheet); cp->pos = x; cp->units = (x+1) * 30; sheet_col_add (sheet, cp); } for (y = 0; y < 6; y += 2){ rp = sheet_row_new (sheet); rp->pos = y; rp->units = (20 * (y + 1)); sheet_row_add (sheet, rp); } } static guint cell_hash (gconstpointer key) { CellPos *ca = (CellPos *) key; return (ca->row << 8) | ca->col; } static gint cell_compare (gconstpointer a, gconstpointer b) { CellPos *ca, *cb; ca = (CellPos *) a; cb = (CellPos *) b; if (ca->row != cb->row) return 0; if (ca->col != cb->col) return 0; return 1; } void sheet_rename (Sheet *sheet, const char *new_name) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (new_name != NULL); g_free (sheet->name); sheet->name = g_strdup (new_name); } Sheet * sheet_new (Workbook *wb, char *name) { GtkWidget *sheet_view; Sheet *sheet; Style *sheet_style; g_return_val_if_fail (wb != NULL, NULL); g_return_val_if_fail (name != NULL, NULL); sheet = g_new0 (Sheet, 1); sheet->signature = SHEET_SIGNATURE; sheet->workbook = wb; sheet->name = g_strdup (name); sheet->last_zoom_factor_used = -1.0; sheet->max_col_used = 0; sheet->max_row_used = 0; sheet->cell_hash = g_hash_table_new (cell_hash, cell_compare); sheet_style = style_new (); sheet_style_attach (sheet, 0, 0, SHEET_MAX_COLS-1, SHEET_MAX_ROWS-1, sheet_style); sheet_init_default_styles (sheet); /* Dummy initialization */ if (0) sheet_init_dummy_stuff (sheet); sheet_view = sheet_view_new (sheet); gtk_object_ref (GTK_OBJECT (sheet_view)); sheet->sheet_views = g_list_prepend (sheet->sheet_views, sheet_view); sheet_selection_append (sheet, 0, 0); gtk_widget_show (sheet_view); sheet_set_zoom_factor (sheet, 1.0); return sheet; } static void cell_hash_free_key (gpointer key, gpointer value, gpointer user_data) { g_free (key); } void sheet_foreach_col (Sheet *sheet, sheet_col_row_callback callback, void *user_data) { GList *l = sheet->cols_info; /* Invoke the callback for the default style */ (*callback)(sheet, &sheet->default_col_style, user_data); /* And then for the rest */ while (l){ (*callback)(sheet, l->data, user_data); l = l->next; } } void sheet_foreach_row (Sheet *sheet, sheet_col_row_callback callback, void *user_data) { GList *l = sheet->rows_info; /* Invoke the callback for the default style */ (callback)(sheet, &sheet->default_row_style, user_data); /* And then for the rest */ while (l){ (*callback)(sheet, l->data, user_data); l = l->next; } } static void sheet_compute_col_row_new_size (Sheet *sheet, ColRowInfo *ci, void *data) { double pix_per_unit = sheet->last_zoom_factor_used; ci->pixels = (ci->units * pix_per_unit) + ci->margin_a + ci->margin_b + 1; } void sheet_set_zoom_factor (Sheet *sheet, double factor) { GList *l, *cl; sheet->last_zoom_factor_used = factor; /* First, the default styles */ sheet_compute_col_row_new_size (sheet, &sheet->default_row_style, NULL); sheet_compute_col_row_new_size (sheet, &sheet->default_col_style, NULL); /* Then every column and row */ sheet_foreach_col (sheet, sheet_compute_col_row_new_size, NULL); sheet_foreach_row (sheet, sheet_compute_col_row_new_size, NULL); for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_set_zoom_factor (sheet_view, factor); } for (cl = sheet->comment_list; cl; cl = cl->next){ Cell *cell = cl->data; cell_comment_reposition (cell); } } /* * Duplicates a column or row */ ColRowInfo * sheet_duplicate_colrow (ColRowInfo *original) { ColRowInfo *info = g_new (ColRowInfo, 1); *info = *original; return info; } ColRowInfo * sheet_row_new (Sheet *sheet) { ColRowInfo *ri; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); ri = sheet_duplicate_colrow (&sheet->default_row_style); row_init_span (ri); return ri; } ColRowInfo * sheet_col_new (Sheet *sheet) { ColRowInfo *ci; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); ci = sheet_duplicate_colrow (&sheet->default_col_style); ci->data = NULL; return ci; } static gint CRsort (gconstpointer a, gconstpointer b) { ColRowInfo *ia = (ColRowInfo *) a; ColRowInfo *ib = (ColRowInfo *) b; return (ia->pos - ib->pos); } void sheet_col_add (Sheet *sheet, ColRowInfo *cp) { if (cp->pos > sheet->max_col_used){ GList *l; sheet->max_col_used = cp->pos; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; GtkAdjustment *ha = GTK_ADJUSTMENT (sheet_view->ha); if (sheet->max_col_used > ha->upper){ ha->upper = sheet->max_col_used; gtk_adjustment_changed (ha); } } } sheet->cols_info = g_list_insert_sorted (sheet->cols_info, cp, CRsort); } void sheet_row_add (Sheet *sheet, ColRowInfo *rp) { if (rp->pos > sheet->max_row_used){ GList *l; sheet->max_row_used = rp->pos; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; GtkAdjustment *va = GTK_ADJUSTMENT (sheet_view->va); if (sheet->max_row_used > va->upper){ va->upper = sheet->max_row_used; gtk_adjustment_changed (va); } } } sheet->rows_info = g_list_insert_sorted (sheet->rows_info, rp, CRsort); } ColRowInfo * sheet_col_get_info (Sheet *sheet, int col) { GList *l = sheet->cols_info; for (; l; l = l->next){ ColRowInfo *ci = l->data; if (ci->pos == col) return ci; } return &sheet->default_col_style; } ColRowInfo * sheet_row_get_info (Sheet *sheet, int row) { GList *l = sheet->rows_info; for (; l; l = l->next){ ColRowInfo *ri = l->data; if (ri->pos == row) return ri; } return &sheet->default_row_style; } void sheet_compute_visible_ranges (Sheet *sheet) { GList *l; for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_compute_visible_ranges (gsheet); } } static void colrow_set_units (Sheet *sheet, ColRowInfo *info) { double pix = sheet->last_zoom_factor_used; info->units = (info->pixels - (info->margin_a + info->margin_b + 1)) / pix; } static void sheet_reposition_comments (Sheet *sheet, int row) { GList *l; /* Move any cell comments */ for (l = sheet->comment_list; l; l = l->next){ Cell *cell = l->data; if (cell->row->pos >= row) cell_comment_reposition (cell); } } void sheet_row_info_set_height (Sheet *sheet, ColRowInfo *ri, int height, gboolean height_set_by_user) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (ri != NULL); if (height_set_by_user) ri->hard_size = 1; ri->pixels = height; colrow_set_units (sheet, ri); sheet_compute_visible_ranges (sheet); sheet_reposition_comments (sheet, ri->pos); sheet_redraw_all (sheet); } /** * sheet_row_set_height: * @sheet: The sheet * @row: The row * @height: The desired height * @height_set_by_user: TRUE if this was done by a user (ie, user manually * set the width) * * Sets the height of a row in terms of the total visible space (as opossite * to the internal required space, which does not include the margins). */ void sheet_row_set_height (Sheet *sheet, int row, int height, gboolean height_set_by_user) { ColRowInfo *ri; int add = 0; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); ri = sheet_row_get_info (sheet, row); if (ri == &sheet->default_row_style){ ri = sheet_duplicate_colrow (ri); ri->pos = row; add = 1; } sheet_row_info_set_height (sheet, ri, height, height_set_by_user); if (add) sheet_row_add (sheet, ri); } /** * sheet_row_set_internal_height: * @sheet: The sheet * @row: The row * @height: The desired height * * Sets the height of a row in terms of the internal required space (the total * size of the row will include the margins. */ void sheet_row_set_internal_height (Sheet *sheet, ColRowInfo *ri, int height) { double pix; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (ri != NULL); pix = sheet->last_zoom_factor_used; if (ri->units == height) return; ri->units = height; ri->pixels = (ri->units * pix) + (ri->margin_a + ri->margin_b - 1); sheet_compute_visible_ranges (sheet); sheet_reposition_comments (sheet, ri->pos); sheet_redraw_all (sheet); } /** * sheet_recompute_spans_for_col: * @sheet: the sheet * @col: The column that changed * * This routine recomputes the column span for the cells that touches * the column. */ static void sheet_recompute_spans_for_col (Sheet *sheet, int col) { GList *l, *cells; Cell *cell; cells = NULL; for (l = sheet->rows_info; l; l = l->next){ ColRowInfo *ri = l->data; if (!(cell = sheet_cell_get (sheet, col, ri->pos))) cell = row_cell_get_displayed_at (ri, col); if (cell) cells = g_list_prepend (cells, cell); } /* No spans, just return */ if (!cells) return; /* Unregister those cells that touched this column */ for (l = cells; l; l = l->next){ int left, right; cell = l->data; cell_unregister_span (cell); cell_get_span (cell, &left, &right); if (left != right) cell_register_span (cell, left, right); } g_list_free (cells); } void sheet_col_info_set_width (Sheet *sheet, ColRowInfo *ci, int width) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (ci != NULL); ci->pixels = width; colrow_set_units (sheet, ci); sheet_compute_visible_ranges (sheet); sheet_redraw_all (sheet); } void sheet_col_set_width (Sheet *sheet, int col, int width) { ColRowInfo *ci; GList *l; int add = 0; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); ci = sheet_col_get_info (sheet, col); if (ci == &sheet->default_col_style){ ci->pos = col; ci = sheet_duplicate_colrow (ci); add = 1; } sheet_col_info_set_width (sheet, ci, width); if (add) sheet_col_add (sheet, ci); /* Compute the spans */ sheet_recompute_spans_for_col (sheet, col); /* Move any cell comments */ for (l = sheet->comment_list; l; l = l->next){ Cell *cell = l->data; if (cell->col->pos >= col) cell_comment_reposition (cell); } } static inline int col_row_distance (GList *list, int from, int to, int default_pixels) { ColRowInfo *cri; int pixels = 0, n = 0; GList *l; if (to == from) return 0; n = to - from; for (l = list; l; l = l->next){ cri = l->data; if (cri->pos >= to) break; if (cri->pos >= from){ n--; pixels += cri->pixels; } } pixels += n * default_pixels; return pixels; } /** * sheet_col_get_distance: * * Return the number of pixels between from_col to to_col */ int sheet_col_get_distance (Sheet *sheet, int from_col, int to_col) { g_assert (from_col <= to_col); g_assert (sheet != NULL); return col_row_distance (sheet->cols_info, from_col, to_col, sheet->default_col_style.pixels); } /** * sheet_row_get_distance: * * Return the number of pixels between from_row to to_row */ int sheet_row_get_distance (Sheet *sheet, int from_row, int to_row) { g_assert (from_row <= to_row); g_assert (sheet != NULL); return col_row_distance (sheet->rows_info, from_row, to_row, sheet->default_row_style.pixels); } int sheet_selection_equal (SheetSelection *a, SheetSelection *b) { if (a->start_col != b->start_col) return 0; if (a->start_row != b->start_row) return 0; if (a->end_col != b->end_col) return 0; if (a->end_row != b->end_row) return 0; return 1; } void sheet_update_auto_expr (Sheet *sheet) { Workbook *wb = sheet->workbook; Value *v; char *error; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); /* defaults */ v = NULL; error = "ERROR"; if (wb->auto_expr) v = eval_expr (sheet, wb->auto_expr, 0, 0, &error); if (v){ char *s; s = value_string (v); workbook_auto_expr_label_set (wb, s); g_free (s); value_release (v); } else workbook_auto_expr_label_set (wb, error); } static char * sheet_get_selection_name (Sheet *sheet) { SheetSelection *ss = sheet->selections->data; static char buffer [40]; if (ss->start_col == ss->end_col && ss->start_row == ss->end_row){ return cell_name (ss->start_col, ss->start_row); } else { snprintf (buffer, sizeof (buffer), "%dLx%dC", ss->end_row - ss->start_row + 1, ss->end_col - ss->start_col + 1); return buffer; } } void sheet_set_text (Sheet *sheet, int col, int row, char *str) { GList *l; Cell *cell; double v; char *format, *text; int text_set = FALSE; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); cell = sheet_cell_get (sheet, col, row); if (!cell) cell = sheet_cell_new (sheet, col, row); text = gtk_entry_get_text (GTK_ENTRY (sheet->workbook->ea_input)); if (*text == '@'){ char *new_text = g_strdup (text); *new_text = '='; gtk_entry_set_text (GTK_ENTRY (sheet->workbook->ea_input), new_text); g_free (new_text); } /* * Figure out if a format matches, and for sanity compare that to * a rendered version of the text, if they compare equally, then * use that. */ if (!CELL_IS_FORMAT_SET (cell) && (*text != '=' && format_match (text, &v, &format))){ StyleFormat *sf; char *new_text; char buffer [50]; Value *vf = value_float (v); /* Render it */ sf = style_format_new (format); new_text = format_value (sf, vf, NULL); value_release (vf); style_format_unref (sf); /* Compare it */ if (strcasecmp (new_text, text) == 0){ cell_set_format_simple (cell, format); sprintf (buffer, "%g", v); cell_set_text (cell, buffer); text_set = TRUE; } g_free (new_text); } if (!text_set) cell_set_text (cell, text); for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_destroy_editing_cursor (gsheet); } workbook_recalc (sheet->workbook); } void sheet_set_current_value (Sheet *sheet) { char *str; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); str = gtk_entry_get_text (GTK_ENTRY (sheet->workbook->ea_input)); sheet_set_text (sheet, sheet->cursor_col, sheet->cursor_row, str); } static void sheet_stop_editing (Sheet *sheet) { sheet->editing = FALSE; if (sheet->editing_saved_text){ string_unref (sheet->editing_saved_text); sheet->editing_saved_text = NULL; sheet->editing_cell = NULL; } } void sheet_accept_pending_input (Sheet *sheet) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); if (!sheet->editing) return; sheet_set_current_value (sheet); for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_stop_editing (gsheet); } sheet_stop_editing (sheet); } void sheet_cancel_pending_input (Sheet *sheet) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); if (!sheet->editing) return; if (sheet->editing_cell){ cell_set_text (sheet->editing_cell, sheet->editing_saved_text->str); sheet_stop_editing (sheet); } for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_destroy_editing_cursor (gsheet); } } void sheet_load_cell_val (Sheet *sheet) { GtkEntry *entry; Cell *cell; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); entry = GTK_ENTRY (sheet->workbook->ea_input); cell = sheet_cell_get (sheet, sheet->cursor_col, sheet->cursor_row); if (cell){ char *text; text = cell_get_text (cell); gtk_entry_set_text (entry, text); g_free (text); } else gtk_entry_set_text (entry, ""); } void sheet_start_editing_at_cursor (Sheet *sheet) { GList *l; Cell *cell; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); gtk_entry_set_text (GTK_ENTRY (sheet->workbook->ea_input), ""); for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_create_editing_cursor (gsheet); } sheet->editing = TRUE; cell = sheet_cell_get (sheet, sheet->cursor_col, sheet->cursor_row); if (cell){ char *text; text = cell_get_text (cell); sheet->editing_saved_text = string_get (text); g_free (text); sheet->editing_cell = cell; cell_set_text (cell, ""); } } /** * sheet_update_controls: * * This routine is ran every time the seleciton has changed. It checks * what the status of various toolbar feedback controls should be */ static void sheet_update_controls (Sheet *sheet) { GList *cells, *l; int bold_first, italic_first; int bold_common, italic_common; cells = sheet_selection_to_list (sheet); if (cells){ Cell *cell = cells->data; bold_first = cell->style->font->hint_is_bold; italic_first = cell->style->font->hint_is_italic; l = cells->next; } else { /* * If no cells are on the selection, use the first cell * in the range to compute the values */ SheetSelection *ss = sheet->selections->data; Style *style; style = sheet_style_compute (sheet, ss->start_col, ss->start_row, NULL); bold_first = style->font->hint_is_bold; italic_first = style->font->hint_is_italic; style_destroy (style); /* Initialize the pointer that is going to be used next */ l = cells; } bold_common = italic_common = TRUE; /* Check every cell on the range */ for (; l; l = l->next){ Cell *cell = l->data; if (italic_first != cell->style->font->hint_is_italic) italic_common = FALSE; if (bold_first != cell->style->font->hint_is_bold) bold_common = FALSE; if (bold_common == FALSE && italic_common == FALSE) break; } g_list_free (cells); /* Update the toolbar */ if (bold_common) workbook_feedback_set ( sheet->workbook, WORKBOOK_FEEDBACK_BOLD, GINT_TO_POINTER(bold_first)); if (italic_common) workbook_feedback_set ( sheet->workbook, WORKBOOK_FEEDBACK_ITALIC, GINT_TO_POINTER(italic_first)); } static void sheet_selection_changed_hook (Sheet *sheet) { sheet_update_auto_expr (sheet); sheet_update_controls (sheet); workbook_set_region_status (sheet->workbook, sheet_get_selection_name (sheet)); } void sheet_selection_append_range (Sheet *sheet, int base_col, int base_row, int start_col, int start_row, int end_col, int end_row) { SheetSelection *ss; GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); ss = g_new0 (SheetSelection, 1); ss->base_col = base_col; ss->base_row = base_row; ss->start_col = start_col; ss->end_col = end_col; ss->start_row = start_row; ss->end_row = end_row; sheet->selections = g_list_prepend (sheet->selections, ss); sheet_accept_pending_input (sheet); sheet_load_cell_val (sheet); for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_set_selection (gsheet, ss); } sheet_redraw_selection (sheet, ss); sheet_redraw_cols (sheet); sheet_redraw_rows (sheet); sheet_selection_changed_hook (sheet); } void sheet_selection_append (Sheet *sheet, int col, int row) { sheet_selection_append_range (sheet, col, row, col, row, col, row); } /** * sheet_selection_extend_to: * @sheet: the sheet * @col: column that gets covered * @row: row that gets covered * * This extends the selection to cover col, row */ void sheet_selection_extend_to (Sheet *sheet, int col, int row) { SheetSelection *ss, old_selection; GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_assert (sheet->selections); ss = (SheetSelection *) sheet->selections->data; old_selection = *ss; if (col < ss->base_col){ ss->start_col = col; ss->end_col = ss->base_col; } else { ss->start_col = ss->base_col; ss->end_col = col; } if (row < ss->base_row){ ss->end_row = ss->base_row; ss->start_row = row; } else { ss->end_row = row; ss->start_row = ss->base_row; } for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_set_selection (gsheet, ss); } sheet_selection_changed_hook (sheet); sheet_redraw_selection (sheet, &old_selection); sheet_redraw_selection (sheet, ss); if (ss->start_col != old_selection.start_col || ss->end_col != old_selection.end_col || ((ss->start_row == 0 && ss->end_row == SHEET_MAX_ROWS-1) ^ (old_selection.start_row == 0 && old_selection.end_row == SHEET_MAX_ROWS-1))) sheet_redraw_cols (sheet); if (ss->start_row != old_selection.start_row || ss->end_row != old_selection.end_row || ((ss->start_col == 0 && ss->end_col == SHEET_MAX_COLS-1) ^ (old_selection.start_col == 0 && old_selection.end_col == SHEET_MAX_COLS-1))) sheet_redraw_rows (sheet); } /** * sheet_select_all: * Sheet: The sheet * * Selects all of the cells in the sheet */ void sheet_select_all (Sheet *sheet) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); sheet_selection_reset_only (sheet); sheet_make_cell_visible (sheet, 0, 0); sheet_cursor_move (sheet, 0, 0); sheet_selection_append_range (sheet, 0, 0, 0, 0, SHEET_MAX_COLS-1, SHEET_MAX_ROWS-1); /* Queue redraws for columns and rows */ sheet_redraw_rows (sheet); sheet_redraw_cols (sheet); } int sheet_is_all_selected (Sheet *sheet) { SheetSelection *ss; GList *l; g_return_val_if_fail (sheet != NULL, FALSE); g_return_val_if_fail (IS_SHEET (sheet), FALSE); for (l = sheet->selections; l != NULL; l = l->next){ ss = l->data; if (ss->start_col == 0 && ss->start_row == 0 && ss->end_col == SHEET_MAX_COLS-1 && ss->end_row == SHEET_MAX_ROWS-1) return TRUE; } return FALSE; } int sheet_col_selection_type (Sheet *sheet, int col) { SheetSelection *ss; GList *l; int ret = ITEM_BAR_NO_SELECTION; g_return_val_if_fail (sheet != NULL, FALSE); g_return_val_if_fail (IS_SHEET (sheet), FALSE); if (sheet->selections == NULL){ if (col == sheet->cursor_col) return ITEM_BAR_PARTIAL_SELECTION; return ret; } for (l = sheet->selections; l != NULL; l = l->next){ ss = l->data; if (ss->start_col > col || ss->end_col < col) continue; if (ss->start_row == 0 && ss->end_row == SHEET_MAX_ROWS-1) return ITEM_BAR_FULL_SELECTION; ret = ITEM_BAR_PARTIAL_SELECTION; } return ret; } int sheet_row_selection_type (Sheet *sheet, int row) { SheetSelection *ss; GList *l; int ret = ITEM_BAR_NO_SELECTION; g_return_val_if_fail (sheet != NULL, FALSE); g_return_val_if_fail (IS_SHEET (sheet), FALSE); if (sheet->selections == NULL){ if (row == sheet->cursor_row) return ITEM_BAR_PARTIAL_SELECTION; return ret; } for (l = sheet->selections; l != NULL; l = l->next){ ss = l->data; if (ss->start_row > row || ss->end_row < row) continue; if (ss->start_col == 0 && ss->end_col == SHEET_MAX_COLS-1) return ITEM_BAR_FULL_SELECTION; ret = ITEM_BAR_PARTIAL_SELECTION; } return ret; } /* * This routine is used to queue the redraw regions for the * cell region specified. * * It is usually called before a change happens to a region, * and after the change has been done to queue the regions * for the old contents and the new contents. */ void sheet_redraw_cell_region (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_redraw_cell_region ( sheet_view, start_col, start_row, end_col, end_row); } } void sheet_redraw_selection (Sheet *sheet, SheetSelection *ss) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (ss != NULL); sheet_redraw_cell_region (sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); } int sheet_row_check_bound (int row, int diff) { int new_val = row + diff; if (new_val < 0) return 0; if (new_val >= SHEET_MAX_ROWS) return SHEET_MAX_ROWS - 1; return new_val; } int sheet_col_check_bound (int col, int diff) { int new_val = col + diff; if (new_val < 0) return 0; if (new_val >= SHEET_MAX_COLS) return SHEET_MAX_COLS - 1; return new_val; } static void sheet_selection_change (Sheet *sheet, SheetSelection *old, SheetSelection *new) { GList *l; if (sheet_selection_equal (old, new)) return; sheet_accept_pending_input (sheet); sheet_redraw_selection (sheet, old); sheet_redraw_selection (sheet, new); sheet_selection_changed_hook (sheet); for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_set_selection (gsheet, new); } if (new->start_col != old->start_col || new->end_col != old->end_col || ((new->start_row == 0 && new->end_row == SHEET_MAX_ROWS-1) ^ (old->start_row == 0 && old->end_row == SHEET_MAX_ROWS-1))) sheet_redraw_cols (sheet); if (new->start_row != old->start_row || new->end_row != old->end_row || ((new->start_col == 0 && new->end_col == SHEET_MAX_COLS-1) ^ (old->start_col == 0 && old->end_col == SHEET_MAX_COLS-1))) sheet_redraw_rows (sheet); } /** * sheet_selection_extend_horizontal: * * @sheet: The Sheet * * @count: units to extend the selection horizontally */ void sheet_selection_extend_horizontal (Sheet *sheet, int n) { SheetSelection *ss; SheetSelection old_selection; /* FIXME: right now we only support units (1 or -1) * to fix this we need to account for the fact that * the selection boundary might change and adjust * appropiately */ g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail ((n == 1 || n == -1)); ss = (SheetSelection *)sheet->selections->data; old_selection = *ss; if (ss->base_col < ss->end_col) ss->end_col = sheet_col_check_bound (ss->end_col, n); else if (ss->base_col > ss->start_col) ss->start_col = sheet_col_check_bound (ss->start_col, n); else { if (n > 0) ss->end_col = sheet_col_check_bound (ss->end_col, 1); else ss->start_col = sheet_col_check_bound (ss->start_col, -1); } sheet_selection_change (sheet, &old_selection, ss); } /* * sheet_selection_extend_vertical * @sheet: The Sheet * * @n: units to extend the selection vertically */ void sheet_selection_extend_vertical (Sheet *sheet, int n) { SheetSelection *ss; SheetSelection old_selection; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail ((n == 1 || n == -1)); ss = (SheetSelection *)sheet->selections->data; old_selection = *ss; if (ss->base_row < ss->end_row) ss->end_row = sheet_row_check_bound (ss->end_row, n); else if (ss->base_row > ss->start_row) ss->start_row = sheet_row_check_bound (ss->start_row, n); else { if (n > 0) ss->end_row = sheet_row_check_bound (ss->end_row, 1); else ss->start_row = sheet_row_check_bound (ss->start_row, -1); } sheet_selection_change (sheet, &old_selection, ss); } void sheet_selection_set (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { SheetSelection *ss; SheetSelection old_selection; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); ss = (SheetSelection *)sheet->selections->data; old_selection = *ss; ss->start_row = start_row; ss->end_row = end_row; ss->start_col = start_col; ss->end_col = end_col; sheet_selection_change (sheet, &old_selection, ss); } /** * sheet_selections_free * @sheet: the sheet * * Releases the selection associated with this sheet */ static void sheet_selections_free (Sheet *sheet) { g_list_free (sheet->selections); sheet->selections = NULL; } /* * sheet_selection_reset * sheet: The sheet * * Clears all of the selection ranges. * Warning: This does not set a new selection, this should * be taken care on the calling routine. */ void sheet_selection_reset_only (Sheet *sheet) { GList *list = sheet->selections; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (list = sheet->selections; list; list = list->next){ SheetSelection *ss = list->data; sheet_redraw_selection (sheet, ss); g_free (ss); } sheet_selections_free (sheet); sheet->walk_info.current = NULL; /* Redraw column bar */ sheet_redraw_cols (sheet); /* Redraw the row bar */ sheet_redraw_rows (sheet); } /** * sheet_selection_reset: * sheet: The sheet * * Clears all of the selection ranges and resets it to a * selection that only covers the cursor */ void sheet_selection_reset (Sheet *sheet) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); sheet_selection_reset_only (sheet); sheet_selection_append (sheet, sheet->cursor_col, sheet->cursor_row); } int sheet_selection_is_cell_selected (Sheet *sheet, int col, int row) { GList *list = sheet->selections; for (list = sheet->selections; list; list = list->next){ SheetSelection *ss = list->data; if ((ss->start_col <= col) && (col <= ss->end_col) && (ss->start_row <= row) && (row <= ss->end_row)){ return 1; } } return 0; } /* * walk_boundaries: implements the decitions for walking a region * returns TRUE if the cursor left the boundary region */ static int walk_boundaries (int lower_col, int lower_row, int upper_col, int upper_row, int inc_x, int inc_y, int current_col, int current_row, int *new_col, int *new_row) { if (current_row + inc_y > upper_row || current_col + inc_x > upper_col){ *new_row = current_row; *new_col = current_col; return TRUE; } else { if (current_row + inc_y < lower_row || current_col + inc_x < lower_col){ *new_row = current_row; *new_col = current_col; return TRUE; } else { *new_row = current_row + inc_y; *new_col = current_col + inc_x; } } return FALSE; } /* * walk_boundaries: implements the decitions for walking a region * returns TRUE if the cursor left the boundary region. This * version implements wrapping on the regions. */ static int walk_boundaries_wrapped (int lower_col, int lower_row, int upper_col, int upper_row, int inc_x, int inc_y, int current_col, int current_row, int *new_col, int *new_row) { if (current_row + inc_y > upper_row){ if (current_col + 1 > upper_col) goto overflow; *new_row = lower_row; *new_col = current_col + 1; return FALSE; } if (current_row + inc_y < lower_row){ if (current_col - 1 < lower_col) goto overflow; *new_row = upper_row; *new_col = current_col - 1; return FALSE; } if (current_col + inc_x > upper_col){ if (current_row + 1 > upper_row) goto overflow; *new_row = current_row + 1; *new_col = lower_col; return FALSE; } if (current_col + inc_x < lower_col){ if (current_row - 1 < lower_row) goto overflow; *new_row = current_row - 1; *new_col = upper_col; return FALSE; } *new_row = current_row + inc_y; *new_col = current_col + inc_x; return FALSE; overflow: *new_row = current_row; *new_col = current_col; return TRUE; } int sheet_selection_walk_step (Sheet *sheet, int forward, int horizontal, int current_col, int current_row, int *new_col, int *new_row) { SheetSelection *ss; int inc_x = 0, inc_y = 0; int selections_count, diff, overflow;; diff = forward ? 1 : -1; if (horizontal) inc_x = diff; else inc_y = diff; selections_count = g_list_length (sheet->selections); if (selections_count == 1){ ss = sheet->selections->data; /* If there is no selection besides the cursor, plain movement */ if (ss->start_col == ss->end_col && ss->start_row == ss->end_row){ walk_boundaries (0, 0, SHEET_MAX_COLS, SHEET_MAX_ROWS, inc_x, inc_y, current_col, current_row, new_col, new_row); return FALSE; } } if (!sheet->walk_info.current) sheet->walk_info.current = sheet->selections->data; ss = sheet->walk_info.current; overflow = walk_boundaries_wrapped ( ss->start_col, ss->start_row, ss->end_col, ss->end_row, inc_x, inc_y, current_col, current_row, new_col, new_row); if (overflow){ int p; p = g_list_index (sheet->selections, ss); p += diff; if (p < 0) p = selections_count - 1; else if (p == selections_count) p = 0; ss = g_list_nth (sheet->selections, p)->data; sheet->walk_info.current = ss; if (forward){ *new_col = ss->start_col; *new_row = ss->start_row; } else { *new_col = ss->end_col; *new_row = ss->end_row; } } return TRUE; } /* * assemble_cell_list: A callback for sheet_cell_foreach_range * intented to assemble a list of cells in a region. * * The closure parameter should be a pointer to a GList. */ static int assemble_cell_list (Sheet *sheet, int col, int row, Cell *cell, void *user_data) { GList **l = (GList **) user_data; *l = g_list_prepend (*l, cell); return TRUE; } CellList * sheet_selection_to_list (Sheet *sheet) { GList *selections; CellList *list; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); g_return_val_if_fail (sheet->selections, NULL); list = NULL; for (selections = sheet->selections; selections; selections = selections->next){ SheetSelection *ss = selections->data; sheet_cell_foreach_range ( sheet, TRUE, ss->start_col, ss->start_row, ss->end_col, ss->end_row, assemble_cell_list, &list); } return list; } /** * sheet_col_get: * * Returns an allocated column: either an existing one, or a fresh copy */ ColRowInfo * sheet_col_get (Sheet *sheet, int pos) { GList *clist; ColRowInfo *col; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); for (clist = sheet->cols_info; clist; clist = clist->next){ col = (ColRowInfo *) clist->data; if (col->pos == pos) return col; } col = sheet_col_new (sheet); col->pos = pos; sheet_col_add (sheet, col); return col; } /** * sheet_row_get: * * Returns an allocated row: either an existing one, or a fresh copy */ ColRowInfo * sheet_row_get (Sheet *sheet, int pos) { GList *rlist; ColRowInfo *row; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); for (rlist = sheet->rows_info; rlist; rlist = rlist->next){ row = (ColRowInfo *) rlist->data; if (row->pos == pos) return row; } row = sheet_row_new (sheet); row->pos = pos; sheet_row_add (sheet, row); return row; } static int gen_row_blanks (Sheet *sheet, int col, int start_row, int end_row, sheet_cell_foreach_callback callback, void *closure) { int row; for (row = 0; row < end_row; row++) if (!(*callback)(sheet, col, row, NULL, closure)) return FALSE; return TRUE; } static int gen_col_blanks (Sheet *sheet, int start_col, int end_col, int start_row, int end_row, sheet_cell_foreach_callback callback, void *closure) { int col; for (col = 0; col < end_col; col++) if (!gen_row_blanks (sheet, col, start_row, end_row, callback, closure)) return FALSE; return TRUE; } /** * sheet_cell_get: * @sheet: The sheet where we want to locate the cell * @col: the cell column * @row: the cell row * * Return value: a (Cell *) containing the Cell, or NULL if * the cell does not exist */ Cell * sheet_cell_get (Sheet *sheet, int col, int row) { Cell *cell; CellPos cellpos; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); cellpos.col = col; cellpos.row = row; cell = g_hash_table_lookup (sheet->cell_hash, &cellpos); return cell; } /** * sheet_cell_fetch: * @sheet: The sheet where we want to locate the cell * @col: the cell column * @row: the cell row * * Return value: a (Cell *) containing the Cell at col, row. * If no cell existed at that location before, it is created. */ Cell * sheet_cell_fetch (Sheet *sheet, int col, int row) { Cell *cell; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); cell = sheet_cell_get (sheet, col, row); if (!cell) cell = sheet_cell_new (sheet, col, row); return cell; } /** * sheet_cell_foreach_range: * * For each existing cell in the range specified, invoke the * callback routine. If the only_existing flag is passed, then * callbacks are only invoked for existing cells. * * Return value: * FALSE if some invoked routine requested to stop (by returning FALSE). */ int sheet_cell_foreach_range (Sheet *sheet, int only_existing, int start_col, int start_row, int end_col, int end_row, sheet_cell_foreach_callback callback, void *closure) { GList *col; GList *row; int last_col_gen = -1, last_row_gen = -1; int cont; g_return_val_if_fail (sheet != NULL, FALSE); g_return_val_if_fail (IS_SHEET (sheet), FALSE); g_return_val_if_fail (callback != NULL, FALSE); col = sheet->cols_info; for (; col; col = col->next){ ColRowInfo *ci = col->data; if (ci->pos < start_col) continue; if (ci->pos > end_col) break; if (!only_existing){ if ((last_col_gen > 0) && (ci->pos != last_col_gen+1)) if (!gen_col_blanks (sheet, last_col_gen, ci->pos, start_row, end_row, callback, closure)) return FALSE; if (ci->pos > start_col) if (!gen_col_blanks (sheet, start_col, ci->pos, start_row, end_row, callback, closure)) return FALSE; } last_col_gen = ci->pos; last_row_gen = -1; for (row = (GList *) ci->data; row; row = row->next){ Cell *cell = (Cell *) row->data; int row_pos = cell->row->pos; if (row_pos < start_row) continue; if (row_pos > end_row) break; if (!only_existing){ if (last_row_gen > 0){ if (row_pos != last_row_gen+1) if (!gen_row_blanks (sheet, ci->pos, last_row_gen, row_pos, callback, closure)) return FALSE; } if (row_pos > start_row){ if (!gen_row_blanks (sheet, ci->pos, row_pos, start_row, callback, closure)) return FALSE; } } cont = (*callback)(sheet, ci->pos, row_pos, cell, closure); if (cont == FALSE) return FALSE; } } return TRUE; } static gboolean fail_if_found (Sheet *sheet, int col, int row, Cell *cell, void *user_data) { return FALSE; } /** * sheet_is_region_empty: * @sheet: sheet to check * @start_col: starting column * @start_row: starting row * @end_col: end column * @end_row: end row * * Returns TRUE if the specified region of the @sheet does not * contain any cells. * * FIXME: Perhaps this routine should be extended to allow testing for specific * features of a cell rather than just the existance of the cell. */ gboolean sheet_is_region_empty (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { g_return_val_if_fail (sheet != NULL, TRUE); g_return_val_if_fail (IS_SHEET (sheet), TRUE); g_return_val_if_fail (start_col <= end_col, TRUE); g_return_val_if_fail (start_row <= end_row, TRUE); return sheet_cell_foreach_range ( sheet, TRUE, start_col, start_row, end_col, end_row, fail_if_found, NULL); } static gint CRowSort (gconstpointer a, gconstpointer b) { Cell *ca = (Cell *) a; Cell *cb = (Cell *) b; return ca->row->pos - cb->row->pos; } /** * sheet_cell_add_to_hash: * @sheet The sheet where the cell is inserted * @cell The cell, it should already have col/pos pointers * initialized pointing to the correct ColRowInfo */ static void sheet_cell_add_to_hash (Sheet *sheet, Cell *cell) { CellPos *cellpos; Cell *cell_on_spot; int left, right; /* See if another cell was displaying in our spot */ cell_on_spot = row_cell_get_displayed_at (cell->row, cell->col->pos); if (cell_on_spot) cell_unregister_span (cell_on_spot); cellpos = g_new (CellPos, 1); cellpos->col = cell->col->pos; cellpos->row = cell->row->pos; g_hash_table_insert (sheet->cell_hash, cellpos, cell); /* * Now register the sizes of our cells */ if (cell_on_spot){ cell_get_span (cell_on_spot, &left, &right); if (left != right) cell_register_span (cell_on_spot, left, right); } cell_get_span (cell, &left, &right); if (left != right) cell_register_span (cell, left, right); } void sheet_cell_add (Sheet *sheet, Cell *cell, int col, int row) { cell->sheet = sheet; cell->col = sheet_col_get (sheet, col); cell->row = sheet_row_get (sheet, row); cell_realize (cell); if (!cell->style){ int flags; cell->style = sheet_style_compute (sheet, col, row, &flags); if (flags & STYLE_FORMAT) cell->flags |= CELL_FORMAT_SET; } cell_calc_dimensions (cell); sheet_cell_add_to_hash (sheet, cell); cell->col->data = g_list_insert_sorted (cell->col->data, cell, CRowSort); } Cell * sheet_cell_new (Sheet *sheet, int col, int row) { Cell *cell; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); cell = g_new0 (Cell, 1); sheet_cell_add (sheet, cell, col, row); return cell; } static void sheet_cell_remove_from_hash (Sheet *sheet, Cell *cell) { CellPos cellpos; void *original_key; cellpos.col = cell->col->pos; cellpos.row = cell->row->pos; cell_unregister_span (cell); g_hash_table_lookup_extended (sheet->cell_hash, &cellpos, &original_key, NULL); g_hash_table_remove (sheet->cell_hash, &cellpos); g_free (original_key); } static void sheet_cell_remove_internal (Sheet *sheet, Cell *cell) { GList *deps; if (cell->parsed_node) sheet_cell_formula_unlink (cell); deps = cell_get_dependencies (sheet, cell->col->pos, cell->row->pos); if (deps) cell_queue_recalc_list (deps); sheet_cell_remove_from_hash (sheet, cell); cell_unrealize (cell); } void sheet_cell_remove (Sheet *sheet, Cell *cell) { g_return_if_fail (sheet != NULL); g_return_if_fail (cell != NULL); g_return_if_fail (IS_SHEET (sheet)); /* Queue a redraw on the region used by the cell being removed */ sheet_redraw_cell_region (sheet, cell->col->pos, cell->row->pos, cell->col->pos, cell->row->pos); sheet_cell_remove_internal (sheet, cell); cell->col->data = g_list_remove (cell->col->data, cell); sheet_redraw_cell_region (sheet, cell->col->pos, cell->row->pos, cell->col->pos, cell->row->pos); } /** * sheet_cell_remove_to_eot: * * Removes all of the cells from CELL_LIST point on. */ static void sheet_cell_remove_to_eot (Sheet *sheet, GList *cell_list) { while (cell_list){ Cell *cell = cell_list->data; if (cell->parsed_node) sheet_cell_formula_unlink (cell); sheet_cell_remove_from_hash (sheet, cell); cell_destroy (cell); } } void sheet_cell_comment_link (Cell *cell) { Sheet *sheet; g_return_if_fail (cell != NULL); g_return_if_fail (cell->sheet != NULL); sheet = cell->sheet; sheet->comment_list = g_list_prepend (sheet->comment_list, cell); } void sheet_cell_comment_unlink (Cell *cell) { Sheet *sheet; g_return_if_fail (cell != NULL); g_return_if_fail (cell->sheet != NULL); g_return_if_fail (cell->comment != NULL); sheet = cell->sheet; sheet->comment_list = g_list_remove (sheet->comment_list, cell); } void sheet_cell_formula_link (Cell *cell) { Sheet *sheet; g_return_if_fail (cell != NULL); g_return_if_fail (cell->parsed_node != NULL); sheet = cell->sheet; sheet->workbook->formula_cell_list = g_list_prepend (sheet->workbook->formula_cell_list, cell); cell_add_dependencies (cell); } void sheet_cell_formula_unlink (Cell *cell) { Sheet *sheet; g_return_if_fail (cell != NULL); g_return_if_fail (cell->parsed_node != NULL); sheet = cell->sheet; cell_drop_dependencies (cell); sheet->workbook->formula_cell_list = g_list_remove (sheet->workbook->formula_cell_list, cell); } /** * sheet_col_destroy: * * Destroys a ColRowInfo from the Sheet with all of its cells */ static void sheet_col_destroy (Sheet *sheet, ColRowInfo *ci) { GList *l; for (l = ci->data; l; l = l->next){ Cell *cell = l->data; sheet_cell_remove_internal (sheet, cell); cell_destroy (cell); } sheet->cols_info = g_list_remove (sheet->cols_info, ci); g_list_free (ci->data); g_free (ci); } /* * Destroys a row ColRowInfo */ static void sheet_row_destroy (Sheet *sheet, ColRowInfo *ri) { sheet->rows_info = g_list_remove (sheet->rows_info, ri); row_destroy_span (ri); g_free (ri); } static void sheet_destroy_styles (Sheet *sheet) { GList *l; for (l = sheet->style_list; l; l = l->next){ StyleRegion *sr = l->data; style_destroy (sr->style); g_free (sr); } g_list_free (l); } static void sheet_destroy_columns_and_rows (Sheet *sheet) { GList *l; for (l = sheet->cols_info; l; l = l->next) sheet_col_destroy (sheet, l->data); for (l = sheet->rows_info; l; l = l->next) sheet_row_destroy (sheet, l->data); } /** * sheet_destroy: * @sheet: the sheet to destroy * * Destroys a Sheet. * * Please note that you need to unattach this sheet before * calling this routine or you will get a warning. */ void sheet_destroy (Sheet *sheet) { GList *l; g_assert (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (sheet->workbook == NULL); sheet_selections_free (sheet); g_free (sheet->name); for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; gtk_object_unref (GTK_OBJECT (sheet_view)); } g_list_free (sheet->sheet_views); g_list_free (sheet->comment_list); g_hash_table_foreach (sheet->cell_hash, cell_hash_free_key, NULL); g_hash_table_destroy (sheet->cell_hash); sheet_destroy_columns_and_rows (sheet); sheet_destroy_styles (sheet); sheet->signature = 0; g_free (sheet); } /** * sheet_clear_region: * * Clears are region of cells * * We assemble a list of cells to destroy, since we will be making changes * to the structure being manipulated by the sheet_cell_foreach_range routine */ void sheet_clear_region (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { GList *destroyable_cells, *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (start_col <= end_col); g_return_if_fail (start_row <= end_row); /* Queue a redraw for the cells being removed */ sheet_redraw_cell_region (sheet, start_col, start_row, end_col, end_row); destroyable_cells = NULL; sheet_cell_foreach_range ( sheet, TRUE, start_col, start_row, end_col, end_row, assemble_cell_list, &destroyable_cells); for (l = destroyable_cells; l; l = l->next){ Cell *cell = l->data; sheet_cell_remove (sheet, cell); cell_destroy (cell); } g_list_free (destroyable_cells); workbook_recalc (sheet->workbook); } void sheet_selection_clear (Sheet *sheet) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (l = sheet->selections; l; l = l->next){ SheetSelection *ss = l->data; sheet_clear_region (sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); } } static int clear_cell_content (Sheet *sheet, int col, int row, Cell *cell, void *user_data) { cell_set_text (cell, ""); return TRUE; } /** * sheet_clear_region_content: * @sheet: The sheet on which we operate * @start_col: starting column * @start_row: starting row * @end_col: end column * @end_row: end row * * Clears the contents in a region of cells */ void sheet_clear_region_content (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (start_col <= end_col); g_return_if_fail (start_row <= end_row); /* Queue a redraw for the region being redrawn */ sheet_redraw_cell_region (sheet, start_col, start_row, end_col, end_row); sheet_cell_foreach_range ( sheet, TRUE, start_col, start_row, end_col, end_row, clear_cell_content, NULL); workbook_recalc (sheet->workbook); } /** * sheet_selection_clear_content: * @sheet: The sheet where we operate * * Removes the contents of all the cells in the current selection. **/ void sheet_selection_clear_content (Sheet *sheet) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (l = sheet->selections; l; l = l->next){ SheetSelection *ss = l->data; sheet_clear_region_content (sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); } } static int clear_cell_comments (Sheet *sheet, int col, int row, Cell *cell, void *user_data) { cell_comment_destroy (cell); return TRUE; } /** * sheet_clear_region_comments: * @sheet: The sheet on which we operate * @start_col: starting column * @start_row: starting row * @end_col: end column * @end_row: end row * * Removes all of the comments in the cells in the specified range. **/ void sheet_clear_region_comments (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (start_col <= end_col); g_return_if_fail (start_row <= end_row); /* Queue a redraw for the region being redrawn */ sheet_redraw_cell_region (sheet, start_col, start_row, end_col, end_row); sheet_cell_foreach_range ( sheet, TRUE, start_col, start_row, end_col, end_row, clear_cell_comments, NULL); } /** * sheet_selection_clear_comments: * @sheet: The sheet where we operate * * Removes all of the comments on the range of selected cells. **/ void sheet_selection_clear_comments (Sheet *sheet) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (l = sheet->selections; l; l = l->next){ SheetSelection *ss = l->data; sheet_clear_region_comments (sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); } } static int clear_cell_format (Sheet *sheet, int col, int row, Cell *cell, void *user_data) { cell_set_format (cell, "General"); return TRUE; } void sheet_clear_region_formats (Sheet *sheet, int start_col, int start_row, int end_col, int end_row) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (start_col <= end_col); g_return_if_fail (start_row <= end_row); /* Queue a draw for the region being modified */ sheet_redraw_cell_region (sheet, start_col, start_row, end_col, end_row); sheet_cell_foreach_range ( sheet, TRUE, start_col, start_row, end_col, end_row, clear_cell_format, NULL); } void sheet_selection_clear_formats (Sheet *sheet) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (l = sheet->selections; l; l = l->next){ SheetSelection *ss = l->data; sheet_clear_region_formats (sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); } } gboolean sheet_verify_selection_simple (Sheet *sheet, char *command_name) { char *msg; if (g_list_length (sheet->selections) == 1) return TRUE; msg = g_strconcat ( "The command `", command_name, "' can not be performed with multiple selections", NULL); gnumeric_notice (sheet->workbook, GNOME_MESSAGE_BOX_ERROR, msg); g_free (msg); return FALSE; } gboolean sheet_selection_copy (Sheet *sheet) { SheetSelection *ss; g_return_val_if_fail (sheet != NULL, FALSE); g_return_val_if_fail (IS_SHEET (sheet), FALSE); g_return_val_if_fail (sheet->selections, FALSE); if (!sheet_verify_selection_simple (sheet, "copy")) return FALSE; ss = sheet->selections->data; if (sheet->workbook->clipboard_contents) clipboard_release (sheet->workbook->clipboard_contents); sheet->workbook->clipboard_contents = clipboard_copy_cell_range ( sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); return TRUE; } gboolean sheet_selection_cut (Sheet *sheet) { SheetSelection *ss; g_return_val_if_fail (sheet != NULL, FALSE); g_return_val_if_fail (IS_SHEET (sheet), FALSE); g_return_val_if_fail (sheet->selections, FALSE); if (!sheet_verify_selection_simple (sheet, "cut")) return FALSE; ss = sheet->selections->data; sheet_selection_copy (sheet); sheet_clear_region (sheet, ss->start_col, ss->start_row, ss->end_col, ss->end_row); return TRUE; } void sheet_selection_paste (Sheet *sheet, int dest_col, int dest_row, int paste_flags, guint32 time) { CellRegion *content; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (sheet->selections); content = sheet->workbook->clipboard_contents; if (content) if (!sheet_verify_selection_simple (sheet, _("Paste"))) return; clipboard_paste_region (content, sheet, dest_col, dest_row, paste_flags, time); } static void sheet_move_column (Sheet *sheet, ColRowInfo *ci, int new_column) { GList *rows, *column_cells, *l; int diff = new_column - ci->pos; /* remove the cells */ column_cells = NULL; for (rows = ci->data; rows; rows = rows->next){ Cell *cell = rows->data; sheet_cell_remove_from_hash (sheet, cell); column_cells = g_list_prepend (column_cells, cell); } /* Update the column position */ ci->pos = new_column; /* Insert the cells back */ for (l = column_cells; l; l = l->next){ Cell *cell = l->data; sheet_cell_add_to_hash (sheet, cell); cell_relocate (cell, diff, 0); } g_list_free (column_cells); } /** * sheet_insert_col: * @sheet The sheet * @col At which position we want to insert * @count The number of columns to be inserted */ void sheet_insert_col (Sheet *sheet, int col, int count) { GList *cur_col, *deps; int col_count; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); col_count = g_list_length (sheet->cols_info); if (col_count == 0) return; /* 1. Start scaning from the last column toward the goal column * moving all of the cells to their new location */ cur_col = g_list_nth (sheet->cols_info, col_count - 1); do { ColRowInfo *ci; int new_column; ci = cur_col->data; if (ci->pos < col) break; /* 1.1 Move every cell on this column count positions */ new_column = ci->pos + count; if (new_column > SHEET_MAX_COLS-1){ sheet_col_destroy (sheet, ci); /* Skip to next */ cur_col = cur_col->prev; continue; } sheet_move_column (sheet, ci, new_column); /* 1.4 Go to the next column */ cur_col = cur_col->prev; } while (cur_col); /* 2. Recompute dependencies */ deps = region_get_dependencies (sheet, col, 0, SHEET_MAX_COLS-1, SHEET_MAX_ROWS-1); cell_queue_recalc_list (deps); workbook_recalc (sheet->workbook); /* 3. Redraw */ sheet_redraw_all (sheet); } /* * sheet_delete_col * @sheet The sheet * @col At which position we want to start deleting columns * @count The number of columns to be deleted */ void sheet_delete_col (Sheet *sheet, int col, int count) { GList *cols, *deps, *destroy_list, *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); /* Is there any work to do? */ if (g_list_length (sheet->cols_info) == 0) return; /* Assemble the list of columns to destroy */ destroy_list = NULL; for (cols = sheet->cols_info; cols; cols = cols->next){ ColRowInfo *ci = cols->data; if (ci->pos < col) continue; if (ci->pos > col+count-1) break; destroy_list = g_list_prepend (destroy_list, ci); } for (l = destroy_list; l; l = l->next){ ColRowInfo *ci = l->data; sheet_col_destroy (sheet, ci); } g_list_free (destroy_list); for (cols = sheet->cols_info; cols; cols = cols->next){ ColRowInfo *ci = cols->data; if (ci->pos < col) continue; g_assert (ci->pos > col+count-1); sheet_move_column (sheet, ci, ci->pos-count); } /* Recompute dependencies */ deps = region_get_dependencies (sheet, col, 0, SHEET_MAX_COLS-1, SHEET_MAX_ROWS-1); cell_queue_recalc_list (deps); workbook_recalc (sheet->workbook); sheet_redraw_all (sheet); } /** * colrow_closest_above: * * Returns the closest column (from above) to pos */ static GList * colrow_closest_above (GList *l, int pos) { for (; l; l = l->next){ ColRowInfo *info = l->data; if (info->pos >= pos) return l; } return NULL; } /** * sheet_shift_row: * @sheet the sheet * @row row where the shifting takes place * @col first column * @count numbers of columns to shift. anegative numbers will * delete count columns, positive number will insert * count columns. */ void sheet_shift_row (Sheet *sheet, int col, int row, int count) { GList *cur_col, *deps, *l, *cell_list; int col_count, new_column; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); col_count = g_list_length (sheet->cols_info); if (count < 0){ sheet_clear_region (sheet, col, row, col - count - 1, row); cur_col = colrow_closest_above (sheet->cols_info, col); } else cur_col = g_list_nth (sheet->cols_info, col_count - 1); /* If nothing interesting found, return */ if (cur_col == NULL) return; cell_list = NULL; do { ColRowInfo *ci; ci = cur_col->data; if (count > 0){ if (ci->pos < col) break; } else { if (ci->pos < col){ cur_col = cur_col->next; continue; } } new_column = ci->pos + count; /* Search for this row */ for (l = ci->data; l; l = l->next){ Cell *cell = l->data; if (cell->row->pos > row) break; if (cell->row->pos < row) continue; cell_list = g_list_prepend (cell_list, cell); } /* Advance to the next column */ if (count > 0) cur_col = cur_col->prev; else cur_col = cur_col->next; } while (cur_col); /* Now relocate the cells */ l = g_list_nth (cell_list, g_list_length (cell_list)-1); for (; l; l = l->prev){ Cell *cell = l->data; new_column = cell->col->pos + count; /* If it overflows, remove it */ if (new_column > SHEET_MAX_COLS-1){ sheet_cell_remove (sheet, cell); cell_destroy (cell); break; } /* Relocate the cell */ sheet_cell_remove (sheet, cell); sheet_cell_add (sheet, cell, new_column, row); cell_relocate (cell, count, 0); } g_list_free (l); /* Check the dependencies and recompute them */ deps = region_get_dependencies (sheet, col, row, SHEET_MAX_COLS-1, row); cell_queue_recalc_list (deps); workbook_recalc (sheet->workbook); sheet_redraw_all (sheet); } void sheet_shift_rows (Sheet *sheet, int col, int start_row, int end_row, int count) { int i; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); g_return_if_fail (start_row <= end_row); for (i = start_row; i <= end_row; i++) sheet_shift_row (sheet, col, i, count); } /** * sheet_insert_row: * @sheet The sheet * @row At which position we want to insert * @count The number of rows to be inserted */ void sheet_insert_row (Sheet *sheet, int row, int count) { GList *cell_store, *cols, *l, *rows, *deps, *destroy_list; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); cell_store = NULL; /* 1. Walk every column, see which cells are out of range */ for (cols = sheet->cols_info; cols; cols = cols->next){ ColRowInfo *ci = cols->data; GList *cells; for (cells = ci->data; cells; cells = cells->next){ Cell *cell = cells->data; if (cell->row->pos < row) continue; /* If the new position is out of range, destroy the cell */ if (cell->row->pos + count > SHEET_MAX_ROWS-1){ sheet_cell_remove_to_eot (sheet, cells); /* Remove any trace of the tail that just got deleted */ if (cells->prev) cells->prev->next = NULL; g_list_free (cells); break; } /* At this point we now we can move the cell safely */ sheet_cell_remove_from_hash (sheet, cell); /* Keep track of it */ cell_store = g_list_prepend (cell_store, cell); } } /* 2. Relocate the row information pointers, destroy overflowed rows */ destroy_list = NULL; for (rows = sheet->rows_info; rows; rows = rows->next){ ColRowInfo *ri = rows->data; if (ri->pos < row) continue; if (ri->pos + count > SHEET_MAX_ROWS-1){ destroy_list = g_list_prepend (destroy_list, ri); continue; } ri->pos += count; } /* Destroy those row infos that are gone */ for (l = destroy_list; l; l = l->next){ ColRowInfo *ri = l->data; sheet_row_destroy (sheet, ri); } g_list_free (destroy_list); /* 3. Put back the moved cells in their new spot */ for (l = cell_store; l; l = l->next){ Cell *cell = l->data; sheet_cell_add_to_hash (sheet, cell); cell_relocate (cell, 0, count); } g_list_free (cell_store); /* 4. Recompute any changes required */ deps = region_get_dependencies (sheet, 0, row, SHEET_MAX_COLS-1, SHEET_MAX_ROWS-1); cell_queue_recalc_list (deps); workbook_recalc (sheet->workbook); /* 5. Redraw everything */ sheet_redraw_all (sheet); } /** * sheet_delete_row: * @sheet The sheet * @row At which position we want to delete * @count The number of rows to be deleted */ void sheet_delete_row (Sheet *sheet, int row, int count) { GList *destroy_list, *cols, *rows, *cell_store, *deps, *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); /* 1. Remove cells from hash tables and grab all dangling rows */ cell_store = NULL; for (cols = sheet->cols_info; cols; cols = cols->next){ ColRowInfo *ci = cols->data; GList *cells; destroy_list = NULL; for (cells = ci->data; cells; cells = cells->next){ Cell *cell = cells->data; if (cell->row->pos < row) continue; sheet_cell_remove_from_hash (sheet, cell); if (cell->row->pos >= row && cell->row->pos <= row+count-1){ destroy_list = g_list_prepend (destroy_list, cell); continue; } cell_store = g_list_prepend (cell_store, cell); } /* Destroy the cells in the range */ for (l = destroy_list; l; l = l->next){ Cell *cell = l->data; cell->col->data = g_list_remove (cell->col->data, cell); cell_destroy (cell); } g_list_free (destroy_list); } /* 2. Relocate row information pointers, destroy unused rows */ destroy_list = NULL; for (rows = sheet->rows_info; rows; rows = rows->next){ ColRowInfo *ri = rows->data; if (ri->pos < row) continue; if (ri->pos >= row && ri->pos <= row+count-1){ destroy_list = g_list_prepend (destroy_list, ri); continue; } ri->pos -= count; } for (l = destroy_list; l; l = l->next){ ColRowInfo *ri = l->data; sheet_row_destroy (sheet, ri); } g_list_free (destroy_list); /* 3. Put back the cells at their new location */ for (l = cell_store; l; l = l->next){ Cell *cell = l->data; sheet_cell_add_to_hash (sheet, cell); cell_relocate (cell, 0, -count); } g_list_free (cell_store); /* 4. Recompute dependencies */ deps = region_get_dependencies (sheet, 0, row, SHEET_MAX_COLS-1, SHEET_MAX_ROWS-1); cell_queue_recalc_list (deps); workbook_recalc (sheet->workbook); /* 5. Redraw everything */ sheet_redraw_all (sheet); } /** * sheet_shift_col: * @sheet the sheet * @row first row * @col column where the shifting takes place * @count numbers of rows to shift. a negative numbers will * delete count rows, positive number will insert * count rows. */ void sheet_shift_col (Sheet *sheet, int col, int row, int count) { GList *row_list, *cur_row, *deps, *cell_list, *l; ColRowInfo *ci; int row_count; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); ci = sheet_col_get_info (sheet, col); /* Check if the column did exist, if not, then shift_col is a no-op */ if (ci->pos != col) return; if (count < 0){ sheet_clear_region (sheet, col, row, col, row -count - 1); ci = sheet_col_get_info (sheet, col); row_list = ci->data; cur_row = colrow_closest_above (row_list, row); } else { row_list = ci->data; row_count = g_list_length (row_list); cur_row = g_list_nth (row_list, row_count-1); } /* If nothing interesting found, return */ if (cur_row == NULL) return; cell_list = NULL; do { Cell *cell = cur_row->data; int new_row; if (count > 0){ if (cell->row->pos < row) break; } else { if (cell->row->pos < row){ cur_row = cur_row->next; continue; } } new_row = cell->row->pos + count; cell_list = g_list_prepend (cell_list, cell); /* Advance to next row */ if (count > 0) cur_row = cur_row->prev; else cur_row = cur_row->next; } while (cur_row); /* Relocate the cells */ l = g_list_nth (cell_list, g_list_length (cell_list)-1); for (; l; l = l->prev){ Cell *cell = l->data; int old_pos = cell->row->pos; int new_row = old_pos + count; sheet_cell_remove (sheet, cell); /* if it overflows */ if (new_row > SHEET_MAX_ROWS-1){ cell_destroy (cell); continue; } sheet_cell_add (sheet, cell, col, new_row); cell_relocate (cell, 0, count); } g_list_free (cell_list); /* Recompute dependencies on the changed data */ deps = region_get_dependencies (sheet, col, row, col, SHEET_MAX_ROWS-1); cell_queue_recalc_list (deps); workbook_recalc (sheet->workbook); sheet_redraw_all (sheet); } /** * sheet_shift_cols: * @sheet the sheet * @start_col first column * @end_col end column * @row first row where the shifting takes place. * @count numbers of rows to shift. a negative numbers will * delete count rows, positive number will insert * count rows. */ void sheet_shift_cols (Sheet *sheet, int start_col, int end_col, int row, int count) { int i; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (count != 0); for (i = start_col; i <= end_col; i++) sheet_shift_col (sheet, i, row, count); } void sheet_style_attach (Sheet *sheet, int start_col, int start_row, int end_col, int end_row, Style *style) { StyleRegion *sr; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (style != NULL); g_return_if_fail (start_col <= end_col); g_return_if_fail (start_row <= end_row); sr = g_new (StyleRegion, 1); sr->range.start_col = start_col; sr->range.start_row = start_row; sr->range.end_col = end_col; sr->range.end_row = end_row; sr->style = style; sheet->style_list = g_list_prepend (sheet->style_list, sr); } /** * sheet_style_compute: * @sheet: Which sheet we are looking up * @col: column * @row: row * @non_default: A pointer where we store the attributes * the cell has which are not part of the * default style. */ Style * sheet_style_compute (Sheet *sheet, int col, int row, int *non_default) { GList *l; Style *style; g_return_val_if_fail (sheet != NULL, NULL); g_return_val_if_fail (IS_SHEET (sheet), NULL); style = style_new_empty (); /* Look in the styles applied to the sheet */ for (l = sheet->style_list; l; l = l->next){ StyleRegion *sr = l->data; int is_default_style = l->next == NULL; int flags; flags = style->valid_flags; if (range_contains (&sr->range, col, row)){ style_merge_to (style, sr->style); if (style->valid_flags == STYLE_ALL){ if (non_default){ if (!is_default_style) *non_default = STYLE_ALL; else *non_default = flags; } return style; } } } g_warning ("Strange, no style available here\n"); return style_new (); } void sheet_make_cell_visible (Sheet *sheet, int col, int row) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_make_cell_visible (gsheet, col, row); } } void sheet_cursor_move (Sheet *sheet, int col, int row) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); sheet_accept_pending_input (sheet); sheet->cursor_col = col; sheet->cursor_row = row; for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_cursor_set (gsheet, col, row); gnumeric_sheet_set_cursor_bounds (gsheet, col, row, col, row); } sheet_load_cell_val (sheet); } void sheet_cursor_set (Sheet *sheet, int base_col, int base_row, int start_col, int start_row, int end_col, int end_row) { GList *l; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (start_col <= end_col); g_return_if_fail (start_row <= end_row); sheet_accept_pending_input (sheet); sheet->cursor_col = base_col; sheet->cursor_row = base_row; for (l = sheet->sheet_views; l; l = l->next){ GnumericSheet *gsheet = GNUMERIC_SHEET_VIEW (l->data); gnumeric_sheet_cursor_set (gsheet, base_col, base_row); gnumeric_sheet_set_cursor_bounds ( gsheet, start_col, start_row, end_col, end_row); } sheet_load_cell_val (sheet); } void sheet_fill_selection_with (Sheet *sheet, char *str) { GList *l; int col, row; g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); g_return_if_fail (str != NULL); for (l = sheet->selections; l; l = l->next){ SheetSelection *ss = l->data; for (col = ss->start_col; col <= ss->end_col; col++) for (row = ss->start_row; row <= ss->end_row; row++) sheet_set_text (sheet, col, row, str); } } void sheet_hide_cursor (Sheet *sheet) { GList *l; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_hide_cursor (sheet_view); } } void sheet_show_cursor (Sheet *sheet) { GList *l; for (l = sheet->sheet_views; l; l = l->next){ SheetView *sheet_view = l->data; sheet_view_show_cursor (sheet_view); } } char * cellref_name (CellRef *cell_ref, Sheet *eval_sheet, int eval_col, int eval_row) { static char buffer [sizeof (long) * 4 + 4]; char *p = buffer; int col, row; if (cell_ref->col_relative) col = eval_col + cell_ref->col; else { *p++ = '$'; col = cell_ref->col; } if (col <= 'Z'-'A'){ *p++ = col + 'A'; } else { int a = col / ('Z'-'A'+1); int b = col % ('Z'-'A'+1); *p++ = a + 'A' - 1; *p++ = b + 'A'; } if (cell_ref->row_relative) row = eval_row + cell_ref->row; else { *p++ = '$'; row = cell_ref->row; } sprintf (p, "%d", row+1); /* If it is a non-local reference, add the path to the external sheet */ if (cell_ref->sheet == eval_sheet || cell_ref->sheet == NULL) return g_strdup (buffer); else { Sheet *sheet = cell_ref->sheet; char *s; if (strchr (sheet->name, ' ')) s = g_strconcat ("\"", sheet->name, "\"!", buffer, NULL); else s = g_strconcat (sheet->name, "!", buffer, NULL); return s; } } void sheet_mark_clean (Sheet *sheet) { g_return_if_fail (sheet != NULL); g_return_if_fail (IS_SHEET (sheet)); sheet->modified = FALSE; } /** * sheet_lookup_by_name: * @sheet: Local sheet. * @name: a sheet name. * * This routine parses @name for a reference to another sheet * in this workbook. If this fails, it will try to parse a * filename in @name and load the given workbook and lookup * the sheet name from that workbook. * * The routine might return NULL. */ Sheet * sheet_lookup_by_name (Sheet *base, char *name) { Sheet *sheet; g_return_val_if_fail (base != NULL, NULL); g_return_val_if_fail (IS_SHEET (base), NULL); /* * FIXME: currently we only try to lookup the sheet name * inside the workbook, we need to lookup external files as * well. */ sheet = workbook_sheet_lookup (base->workbook, name); if (sheet) return sheet; return NULL; }